Spring Boot 의 Web MVC 설정 방법에 대해 정리 한다.
환경이 바뀌었으니 새로운 프로젝트를 만드는 것도 좋겠지만 정리에 사용중인 프로젝트를 수정하려고 한다.
우선 POM.xml 파일을 수정해서 Spring Boot 를 사용할 수 있도록 수정한다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.nimkoes</groupId>
<artifactId>SpringWebMVCSandbox</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringWebMVCSandbox Maven Webapp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Spring Boot 를 사용하기 위해
1. parent POM 을 지정
2. 의존성에 view template 과 spring web 과 관련한 starter 를 추가
3. war 가 아닌 jar 패키징
4. build 영역의 plugin 설정
을 수정 했다.
view template 과 관련 된 thymeleaf 와 lombok 그리고 starter test 는 굳이 추가하지 않아도 된다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/>
</parent>
<groupId>me.nimkoes</groupId>
<artifactId>SpringWebMVCSandbox</artifactId>
<version>1.0-SNAPSHOT</version>
<name>SpringWebMVCSandbox Maven Webapp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
그리고 Spring Boot 가 기본적으로 사용하는 디렉토리 구조를 맞춰주기 위해 src/main 경로에 resources, static, templates 그리고 application.properties 파일을 만들어 주었다.
이 경로와 파일은 따로 설정하지 않아도 Spring Boot 가 기본적으로 바라보고 사용하는 것이다.
static 경로의 리소스는 정적인 자원을, templates 는 view template 인 thymeleaf 를 사용할 경우 view 에 대한 리소스 기본 경로이다.
다음은 resources 폴더를 resource 로 인식할 수 있도록 IDE 설정을 해야 한다.
File > Project Structure 메뉴에 들어간 다음 Modules 의 Sources 탭에서 resources 폴더를 Resources 로 지정해 준다.
여기까지 수정한 다음 지금까지 해왔던 것처럼 WAS (tomcat 서버) 를 실행하면 정상적으로 동작하지 않는 것을 확인할 수 있다.
그건 Spring Boot 애플리케이션이 Spring Web MVC 만 사용헀을 때와 다른 점이 있기 떄문이다.
물론 Spring Boot 애플리케이션도 외부 WAS 를 사용할 수 있지만, 기본 컨셉은 '독립 실행 가능한 웹 애플리케이션'을 만드는 것이기 때문이다.
Spring Boot 는 Java application 을 실행하듯 웹 애플리케이션을 구동할 수 있고, 그렇기 때문에 WAS 도 내부에서 만들어 사용하고 DispatcherServlet 도 내부에서 만들어 내부 WAS 에 직접 등록해서 사용하도록 되어 있다.
일반적으로 알고 있던 WAS 위에 내가 만든 웹 애플리케이션이 배포되어 구동하는 것이 아닌, 자바 애플리케이션 안에 WAS 가 만들어져 사용하는 구조이다.
지금 상태는 Spring Boot 를 사용할 수 있는 준비는 했지만, 사용하고 있지는 않은 상태이다.
'독립 실행 가능한 웹 애플리케이션' 이라는 말을 기억하면서 Spring Boot 를 사용한 웹 애플리케이션을 구동해 보자.
서블릿과 같은 특별한 형태를 제외하고 모든 일반적인 Java 프로그램의 시작점은 main 메소드이다.
어떤 이름을 사용해도 상관 없지만 프로그램의 시작점이 될 App.class 파일을 만들고 main 메소드를 실행하면 Spring Boot 애플리케이션을 구동하도록 작성 하였다.
그리고 현재 사용하지 않는 설정 파일인 MyWebConfig 와 WebApplication 클래스 파일을 삭제 했다.
package me.nimkoes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
이제 App.class 클래스를 Java Application 을 실행하듯 구동 시켜보면 정상적으로 동작 하는 것을 확인할 수 있다.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-07-04 15:59:13.816 INFO 24428 --- [ main] me.nimkoes.App : Starting App using Java 1.8.0_181 on DESKTOP-ISRSTA1 with PID 24428 (D:\_\ijProjects\SpringWebMVCSandbox\target\classes started by resor in D:\_\ijProjects\SpringWebMVCSandbox)
2021-07-04 15:59:13.818 INFO 24428 --- [ main] me.nimkoes.App : No active profile set, falling back to default profiles: default
2021-07-04 15:59:14.618 INFO 24428 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-07-04 15:59:14.625 INFO 24428 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-07-04 15:59:14.625 INFO 24428 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.48]
2021-07-04 15:59:14.747 INFO 24428 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-07-04 15:59:14.747 INFO 24428 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 884 ms
2021-07-04 15:59:14.969 WARN 24428 --- [ main] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2021-07-04 15:59:15.024 INFO 24428 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-07-04 15:59:15.031 INFO 24428 --- [ main] me.nimkoes.App : Started App in 1.534 seconds (JVM running for 2.314)
2021-07-04 15:59:21.257 INFO 24428 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-07-04 15:59:21.257 INFO 24428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-07-04 15:59:21.257 INFO 24428 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
Console 영역을 보면 애플리케이션이 정상적으로 구동 되었고, 8080 포트에 서버가 올라온 것도 확인할 수 있다.
꽤 오래전에 만들어 두었던 MyHelloController 에 Get 요청을 처리하는 /hello 요청을 보내보면 다음과 같이 정상 실행 되는 것을 볼 수 있다.
이것으로 Spring Web MVC 만 사용했던 애플리케이션을 Spring Boot 를 사용하는 애플리케이션으로 전환이 완료 되었다.
실제로 어떤 bean 이 등록 되어 있는지 확인해 보기 위해 DispatcherServlet 클래스의 doService 메소드에 debugger 를 설정하고 확인해 보았다.
기본적으로 등록되는 bean 중 handlerMapping, handlerAdapter 그리고 viewResolver 위주로 확인해 보았다.
꽤 많은 handler 가 기본적으로 등록 되어 있었다.
viewResolver 의 경우 ContentNegotiatingViewResolver 가 등록 되어 있는데, 이 bean 은 직접 처리하지 않고 다른 viewResolver 에게 처리를 위임하는 역할을 한다.
그래서 ContentNegotiatingViewResolver 안에 viewResolver 가 따로 List 형태로 참조 되어 있고, 이 내용은 viewResolvers 에 정의되어 있는 것과 동일하다.
ContentNegotiatingViewResolver 는 http request header 정보를 분석해서 어떤 viewResolver 에게 처리를 위임할지 정하게 된다.
그럼 이렇게 자동으로 등록 된 bean 들에 대한 정보는 어디에서 왔을까.
Spring Web MVC 에서는 DispatcherServlet.properties 파일을 참조해서 가지고 왔는데, Spring Boot 에서는 spring.factories 라는 파일을 참조 한다.
이 파일 내용 중 'org.springframework.boot.autoconfigure.EnableAutoConfiguration' 이 있는데, 여기에 작성되어 있는 자동 설정을 사용하여 baen 이 등록 된다.
수많은 자동 설정 내용 중에 자세히 살펴볼 내용 중 하나는 web 설정과 관련 된 WebMvcAutoConfiguration 이다.
// 생략 ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
// 생략 ...
}
자동 설정 클래스 파일에 작성 한 annotation 중에 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 가 있는데, 이 타입의 클래스 파일이 없는 경우에 Web MVC 자동 설정을 하겠다는 것을 의미한다.
WebMvcConfigurationSupport.class 클래스는 지난번 DelegatingWebMvcConfiguration.class 의 부모 클래스였다.
DelegatingWebMvcConfiguration.class 클래스는 @EnableWebMvc annotation 이 @Import 하는 설정 파일 이었다.
즉, 만약 @EnableWebMvc annotation 을 사용해서 Web MVC 설정을 하게 된다면 Spring Boot 가 기본 제공하는 web 관련 자동 설정을 사용하지 않게 됨을 뜻한다.
지난번 내용을 포함하여 Web 설정 방법에 대해 정리 하면 다음과 같다.
1. Spring Boot 의 MVC 자동 설정 사용 + 기능 확장 (추가설정)
→ @Configuration + Implements WebMvcConfigurer
2. Spring Boot 의 MVC 자동 설정 사용하지 않음 + 기능 확장 (추가설정)
→ @Configuration + Implements WebMvcConfigurer + @EnableWebMvc
그렇다고 이 방법만 있는 것은 절대 아니며, 위와 같이 설정하는게 일반적이고 직접 bean 을 등록 하는 것보다 편리할 뿐이다.
하지만 1, 2번 방법으로 설정을 하는 방법은 어쨌든 java 코드를 작성 해야 하는데, java 코드를 작성하지 않고 설정을 변경할 수 있는 방법이 있다.
Spring Boot 의 Web MVC 자동 설정과 관련된 WebMvcAutoConfiguration.class 클래스 파일을 보면 properties 들을 꽤 많이 참조하고 있는것을 알 수 있다.
이 properties 들은 기본적으로 resources 로 지정된 디렉토리 하위의 application.properties 파일의 내용을 참조 하도록 되어 있다.
그래서 대부분의 설정 값을 수정하는 방법으로 application.properties 파일을 사용하고, 개발, 테스트, 스테이징, 운영 등 환경에 따라 다른 설정을 해야하는 경우 이 파일 기준으로 관리하는게 좋은 방법이라고 생각 한다.
마지막으로 war 배포에 대해 정리해 본다.
다들 jar, war 라고 하는데 정확한 의미에 대해 모르고 쓰는 사람들도 있는것 같다.
jar 는 java archive (자바 아카이브) 의 줄임말로 class 파일들의 묶음이라고 생각해도 큰 무리는 없을것 같다.
단순히 class 파일 묶음 이라고 생각해도 좋지만 조금 더 나아가서 '실행 가능한 jar' 라는 말이 있다.
class 파일을 실행할 수 있다는 것은 어딘가에 main 메소드가 있다는 것을 뜻한다.
하지만 컴퓨터는 어디에 main 메소드가 있는지 알지 못한다.
그래서 실행 가능한 jar 파일은 보통 최상단에 META-INF 디렉토리를 만들고 그 안에 있는 MENIFEST.MF 파일에 Main-Class 를 정의하여 java -jar 명령으로 jar 를 실행할 때 시작 점을 컴퓨터에 알려준다.
위에 만든 Spring Boot 애플리케이션을 maven package 를 실행하여 target 디렉토리에 jar 파일을 만들면 다음과 같은 결과를 볼 수 있다.
다음으로 war 는 web application archive (웹 애플리케이션 아카이브) 의 줄임말로 웹 애플리케이션과 관련된 리소스들을 묶은 파일이다.
이 파일은 WAS (web application server) 에 압축이 풀리면서 배포가 되는 목적을 가지고 있다.
그래서 내용을 보면 WAS 가 이해할 수 있는 구조를 가지도록 파일들이 각자의 위치에 배치되어 있는 것을 볼 수 있다.
war 배포에 대해 정리 해본다고 했는데, Spring Boot 로 만드는 웹 애플리케이션을 war package 형태로 배포하기 위해서는 몇가지 작업을 해주어야 한다.
우선 POM.xml 파일에 packaging 을 war 로 수정한다.
그 다음 SpringBootServletInitializer 클래스를 상속 받는 임의의 클래스를 하나 정의 한다.
지금은 ServletInitializer 라는 이름을 사용 했다.
package me.nimkoes;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(App.class);
}
}
SpringBootServletInitializer.class 클래스에 대한 api 문서를 보면 왜 이 클래스를 상속 받은 클래스가 있어야 하는지 알 수 있다.
An opinionated WebApplicationInitializer to run a SpringApplication from a traditional WAR deployment. Binds Servlet, Filter and ServletContextInitializer beans from the application context to the server. To configure the application either override the configure(SpringApplicationBuilder) method (calling SpringApplicationBuilder.sources(Class...)) or make the initializer itself a @Configuration. If you are using SpringBootServletInitializer in combination with other WebApplicationInitializers you might also want to add an @Ordered annotation to configure a specific startup order. Note that a WebApplicationInitializer is only needed if you are building a war file and deploying it. If you prefer to run an embedded web server then you won't need this at all. |
이 클래스에 대한 마지막 설명을 보면 embedded web server 를 사용하려 한다면 굳이 이 클래스를 사용 할 필요가 없다고 하고 있다.
왜냐하면 war 포맷으로 packaging 한다는 것은 웹 애플리케이션 외부에 있는 WAS 에서 실행한다는 의미가 강하고, jar 포맷으로 packaging 한다는 것은 웹 애플리케이션 내부에 있는 WAS 에서 실행한다는 의미이기 때문이다.
IDE 설정을 local 에 설치한 tomcat 으로 맞춰준 다음 실행하면 이번에는 다음과 같이 외부 WAS 에 war 파일이 배포되어 실행되는 것을 확인할 수 있다.
조금 잘려 나오긴 했지만, 자바 애플리케이션을 구동하여 실행했을 때와 로그를 비교해보면 독립 실행 했을 때와 WAS 위에 배포했을 때의 다른 점을 로그를 통해 알 수 있다.
독립 실행 했을 때는 'Spring' 배너가 로깅 되는 것을 시작으로 WAS 에 대한 port 정보 등이 로깅 되었었는데, WAS 에 war 를 배포했을 때는 WAS 가 구동하는 로그가 먼저 출력 되고 Sping boot 애플리케이션을 구동하고 나서는 WAS 에 대한 port 정보가 출력되지 않고 있는것을 볼 수 있다.
마지막에 다룬 war 배포 관련하여 maven 에 대해 정리할 때 관련있는 내용을 조금 다룬적이 있었다.
링크로 남겨둔 게시물의 아래쪽에 정리 된 'war packaging custom' 내용을 참고해도 좋을것 같다.
'Archive > Spring Web MVC' 카테고리의 다른 글
2.4 핸들러 인터셉터 (HandlerInterceptor interface) (0) | 2021.07.07 |
---|---|
2.3 Spring Web MVC 의 Formatter (Spring Boot 자동 설정) (0) | 2021.07.06 |
2.1 Spring Web MVC 설정 @EnableWebMvc, WebMvcConfigurer (0) | 2021.07.04 |
1.5 Spring MVC 동작 원리 마무리 (0) | 2021.06.28 |
1.4 DispatcherServlet 기본 동작 원리 (0) | 2021.06.27 |