[Spring Boot] 스프링과 웹 서버
부트를 사용하기 전 웹 애플리케이션을 배포하려면 WAS를 따로 설치해 주고 해당 WAS에서 잘 동작하도록 서블릿 스펙에 맞춰 코드를 작성한 후 WAR로 패키징해서 WAS의 배포 디렉토리에 WAR파일을 전달해 줘야 했다.
부트는 이런 작업을 자동화한다.
Tomcat (WAS) 을 기본적으로 포함하고 있어 작성한 자바 코드를 빌드한 jar파일을 실행해주기만 하면 애플리케이션이 실행된다. (jar는 메인 메서드가 있다)
자바는 여러 클래스와 리소스들을 묶어서 JAR (Java Archieve) 압축 파일을 만들 수 있다.
JAR 로 패키징 된 파일은 JVM에서 직접 실행되거나 다른 애플리케이션의 라이브러리로 사용될 수 있다. (직접 실행되는 경우 메인 메서드가 있어야 하고, MANIFEST 파일에 실행할 메인 메서드명이 지정되어야 한다)
WAR (Web Application Archieve) 는 이름 그대로 WAS에 배포할 때 사용되는 파일이다. (JVM 위에 WAS, WAS 위에 WAR)
정적 리소스와 클래스 파일을 모두 포함해 구조가 좀 더 복잡하다.
WEB-INF:
classes # 실행 클래스
lib # 라이브러리
web.xml # 웹 서버 배치. (설정 파일)
index.html # 정적 리소스
WEB-INF를 제외한 영역에는 정적 리소스가 포함된다. (html, css, js)
작성한 애플리케이션을 WAR로 패키징 한 후에 ROOT.war로 파일명을 변경한 후 Tomcat의 /webapps 디렉토리에 넣어 주면 애플리케이션이 배포된다. (자동으로 압축이 풀리고, 배포 디렉토리는 server.xml에서 변경할 수 있다)
WAS가 실행되면 애플리케이션 컨텍스트를 등록하고 디스패처 서블릿을 등록하는 등 애플리케이션을 시작하기 위한 초기화 작업을 수행한다. (스프링에서는 web.xml에서 관련 설정을 진행한다)
web.xml을 사용하지 않고도 서블릿 스펙에서 자바 코드로도 초기화를 진행할 수 있다.
public class MyContainerInitV1 implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println("initialize...");
System.out.println("c = " + c);
System.out.println("ctx = " + ctx);
}
}
서블릿 컨테이너는 실행 시점에 ServletContainerInitializer 인터페이스의 초기화 메서드인 onStartUp() 메서드를 호출한다.
해당 인터페이스는 초기화 인터페이스로 onStartUp 메서드 내부에 초기화 내용을 작성하면 된다.
작성한 서블릿 컨테이너는 resources/META-INF/services 디렉토리에 클래스명을 입력해 줘서 WAS에게 해당 클래스가 초기화 클래스임을 알려줘야 한다.
이렇게 onStartUp 메서드 내부에 초기화 내용을 작성하는 방법 외에도 서블릿 초기화에는 두 가지 방법이 더 있다. (애플리케이션 초기화라고 부른다)
1. 프로그래밍 방식
public interface AppInit {
void onStartup(ServletContext servletContext); // 서블릿 컨테이너
}
public class AppInitV1Servlet implements AppInit {
@Override
public void onStartup(ServletContext servletContext) {
System.out.println("v1servlet...");
ServletRegistration.Dynamic helloServlet = servletContext.addServlet("helloServlet", new HelloServlet());
helloServlet.addMapping("/hello-servlet");
}
}
@HandlesTypes(AppInit.class)
public class MyContainerInitV2 implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println("v2 init...");
for (Class<?> appInitClass : c) {
try {
AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance();
appInit.onStartup(ctx);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
인터페이스를 하나 두고 onStartup 메서드를 오버라이드해서 서블릿을 초기화하는 방법이다.
@HandlerTypes 애너테이션으로 도입한 인터페이스인 AppInit을 애플리케이션 초기화 인터페이스를 지정해준다.
이제 변수 c에는 AppInit 구현체의 정보가 전달된다. c를 사용해 초기화를 진행한다.
변화에 유연하게 변경할 수 있다.
2. 애너테이션 방식
@WebServlet(urlPatterns = "/test")
public class TestServlet extends HttpServlet {
...
}
애너테이션 하나로 서블릿을 편하게 등록할 수 있지만 프로그래밍 방식처럼 유연하게 변경하기는 힘들다.
WAS가 실행되면 ServletContainerInitializer 인터페이스의 구현체가 실행되고, @HandlesTypes 애너테이션을 탐색해 구현체에게 관련 정보를 넘겨준다.
이후 위에서 언급한대로 넘겨받은 정보를 사용해 애플리케이션을 초기화한다.
서블릿 컨테이너 초기화를 진행하려면 ServletContainerInitializer 인터페이스를 구현한 코드를 만들고 해당 클래스를 WAS에게 알려주는 작업까지 수행해야 하지만, 애플리케이션 초기화는 특정 인터페이스만 구현하면 돼 설정이 편하고 서블릿에 의존을 줄일 수 있다.
필요한 기능들은 애플리케이션 초기화에 포함시키면 된다.
스프링 MVC는 디스패처 서블릿이 클라이언트의 요청을 모두 받고, 적절한 컨트롤러를 연결시켜주는 방식으로 동작한다.
컨트롤러를 만들고 빈으로 등록한 후 디스패처 서블릿이 컨트롤러를 호출하도록 설계해보자.
@Configuration
public class HelloConfig {
@Bean
public HelloController helloController() {
return new HelloController();
}
}
@RestController
public class HelloController {
@GetMapping("/a")
public String hello() {
System.out.println("hello controller...");
return "hellocontroller";
}
}
컨트롤러를 만들고 빈으로 등록해줬다.
@RestController 내부에 @Component 애너테이션이 있어서 빈으로 등록될 것 같지만... 컴포넌트 스캔을 사용하지 않아으니 @Configuration @Bean 애너테이션을 통해 수동으로 등록해 줘야 한다.
public class AppInitV2Spring implements AppInit{
@Override
public void onStartup(ServletContext servletContext) {
System.out.println("AppInitV2Spring....");
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
servletContext.addServlet("dispatcherServletV2", dispatcherServlet)
.addMapping("/spring/*");
}
}
스프링 컨테이너와 디스패처 서블릿을 만들고 등록해준다.
디스패처 서블릿에 해당하는 요청이 들어오면 해당 스프링 컨테이너에 들어있는 컨트롤러 빈들을 찾아서 적절한 컨트롤러에 연결해준다.
Tomcat (WAS) 은 서블릿 컨테이너의 역할을 수행하고 스프링 컨테이너와 함께 작동한다고 생각하면 된다. (서블릿 컨테이너와 스프링 컨테이너는 함께 작동하지만 서로는 독립적인 컨테이너이다)
public class AppInitV3SpringMvc implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("V3 init...");
}
}
스프링 MVC는 지금까지의 작업을 추상화한다.
단순하게 WebApplicationInitializer 인터페이스를 구현하는것으로 지금까지의 귀찮은 과정을 생략할 수 있다.
onStartup 메서드를 오버라이드하고 초기화 기능들을 해당 메서드 내부에 작성하면 된다.
spring-web 라이브러리 내부에서 서블릿 컨테이너가 요구하는 작업들을 처리해 주기 때문에 이런 추상화가 가능하다.
코드를 적당히 작성하고 라이브러리 코드를 까 보면 추상화 작업을 확인할 수 있다.
스프링 부트가 도입되기 전에는 이렇게 Tomcat같은 서블릿 컨테이너를 따로 설치하고 Tomcat 위에 애플리케이션을 올려서 배포해야 했지만, 부트가 내장 Tomcat을 지원하면서 해당 부분이 많이 간소화됐다.
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 외부 설정 (0) | 2023.05.28 |
---|---|
[Spring Boot] 자동 구성 (0) | 2023.05.26 |
[Spring Boot] 라이브러리의 선택과 버전 관리 (0) | 2023.05.23 |
[Spring Boot] 부트와 내장 WAS (0) | 2023.05.22 |
[Spring Boot] 스프링 부트의 도입 (0) | 2023.05.14 |
댓글
이 글 공유하기
다른 글
-
[Spring Boot] 자동 구성
[Spring Boot] 자동 구성
2023.05.26 -
[Spring Boot] 라이브러리의 선택과 버전 관리
[Spring Boot] 라이브러리의 선택과 버전 관리
2023.05.23 -
[Spring Boot] 부트와 내장 WAS
[Spring Boot] 부트와 내장 WAS
2023.05.22 -
[Spring Boot] 스프링 부트의 도입
[Spring Boot] 스프링 부트의 도입
2023.05.14