웹서버와 서블릿컨테이너

Shaun·2023년 3월 4일
1

SpringBoot

목록 보기
16/20
post-thumbnail

스프링 부트로 개발을 시작했으며 지금까지 당연하게 그냥 써왔다. 이번 기회에 스프링부트에 조금더 깊게 파악해보자

스프링부트란?

  • 스프링을 편리하게 사용할 수 있도록 지원,
  • 최근에는 기본으로 사용 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • 관례에 의한 간결한 설정

=> 기존 스프링만 사용할때는 버전, 설정 등등 해결해줌

스프링부트 특징

  • 내장톰캣
  • 라이브러리 버전 관리
  • 프로젝트에 필요한 스프링과 외부라이브러리 빈으로 자동 등록
  • 프로덕션 준비: 모니터링을

JAR

  • 위한 메트릭, 상태 확인 기능 제공

  • 예전에는 톰캣을 따로 깔아줘야 했다. 그리고 톰캣(WAS)에서 동작하도록 서블릿 스펙에 맞춰 코드를 짜고 WAR형식으로 빌드해서 WAS에 배포
    -> 서블릿 만들고 WAR로 빌드해 WAS에 배포해야함

  • 지금은 톰캣이 라이브러리로 내장되 있음. JAR로 빌드한다음 실행 하기만 하면 WAS도 자동실행

JAR vs WAR

JAR

  • JVM 위에서 직접 실행되거나 또는 다른 곳에서 사용하는 라이브러리로 제공
  • 클래스와 관련 리소스를 압축한 단순한 파일
  • 파일을 직접 실행할 수도 있고, 다른 곳에서 라이브러리로 사용할 수도 있다.

WAR

  • WAR 파일은 웹 WAS 에 배포할 때 사용하는 파일
  • 웹 애플리케이션 서버 위에서 실행되고, HTML 같은 정적 리소스와 클래스 파일을 모두 함께 포함하기 때문에 JAR와 비교해서 구조가 더 복잡하다
  • WAR 구조를 지켜야 한다.

WAS

WAS 사용시 필요한것들

  • 필터, 서블릿 등록
  • 스프링 사용시 스프링 컨테이너 등록
  • 서블릿 컨테이너와 스프링 컨테이너를 연결짓는 디스패처 서블릿

서블릿 컨테이너 초기화

  • 서블릿은 ServletContainerInitializer 라는 인터페이스 제공, 사용시 onStartup() 을 호출해 서블릿 컨테이너 초기화 진행

  • Set<Class<?>> c : 조금더 유연한 초기화 기능 제공

  • ServletContext ctx : 서블릿 컨테이너 자체의 기능 제공, 이것을 통해 서블릿 등록 가능

  • 초기화시 WAS에게 알려줘야함
    ->resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 라는 파일에 ServletContainerInitializer를 구현한 클래스 위치를 적어줘야함

애플리케이션 초기화

= 서블릿 컨테이너를 통한 서블릿 등록

  • 서블릿 등록 방법
  1. 프로그래밍 방법
  2. @WebServlet 방법

프로그래밍 방법

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!");
      }
  • 간단한 hello서블릿
public interface AppInit {
      void onStartup(ServletContext servletContext);
}
  • 서블릿 컨테이너를 통해 서블릿 등록시 필요한 인터페이스
/**
   * 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");
} }
  • 서블릿 컨테이너를 통한 서블릿 등록

@WebServlet

@WebServlet(urlPatterns = "/test")
  public class TestServlet extends HttpServlet {}
  • 이 방법이 더 간단해보이지만 상대적으로 프로그래밍 방법이 좀더 유연하게 커스터마이징 가능함

서블릿 등록(애플리케이션 초기화) + 서블릿 컨테이너 초기화

   @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 {
//new AppInitV1Servlet()과 같은 코드
                  AppInit appInit = (AppInit)
  appInitClass.getDeclaredConstructor().newInstance();
                  appInit.onStartup(ctx);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
} }
}
  • @HandlesType애플리케이션 초기화 인터페이스를 정한다. 이 인터페이스 구현체들이 Set<Class<?>> c 로 들어온다

  • 리플렉션을 사용해서 객체 생성

  • 애플리케이션 초기화를 하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 보냄

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

    ->이것 또한 서블릿 WAS 에게 알려줘야함

결론

= 서블릿을 직접 서블릿 컨테이너에 등록하고 초기화시 사용 과정

-> 서블릿 컨테이너 초기화시 인터페이스도 구현해야하고 서블릿 컨테이너에게 알려줘야 하지만 애플리케이션 초기화 같은 경우 특정 인터페이스만 구현 하면 된다.

스프링 컨테이너 등록

필요사항

  • 스프링 컨테이너 만들기
  • 스프링 컨트룰러 스프링 컨테이너 으로 등록
  • 디스패처 서블릿을 서블릿 컨테이너 등록
@Configuration
  public class HelloConfig {
      @Bean
      public HelloController helloController() {
          return new HelloController();
      }
   }
  • HelloController을 빈으로 직접 등록
/**
   * http://localhost:8080/spring/hello-spring
   */
  public class AppInitV2Spring implements AppInit {
      @Override
      public void onStartup(ServletContext servletContext) {
          System.out.println("AppInitV2Spring.onStartup");
          
//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext = newAnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class); 

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

//디스패처 서블릿을 서블릿 컨테이너에 등록 (이름 주의! dispatcherV2) ServletRegistration.Dynamic servlet =
servletContext.addServlet("dispatcherV2", dispatcher);

// /spring/* 요청이 디스패처 서블릿을 통하도록 설정
servlet.addMapping("/spring/*");
      }
}
  • 서블릿 컨테이너 > 디스패쳐 서블릿> 스프링 컨테이너 > 빈으로 등록한 컨트룰러 Config

  • 마지막은 해당경로로 오는 요청은 DispatcherServlet을 통하도록 함

  • 이제 요청이 오면 DispatcherServlet에서 요청에 해당하는 스프링 컨트룰러 실행

WebApplicationInitializer

public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}
  • 서블릿 컨테이너 초기화, 스프링 컨테이너 초기화 및 연결 작업은 위에서 해왔던 작업으로 알수 있듯이 매우 복잡하다

  • 스프링 MVC에서는 이미 다 처리해 놨다.

public class AppInitV3SpringMvc implements WebApplicationInitializer {
      @Override
      public void onStartup(ServletContext servletContext) throws
  ServletException {
          System.out.println("AppInitV3SpringMvc.onStartup");
          
//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class); 

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

//디스패처 서블릿을 서블릿 컨테이너에 등록 (이름 주의! dispatcherV3) 
ServletRegistration.Dynamic servlet =servletContext.addServlet("dispatcherV3", dispatcher);

//모든 요청이 디스패처 서블릿을 통하도록 설정
servlet.addMapping("/");
      }
}
  • 사실상 이전 코드와 매우 흡사함
profile
호주쉐프에서 개발자까지..

0개의 댓글