💡이 포스팅은 김영한 님의 인프런 강의인 스프링 부트 - 핵심 원리와 활용을 수강하고 학습한 내용을 정리한 포스팅입니다.
김영한 님의 강의를 수강하며 정리한 GitHub Repository입니다.
이번 챕터에서 정리할 내용은 다음과 같습니다.
- .DS_Store
- 배포 방식의 차이 war vs jar
- Tomcat 10.x Issue
- war build
- ServletContainerInitializer, META-INF/services
- WebApplicationInitializer
- @HandlesTypes
.DS_Store
영한님이 제공해 주신 예제 파일을 열어봤는데 폴더마다 .DS_Store 파일이 존재하길래 대체 이건 뭐지?라는 생각이 들었습니다.
찾아보니 .DS_Store 파일은 애플에서 정의한 파일 포맷이며 Desktop Service Store의 약자라고 합니다.
Mac OS X 시스템이 finder로 폴더에 접근할 때 자동으로 생기며, 이때 폴더의 메타데이터를 저장한다고 합니다.
프로젝트와는 전혀 무관한 파일이고 해당 폴더의 메타데이터이기 때문에 폴더에 접근할 때마다 .DS_Store 파일의 내용이 변경될 수 있습니다.
그래서 만약 .DS_Store 파일이 git에 올라가게 된다면 .DS_Store 파일로 인해 conflict가 발생할 수도 있기 때문에 .DS_Store를 git에 등록하면 안 됩니다.
저는 window를 사용하고 있기 때문에 해당 파일을 볼 일은 없었지만, window에서는 thumb.db가 이와 비슷한 파일이라고 합니다.
배포 방식의 차이 war vs jar
토비 님의 스프링 부트 강의 포스팅에서도 한 번 정리한 내용인데, 복습 차원에서 간단하게 다시 정리해 보도록 하겠습니다.
왼쪽은 Spring Boot 이전에 전통적인 배포 방식입니다. (WAR)
반면 오른쪽은 Spring Boot의 배포 방식입니다. (JAR)
그래서 전통적인 legacy 방식과 Spring Boot 방식의 차이를 war vs jar의 차이라고 볼 수 있습니다.
WAR(Web Application Archive)
war 방식의 배포는 먼저 Tomcat 같은 WAS를 별도로 설치해야 하며, WAS에 war 파일을 전달함으로써 배포를 해야 했습니다.
그렇기 때문에 개발자는 먼저 WAS에 대한 지식이 필요하며, 작성한 소스를 WAS에서 동작할 수 있도록 서블릿 스펙에 맞출 필요가 있습니다.
그리고 마지막으로 개발 환경에서도 코드를 실행하기 위해 WAS와 연동하는 등의 추가 설정이 필요합니다.
JAR(Java Archive)
jar 방식은 Spring Boot가 Tomcat 같은 WAS를 내장하고 있기 때문에 별도로 WAS를 설치할 필요가 없으며, 그렇기 때문에 war로 빌드할 필요가 없으며 jar 빌드가 가능합니다.
또 jar로 빌드했기 때문에 main() 메서드를 실행하면 되어, war처럼 WAS를 설치하고 설정할 필요가 없습니다.
💡강의에서 영한님이 권장하시는 부분인데 현재에 와서는 Spring Boot를 적극 사용하고 있기 때문에 개발자들이
war를 만들어서 배포하는 일이 없어지고 있습니다.
그렇지만 과거의 war 방식이 어떤지 알아야 현재의 jar 방식이 왜 이렇게 사용되는지 더 깊이 이해할 수 있기 때문에 war 방식으로도 한 번은 경험을 해보는 것이 좋을 것 같습니다.
Tomcat 10.X Issue
먼저 강의에서 사용하는 기술과 버전입니다.
Java 17 이상 (Spring Boot 3.0부터 JDK 17 이상 버전이 필수)
Spring Boot 3.X
Tomcat 10.X
저는 기존에 설치되어 있던 Tomcat 8.X or Tomcat 9.X 버전이 있었기 때문에 굳이 10.X 버전을 설치하지 않고 환경 설정을 했었는데요.
아래와 같이 404 에러가 발생했었습니다.
이유는 어찌 보면 당연했던 것이었는데요,
Spring 5.X → Spring 6.X
Spring Boot 2.X → Spring Boot 3.X
로 메이저 버전이 올라감에 따라 크게 변경된 부분이 하나 있었습니다.
javax.* → jakarta.*
바로 패키지 구조인데요, javax에서 jakarta로 변경되었습니다.
이 부분이 Tomcat에서도 동일하게 적용된 것인데
Tomcat 9.X 까지는 javax
Tomcat 10.X부터는 jakarta로 변경되어 에러가 발생했던 것이었습니다.
이 부분은 Tocmat 10.X 다운로드 페이지에서도 설명이 되어 있습니다.
따라서 만약 Spring Boot 2.X 버전으로 강의를 들으실 경우에는 Tomcat 9.X 버전을 사용하고
Spring Boot 3.X 버전으로 강의를 들으실 경우에는 Tomcat 10.X를 사용해야 합니다.
강의에서는 그냥 Tomcat 10.X 버전을 사용하라고만 하셔서 이번 기회에 그 이유에 대해 알게 되었습니다.
war build
강의에서 war로 빌드하기 위해 build.gradle에 아래와 같이 옵션을 추가해 주었는데요
plugins {
id 'java'
id 'war' // 추가
}
찾아보니 공식 문서에서는 아래와 같은 방식으로 소개하고 있었습니다.
...
apply plugin: 'war'
두 설정 모두 실행해 보니 정상적으로 동작해서 둘 중 아무거나 사용해도 될 것 같습니다.
war {
archiveName("demo.war") // war 파일 이름 설정
}
그리고 위와 같이 war 빌드 시 추가 설정을 할 수도 있습니다.
ServletContainerInitializer, META-INF/services
과거에는 web.xml을 사용해서 서블릿 컨테이너를 초기화했지만, 지금은 서블릿 스펙에서 자바 코드를 사용한 초기화도 지원합니다.
서블릿은 ServletContainerInitializer라는 초기화 인터페이스를 제공하는데, 해당 인터페이스를 구현하여 onStartup() 메서드에 초기화 시 수행할 기능들을 작성하면 됩니다.
라이브러리/런타임이 웹 애플리케이션의 시작 단계를 알리고 이에 대한 응답으로 필요한 서블릿, 필터 및 리스너의 프로그래밍 방식 등록을 수행할 수 있는 인터페이스입니다.
이 인터페이스의 구현은 META-INF/services 디렉터리 내에 있는 JAR 파일 리소스에 의해 선언되어야 하고 이 인터페이스의 정규화된 클래스 이름으로 명명되어야 하며 런타임의 서비스 제공자 조회 메커니즘 또는 컨테이너 특정 메커니즘을 사용하여 검색됩니다.
위에 설명에서도 나왔듯이 특이했던 점은 WAS에게 실행할 초기화 클래스를 명시적으로 알려줘야 하는데, 이때 경로는 META-INF/servlces/jakarta.servlet.ServletContainerInitializer입니다.
해당 파일에 실행할 초기화 클래스의 경로를 작성해 주면 되는데, 이번 강의에서는 아래 두 클래스를 작성했습니다.
💡이렇게 작성하고 실행하게 되면 서블릿은 초기화 클래스가 2개 있다는 것을 인지하고 해당 클래스를 호출하여 초기화를 진행하게 됩니다.
이때 초기화를 담당하는 클래스들은 ServletContainerInitializer interface를 구현하고 있어야 합니다.
만약 구현하지 않을 경우 cast 에러가 발생합니다.
WebApplicationInitializer
이렇게 web.xml을 사용하지 않고도 서블릿 초기화 설정을 할 수 있는 것을 알아보았는데요,
그렇다면 Spring에서는 어떤 방식을 사용하고 있는지에 대해 궁금해졌습니다.
Spring도 이와 동일한 방식을 사용하고 있으며, web/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 초기화를 진행할 클래스가 작성되어 있습니다.
초기화 시 호출되는 클래스는 SpringServletContainerInitializer인데 한 번 확인해 보겠습니다.
Spring에서 제공하는 ServletContainerInitializer는 기존의 web.xml 기반 접근 방식과 반대되는(또는 가능한 조합으로) Spring의 WebApplicationInitializer SPI를 사용하여 서블릿 컨테이너의 코드 기반 구성을 지원하도록 설계되었
습니다.
작동 메커니즘
이 클래스는 로드되고 인스턴스화되며 클래스 경로에 spring-web 모듈 JAR이 있다고 가정하고 컨테이너 시작 중에 서블릿 호환 컨테이너에 의해 onStartup 메서드가 호출됩니다. 이는 spring-web 모듈의 META-INF/services/jakarta.servlet.ServletContainerInitializer 서비스 공급자(=SpringServletContainerInitializer) 구성 파일을 감지하는 JAR 서비스 API ServiceLoader.load(Class) 메서드를 통해 발생합니다.
Spring의 WebApplicationInitializer SPI는 단 하나의 메서드인 WebApplicationInitializer.onStartup(ServletContext)로 구성됩니다. 서명은 의도적으로 ServletContainerInitializer.onStartup(Set, ServletContext)와 매우 유사합니다. 간단히 말해 SpringServletContainerInitializer는 ServletContext를 인스턴스화하고 사용자 정의 WebApplicationInitializer 구현에 위임하는 역할을 합니다. 그런 다음 ServletContext를 초기화하는 실제 작업을 수행하는 것은 각 WebApplicationInitializer의 책임입니다. 위임의 정확한 프로세스는 아래의 onStartup 설명서에 자세히 설명되어 있습니다.
SpringServletContainerInitializer는 아까 설명한 ServletContainerInitializer 인터페이스를 구현하고 있으며 @HandlesTypes 애노테이션을 사용하여 WebApplicationInitializer 클래스를 전달받고 있습니다.
이를 인지하고 Spring에서 초기화 작업을 추가하고 싶은 경우에는 WebApplicationInitializer 인터페이스를 구현한 구현체를 만든 후 해당 구현체를 Bean으로 등록해 주면 될 것 같습니다.
@HandlesTypes
@HandlesTypes 애노테이션은 ServletContainerInitializer가 처리할 수 있는 Class 또는 Interface를 지정하며 지정한 Class 또는 Interface의 구현체들을 ServletContainerInitializer의 onStartup 메서드의 인자로 전달하게 됩니다.
만약 일치하는 Class가 없다면 null을 전달하게 됩니다.
'Lecture > 김영한 - 스프링 부트 - 핵심 원리와 활용' 카테고리의 다른 글
스프링 부트 - 핵심 원리와 활용 - 외부설정과 프로필1 (0) | 2023.03.12 |
---|---|
스프링 부트 - 핵심 원리와 활용 - 자동 구성(Auto Configuration) (4) | 2023.03.12 |
스프링 부트 - 핵심 원리와 활용 - 스프링 부트 스타터와 라이브러리 관리 (0) | 2023.03.12 |
스프링 부트 - 핵심 원리와 활용 - 스프링 부트와 내장 톰캣 (0) | 2023.03.12 |
스프링 부트 - 핵심 원리와 활용 - 시작하기 (0) | 2023.03.11 |
댓글