Spring Boot Embedded Tomcat
Spring Boot 프로젝트를 WAR 파일로 빌드하면 Tomcat과 같은 외장 WAS에 배포해야 실행이 가능하다.
그런데 Spring Boot에는 내부적으로 Tomcat이나 Undertow와 같은 서버가 내장되어 있어, 애플리케이션을 JAR 파일로 빌드하고 실행하는 것만으로도 웹 애플리케이션을 서비스할 수 있다.
Spring Boot 배포 방법 WAR에서 JAR로 변경하기
기존에 WAR 파일로 빌드되던 Spring Boot 프로젝트를 JAR 파일로 빌드되도록 변경하려고 한다.
1. WAR 플러그인 및 의존 구성 제거
기존에 WAR 파일로 빌드하는 프로젝트였다면 Java 플러그인의 디폴트인 JAR 파일 생성을 비활성화시키고 WAR 테스크를 기본 동작으로 추가하기 위해 WAR 플러그인이 추가되어 있었을 것이다.
다시 Java 플러그인의 디폴트 JAR 파일 생성이 활성화 되도록 빌드 스크립트에서 WAR 플러그인을 제거한다.
plugins {
id "war" // 제거
}
WAR 플러그인은 2개의 의존 구성을 더한다.
1) providedCompile
2) providedRuntime
이 구성들은 컴파일이나 런타임 시에는 필요하지만 WAR 파일로 배포될 때는 제외된다.
WAR 파일로 빌드될 때 Spring Boot의 내장 톰캣은 제외되도록 하기 위해 implementation이 아닌 providedRuntime으로 톰캣 라이브러리가 선언되어 있을 것이다. 이를 제거한다.
// 제거
configurations {
providedRuntime
}
..
dependencies {
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") // 제거
..
}
2. 메인 클레스에서 SpringBootServletInitializer 상속 제거
WAR 파일로 빌드하는 Spring Boot 프로젝트의 메인 클래스를 열어보면 SpringBootServletInitializer 클래스를 상속받고 있다. SpringBootServletInitializer는 무엇이고 왜 상속받는 걸까?
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
SpringBootServletInitializer는 무엇인가.
SpringBootServletInitializer는 WAR 파일을 생성하기 위해 상속받는 클래스이다.
즉, JAR 파일로 배포할 것이라면 상속받지 않아도 된다.
WAR 파일로 빌드하려면 SpringBootServletInitializer를 왜 상속받는가.
과거 Spring은 톰캣과 같은 외장 서블릿 컨테이너에서 동작하도록 하려면 web.xml 파일에 애플리케이션 컨텍스트를 추가해야 했다.
그런데 현재 Spring Boot 프로젝트들은 web.xml 파일 없이도 외장 서블릿 컨테이너에서 실행이 가능하다.
Spring Framework는 Servlet 3.0 이상 환경에서 애플리케이션 컨텍스트 설정을 프로그래밍으로 다룰 수 있도록 web.xml을 대신하여 WebApplicationInitializer 인터페이스를 제공한다.
그리고 SpringBootServletInitializer는 WebApplicationInitializer 인터페이스의 구현체이다.
따라서 현재 Spring Boot는 SpringBootServletInitializer 클래스 상속을 통해 외장 톰캣에서 애플리케이션이 동작할 수 있는 것이다.
- 참고: Spring Boot 웹 애플리케이션을 WAR로 배포할 때 왜 SpringBootServletInitializer를 상속해야 하는 걸까?
3. 빌드 실행하기
gradle build 명령어로 빌드를 실행하면 프로젝트 하위에 build 디렉터리가 생성된다. 나는 IntelliJ IDEA의 오른쪽 Gradle 툴바에서 build 버튼을 클릭하여 실행하였다.
결과물로 프로젝트 폴더/build/libs 안에 JAR 파일이 생성된다.
빌드 Task들 중에서 테스트 코드를 검증하는 test Task 때문에 빌드 실패가 날 수도 있다. 모든 테스트 코드가 항상 성공하는 것이 가장 베스트겠지만 그렇지 않을 수도 있다. 그럴 경우에는 일단 빌드 스크립트의 test 의존 구성에 exclude 옵션을 추가한다.
test Task에서 제외할 경로를 명시하는 것인데, 와일드카드(*) 표현식을 사용해서 모든 경로를 제외했다.
test {
exclude { '/**/*' }
useJUnitPlatform()
}
4. 웹서버 커스텀하게 설정하기
위의 3번째 단계까지 실행하면 빌드는 잘 된다.
그런데 내장 웹서버의 설정을 디폴트 값이 아닌 커스텀하게 설정해야 할 때가 있다.
외장 톰캣의 경우에는 Apache Tomcat을 다운로드하면 conf/server.xml이라는 설정 파일 안에 설정 값이 작성되어 있다.
Spring Boot의 내장 톰캣을 사용하는 경우에는 application.yml(또는 properties) 속성 파일 안에 프로퍼티 값을 추가하면 된다.
그런데 프로퍼티 값을 통해 아래 두 설정 값을 application.yml에 등록하고자 하였지만 실패했다.
- server.tomcat.keep-alive-timeout
: Tomcat이 keep-alive 연결을 닫기 전에 다른 요청을 기다리는 시간을 구성
- server.tomcat.max-keep-alive-requests
: keep-alive 연결이 닫히기 전에 수행할 수 있는 최대 요청 수를 제어
찾아보니 Spring Boot의 자동 설정을 담당하는 클래스 중 하나인 ServerProperties의 Tomcat 클래스 안에 해당 값들이 존재하지 않았다.
현재 내가 작업 중이던 Spring Boot는 2.3.3 버전인데 해당 값들은 Spring Boot 2.5 버전대부터 새롭게 추가된 프로퍼티라서 등록을 할 수 없었다.
- 참고: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.5-Release-Notes#miscellaneous
Spring Boot 버전 업그레이드를하는 방법이 가장 깔끔하지만 버전 업그레이드로 인해 추가적으로 파생되는 이슈들이 우려되므로 일단은 WebServerFactoryCustomizer 클래스를 이용하는 방법을 선택했다.
WebServerFactoryCustomizer 클래스
일반적으로 application.properties 또는 application.yml 파일에 새 항목을 추가하여 웹 서버를 설정한다.
그러나 추가하고 싶은 프로퍼티가 없는 경우 WebServerFactoryCustomizer 클래스를 사용하면 Server Factory에 접근하여 구성 요소를 커스텀하게 설정할 수 있다.
@Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final int MAX_KEEP_ALIVE_REQUESTS = 50;
private static final int KEEP_ALIVE_TIMEOUT = 20000;
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
ProtocolHandler protocolHandler = connector.getProtocolHandler();
if (protocolHandler instanceof AbstractHttp11Protocol<?>) {
AbstractHttp11Protocol<?> handler = (AbstractHttp11Protocol<?>) protocolHandler;
handler.setMaxKeepAliveRequests(MAX_KEEP_ALIVE_REQUESTS);
handler.setKeepAliveTimeout(KEEP_ALIVE_TIMEOUT);
...
}
}
- 참고(WebServerFactoryCustomizer 사용 방법): https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.configure
'개발 > Spring' 카테고리의 다른 글
QueryDSL + multi data source 연동하기 (0) | 2021.09.13 |
---|---|
webflux + reactive redis cache 적용하기 (2) | 2021.08.23 |
Spring-Cloud-Data-Flow(SCDF)구축해보기 (4) | 2020.11.10 |