Programming/Spring Boot

Spring Boot 외장 톰캣에 WAR 배포하는 방법(+ 404 발생하는 원인)

Jan92 2024. 6. 26. 23:30

Spring Boot 외장 톰캣에 WAR 배포하는 방법(+ 404 발생하는 원인)

 

스프링부트 프로젝트의 경우 일반적으로 내장된 톰캣 서버를 통해 독립 실행형 애플리케이션을 생성하여 편리하게 배포하고 운영할 수 있다는 장점이 있습니다.

하지만 특수한 경우 jar 파일이 아닌 war 파일로 빌드하여 외장 톰캣에 직접 애플리케이션을 배포해야 하는 경우도 있는데요.

 

해당 포스팅에서는 Spring Boot 애플리케이션을 war 파일로 빌드하고 이를 외장 톰캣에 배포하는 과정을 정리하였습니다.

+ 추가적으로 배포 과정에서 404 에러가 계속해서 발생하였는데 그 원인도 함께 살펴보겠습니다.

 

*** 아래 과정은 'Spring Boot 3.3.0' 버전과 'gradle' 환경, 'tomcat-10.1.25' 버전에서 진행되었습니다.

 


1. SpringBootServletInitializer 상속 및 configure 메서드 재정의

@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(DemoApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

 

Spring Boot 프로젝트를 war 파일로 배포하기 위한 첫 번째 단계는 메인 클래스에서 SpringBootServletInitializer 추상 클래스를 상속받아 configure 메서드를 오버라이딩 하는 것입니다.

 

*** 해당 과정이 필요한 이유

 

스프링부트 애플리케이션은 내장 서버를 사용할 때 'main()' 메서드를 진입점으로 사용하는데요.

main() 메서드 내부적으로 SpringApplication.run() 메서드를 호출하여 웹 애플리케이션을 실행하는데 필요한 빈들을 스캔하고 구성합니다. 그리고 이 과정에서 내장 서버(Tomcat, Jetty, Undertow 등)가 동작하게 되는 것인데요.

 

하지만 스프링부트 애플리케이션을 서블릿 컨테이너(Apache Tomcat)에서 배포하게 되는 경우에는 ServletContext를 설정해야 하고, 이를 위해 초기화(initializer) 과정이 필요한 것입니다.

 

추가적으로 Servlet 3.0 이전에는 서블릿 컨테이너가 구동될 때 /WEB-INF 디렉터리에 존재하는 web.xml 설정 파일을 읽어 ServletContext를 설정하고 초기화했지만, Servlet 3.0 이상 환경에서는 어노테이션 기반 설정과 프로그래밍 방식의 설정이 가능해졌으며, Spring에서는 이를 활용하기 위해 WebApplicationInitializer 인터페이스를 제공하기 시작했습니다.

(Servlet 3.0 스펙은 톰캣 기준 Apache Tomcat 7.0 버전부터 지원되었습니다.)

 

SpringBootServletInitializer 추상 클래스 내용 중 일부

정리하자면, 스프링부트 애플리케이션을 서블릿 컨테이너에 배포하게 되는 경우 ServletContext를 설정하고 초기화하는 것이 필요합니다.

그리고 Spring에서는 Servlet 3.0 이상의 환경에서 ServletContext를 프로그래밍적으로 다룰 수 있는 WebApplicationInitializer 인터페이스를 제공하였으며, 위 과정에서 사용된 SpringBootServletInitializer 추상 클래스는 WebApplicationInitializer 인터페이스의 구현체입니다.

 

 


2. build.gradle 수정

build.gradle 수정

apply plugin: 'war'
...

dependencies {
	...

	// war 파일 생성시 톰캣 라이브러리를 포함하지 않도록 추가
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}

 

다음으로는 'build.gradle' 파일에 위 설정을 추가해 줍니다.

apply plugin: 'war' 설정이 추가되면 gradle의 build 툴에 'bootWar', 'war' 기능이 추가로 생긴 것을 확인할 수 있는데요.

dependencies 부분에 추가된 providedRuntime 부분은 war 파일 생성 시 톰캣 라이브러리를 포함하지 않도록 하는 설정입니다.

 

 

bootWar, war 구조

build.gradle 파일에 수정 사항을 적용한 후 bootWar, war를 통해 war 파일을 각각 빌드해 보면 bootWar와 war는 다음과 같은 구조 차이를 가지게 되는데요.

 

차이점을 간단하게 설명하자면 bootWar의 경우 jar와 동일하게 자체적(독립적)으로 실행 가능한 war 파일을 생성하기 때문에 WEB-INF/lib-provided 디렉터리에 톰캣 관련 라이브러리를 가지고 있게 됩니다.

 

하지만 war의 경우 오로지 외장 톰캣에 배포할 목적으로 생성되는 war 파일이기 때문에 자체적으로 실행이 불가능하다는 차이점을 가지고 있습니다.

(bootWar로 생성된 war 파일과 다르게 war 파일 내부적으로 톰캣 관련 라이브러리를 가지고 있지 않습니다.)

 

 

    <!-- packaging war 추가 -->
    <packaging>war</packaging>
    ...

    <dependencies>
        ...
		
        <!-- war 파일 생성시 톰캣 라이브러리를 포함하지 않도록 추가 --> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

 

추가로 maven을 사용하시는 경우 'pom.xml' 파일을 다음과 같이 수정하시면 됩니다.

 

 


3. 외장 톰캣에 배포 확인(+ 404 발생 원인)

외장 톰캣에 war 파일 배포

1, 2번 과정을 통해 생성된 war 파일을 외장 톰캣에 배포하기 위해서는 {TOMCAT_INSTANCE_DIR}/webapps 디렉터리에 빌드된 war 파일을 넣고 톰캣을 실행하면 war 파일의 압축이 해제되면서 프로젝트가 실행됩니다.

(테스트의 편의를 위해 demo-0.0.1-SNAPSHOT.war와 같은 파일 이름을 demo.war로 수정하였습니다.)

 

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping
    public String testGetMethod() {
        return "testGetMethod!";
    }
}

