본문 바로가기
Spring Study/SpringBoot

[SpringBoot] 웹 서버와 서블릿 컨테이너

by 정재인 2023. 11. 21.

JAR, WAR

JAR란?

자바는 여러 클래스와 리소스를 묶어서 JAR(Java Archive)라고 하는 압축 파일을 만들 수 있다.

이 파일은 JVM 위에서 직접 실행되거나 다른 곳에서 사용하는 라이브러리로 제공된다.

직접 실행하는 경우 main() 메서드가 필요하고, MANIFEST.MF 파일에 실행할 메인 메서드가 있는 클래스를 지정해두어야 한다.

 

즉, Jar는 클래스와 관련 리소스를 압축한 단순한 파일이다. 필요한 경우 이 파일을 직접 실행할 수도 있고, 다른 곳에서 라이브러리로 사용할 수도 있다.

 

WAR란?

WAR(Web Application Archive)라는 이름에서 알 수 있듯이 WAR 파일은 웹 애플리케이션 서버(WAS)에 배포할 때 사용하는 파일이다. JAR 파일이 JVM 위에서 실행된다면, WAR는 웹 애플리케이션 서버 위에서 실행된다.

WAS 위에서 실행되고, HTML과 같은 정적 리소스와 클래스 파일을 모두 함께 포함하기 때문에 JAR과 비교해서 구조가 더 복잡하다. 또한, WAR 구조를 지켜야 한다.

 

WAR 구조

· WEB-INF: 폴더 하위는 자바 클래스와 라이브러리, 설정 정보가 들어감

  · classes: 실행 클래스 모음

  · lib: 라이브러리 모음

  · web.xml: 웹 서버 배치 설정 파일(생략 가능)

· index.html: 정적 리소스

 


서블릿 컨테이너 초기화(1)

WAS를 실행하려면 초기화 작업들이 필요하다. 1. 서비스에 필요한 필터와 서블릿을 등록하고, 2. 스프링을 사용한다면 스프링 컨테이너를 만들고, 3. 서블릿과 스프링을 연결하는 디스패처 서블릿을 등록해야 한다. WAS가 제공하는 초기화 기능을 사용하면 WAS 실행 시점에 이러한 초기화 과정을 진행할 수 있다.

 

서블릿 컨테이너 초기화 개발

· 서블릿은 ServletContainerInitializer라는 초기화 인터페이스를 제공한다. 이는 컨테이너를 초기화 하는 기능을 제공한다.

· 서블릿 컨테이너는 실행 시점에 초기화 메서드인 onStartup()을 호출한다. 여기서 필요한 기능들을 초기화하거나 등록할 수 있다.

 

ServletContainerInitializer

public interface ServletContainerInitializer{
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

 

· Set<Class<?>> c: 유연한 초기화 기능을 제공한다. @HandlesTypes 애노테이션과 함께 사용

· Servletcontext ctx: 서블릿 컨테이너 자체의 기능을 제공, 이 객체를 통해 필터나 서블릿을 등록

 

ServletContainerInitializer를 작성한 후, WAS에게 실행할 초기화 클래스를 알려주어야 한다.

다음 경로에 파일을 생성한다.

 

resources/META-INF/services/jakarta.servlet.ServletContainerInitializer

hello.container.MyContainerInitV1

 

 


 

서블릿 컨테이너 초기화(2)

HelloServlet이라는 서블릿을 서블릿 컨테이너 초기화 시점에 프로그래밍 방식으로 직접 등록해준다.

1. @WebServlet 애노테이션

2. 프로그래밍 방식

 

HelloServlet

package hello.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          System.out.println("HelloServlet.service");
          resp.getWriter().println("hello servlet!");
      }
}

 

 

애플리케이션 초기화

서블릿 컨테이너는 유연한 초기화 기능을 지원한다.

 

AppInit

package hello.container;

import jakarta.servlet.ServletContext;