(프로젝트가 정상적으로 동작하는지 확인하기 위해 추가한 간단한 api)

 

실행된 프로젝트에서는 요청 경로에 주의해야 하는데요.

테스트를 위한 위 api 요청은 '/test'가 아니라 '/demo/test'로 요청해야 정상적으로 동작하는 것을 확인할 수 있습니다.

 

그 이유는 외장 톰캣에 요청 경로에 대한 설정을 따로 하지 않았기 때문에 요청 경로 가장 앞에 war 파일의 이름(여기서는 '/demo')이 경로로 추가되기 때문입니다.

(war 파일의 이름을 ROOT로 하여 배포하는 경우 war 파일 이름이 경로로 붙지 않습니다.)

 

 

출처 - https://tomcat.apache.org/whichversion.html

위 과정을 통해 정상적으로 war 파일을 생성하고 배포하였는데도 요청 시 404 에러가 발생한다면 해당 프로젝트의 java 버전을 외장 톰캣에서 지원하지 못하는 것이 원인일 수 있습니다.

 

 

 

< 참고 자료 >

https://docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html#howto.traditional-deployment.war
https://medium.com/@SlackBeck/spring-boot-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-war%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%A0-%EB%95%8C-%EC%99%9C-springbootservletinitializer%EB%A5%BC-%EC%83%81%EC%86%8D%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B1%B8%EA%B9%8C-a07b6fdfbbde

 

< Spring Boot 내장 톰캣 관련 자료 >

2024.02.06 - [Programming/Spring Boot] - 스프링부트 내장 톰캣(서블릿 컨테이너) 실행되는 과정

2024.02.06 - [Programming/Spring Boot] - 스프링 부트 내장 톰캣 제거 방법 (exclude embedded tomcat)