public interface Appinit{
    void onStartup(ServletContext servletContext);
}

 

애플리케이션 초기화를 진행하려면 먼저 인터페이스를 만들어야 한다. 내용과 형식은 상관없고, 인터페이스는 꼭 필요하다.

 

AppInitV1Servlet

package hello.container;

import hello.servlet.HelloServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
/**
  * http://localhost:8080/hello-servlet
  */
public class AppInitV1Servlet implements AppInit {

    @Override
    public void onStartup(ServletContext servletContext) {
        System.out.println("AppInitV1Servlet.onStartup");

        //순수 서블릿 코드 등록 ServletRegistration.Dynamic helloServlet =
        servletContext.addServlet("helloServlet", new HelloServlet());
        helloServlet.addMapping("/hello-servlet");
    } 
}

 

프로그래밍 방식으로 HelloServlet 서블릿을 서블릿 컨테이너에 직접 등록한다.

 

MyContainerInitV2

package hello.container;

import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;
import java.util.Set;

@HandlesTypes(AppInit.class)
public class MyContainerInitV2 implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        System.out.println("MyContainerInitV2.onStartup");
        System.out.println("MyContainerInitV2 c = " + c);
        System.out.println("MyContainerInitV2 container = " + ctx);
        
        for (Class<?> appInitClass : c) {
              try {
                  //newAppInitV1Servlet()과 같은 코드
                  AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance();
                  appInit.onStartup(ctx);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
        } 
    }
}

 

애플리케이션 초기화 과정

1. @HandlesTypes 애노테이션에 애플리케이션 초기화 인터페이스를 지정한다.

2. 서블릿 컨테이너 초기화(ServletContainerInitializer)는 파라미터로 넘어오는 Set<Class<?>> c에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.

3. appInit.onStartup(ctx)는 애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 전달한다.

 


스프링 컨테이너 등록

1. 스프링 컨테이너 만들기

2. 스프링 MVC 컨트롤러를 스프링 컨테이너에 빈으로 등록하기

3. 스프링 MVC를 사용하는데 필요한 디스패처 서블릿을 서블릿 컨테이너에 등록하기

 

HelloController

package hello.spring;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
    
@RestController
public class HelloController {
      
    @GetMapping("/hello-spring")
    public String hello() {
        System.out.println("HelloController.hello");
        return "hello spring!";
      }
}

 

 

HelloConfig

package hello.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
  
@Configuration
public class HelloConfig {
    
    @Bean
    public HelloController helloController() {
        return new HelloController();
      }
}

 

 

AppInitV2Spring

package hello.container;

import hello.spring.HelloConfig;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;

/**
  * http://localhost:8080/spring/hello-spring
  */
public class AppInitV2Spring implements AppInit {
    @Override
    public void onStartup(ServletContext servletContext) {
        System.out.println("AppInitV2Spring.onStartup");

    //스프링 컨테이너 생성
    AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
    appContext.register(HelloConfig.class); //스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
    DispatcherServlet dispatcher = new DispatcherServlet(appContext);

    //디스패처 서블릿을 서블릿 컨테이너에 등록 (이름 주의! dispatcherV2)
    ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcherV2", dispatcher); 
    
    // /spring/* 요청이 디스패처 서블릿을 통하도록 설정
    servlet.addMapping("/spring/*");
    }
}

 

AnnotationConfigWebApplicationContext가 스프링 컨테이너이다.

 

스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결

new DispatcherServlet(appContext)

· 스프링 MVC가 제공하는 디스패처 서블릿을 생성하고, 생성자에 앞서 만든 스프링 컨테이너를 전달한면 디스패처 서블릿에 스프링 컨테이너가 연결된다.

· 디스패처 서블릿에 HTTP 요청이 오면 디스패처 서블릿은 해당 스프링 컨테이너에 들어있는 컨트롤러 빈들을 호출한다.

 

댓글