티스토리 뷰

Backend

[Spring] Spring MVC - Servlet

HUN 2021. 9. 7. 21:19

오늘은 서블릿의 개념에 대해서 알아본 후, 실제로 자바와 스프링에 구현된 코드를 보며 컴포넌트들의 역할을 이해해본다.

Servlet


서블릿은 클라이언트의 요청을 처리하고 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바의 프로그래밍 기술이다. 컨테이너가 클라이언트의 동적인 요청을 처리할 수 있도록 도와주는 컴포넌트인 것이다.

일반적으로 웹 서버는 클라이언트의 정적인 요청에만 응답하고 동적인 요청에 대한 처리는 웹 컨테이너에게 위임한다. 이 때 웹 컨테이너는 클라이언트의 HTTP Request를 해석해서 HttpServletRequest 객체를 생성하고, 응답을 전달해줄 수 있는 HttpServletResponse 객체를 생성한다.

그리고 웹 컨테이너는 요청 URL에 매핑되는 적절한 서블릿을 찾고, 쓰레드 풀에서 쓰레드를 가져와 서블릿의 servie() 메소드를 호출한다. 서블릿은 컨테이너에게 제공 받은 HttpServletRequest 객체를 통해 데이터를 사용하고 요청 결과를 HttpServletResponse 객체에 담아 전달한다.

그리고 컨테이너는 클라이언트에게 HTTP Response를 전달하고 HttpServletRequest 객체와 HttpServletResponse 객체를 파괴한다.

웹 컨테이너는 웹 서버와 통신한다. 웹 서버는 nginx나 Apache 등 다양하게 존재한다. 만약 웹 서버마다 데이터 전송 방법이 다르면 통신에 많은 어려움이 있을 것이다. 따라서 웹 서버와 컨테이너간 통신 규칙이 존재하는데 Common Gateway Interface(CGI)가 그것이다.

Servlet은 자바로 구현된 CGI이다

CGI는 웹 서버와 컨테이너간 데이터 교환 방식을 의미하는 것이지 특별한 도구나 라이브러리를 의미하는 것은 아니다. CGI는 어떤 프로그래밍 언어로도 구현이 가능하며 서블릿은 자바로 구현한 CGI인 것이다. 이제 CGI를 통한 데이터 교환 방식을 포함한 통신 과정을 살펴본다.

웹 서버가 동적인 요청을 전달 받으면 CGI 프로그램(컨테이너)에 요청을 전달하고 결과를 전달받기 위한 파이프라인을 연결한다. CGI 프로그램은 입력에 대한 서비스를 수행하고, 결과를 클라이언트에게 전달하기 위해 결과 페이지에 해당하는 MIME 타입의 컨텐츠 데이터를 웹 서버와 연결된 파이프라인에 출력하여 서버에 전달한다. 서버는 파이프라인을 통해 CGI 프로그램에서 출력한 결과 페이지의 데이터를 읽어, HTTP 응답 헤더를 생성하여 데이터를 함께 반환해준다.

Servlet Container


서블릿 컨테이너는 쉽게 말해 서블릿을 관리해주는 컨테이너이다. 서블릿의 생명주기를 관리하고 요청에 따른 스레드를 생성한다.

Servlet Container의 기능

통신 지원

서블릿과 웹 서버가 통신할 수 있는 손쉬운 방법을 제공한다. 통신을 위해서는 소켓을 만들고 특정 포트를 리스닝하고 연결 요청이 오면 스트림을 생성해서 요청을 받는다. 이러한 번거로운 과정을 서블릿 컨테이너에서 대신해준다. 따라서 개발자는 서블릿을 사용한 비즈니스 로직 처리에만 집중할 수 있게된다.

생명 주기 관리

서블릿 컨테이너가 가동되는 순간 서블릿 클래스를 로딩해서 인스턴스화하고, 초기화 메서드를 호출하고, 요청이 들어오면 적절한 서블릿을 찾아 요청을 처리한다

멀티 스레딩 관리

서블릿 컨테이너는 해당 서블릿의 오쳥이 들아어면 스레드를 생성해서 작업을 수행한다. 하지만 요청마다 쓰레드를 생성하고, 과도한 요청이 몰리면 서버에 부하가 커지기 때문에 쓰레드 풀을 주로 사용한다. 쓰레드 풀을 통해 쓰레드의 생성을 제한하고 새로운 쓰레드를 생성하는 것이 아니라 사용하고 반납하여 쓰레드 생성에 대한 비용을 줄인다.

선언적 보안관리

서블릿 컨테이너는 선언적으로 보안을 관리해준다. 따라서 서블릿 내부에서는 보안 관련 메소드를 구현할 필요가 없다.

Servlet Life Cycle

  1. 클라이언트의 요청이 들어오면 서블릿이 메모리에 있는지 확인한다.
  2. 없으면 init() 메소드를 호출해 메모리에 적재한다.
  3. service() 메소드를 호출해 요청을 처리한다
  4. 컨테이너가 서블릿에 종료 요청을 하면 destroy() 메소드가 호출된다. 종료시에 필요한 작업은 destroy() 메소드를 오버라이딩하여 구현한다.

ServletConfig와 ServletContext


Servlet Config와 Servlet Contexts는 서블릿이 생성되는 시점에 서블릿 컨테이너에 의해서 생성되는 객체이다. 두 가지 모두 서블릿에 웹 애플리케이션에 대한 정보를 제공한다는 공통점이 있지만, ServletContext를 통해 접근하는 정보는 모든 서블릿에 의해 공유되지만 ServletConfig는 특정한 서블릿에만 정보를 제공한다.

자바에서 서블릿을 사용하기 위해서는 HttpServlet을 상속해야한다. HttpServletServletConfig를 구현한 GenericServlet을 상속하고있다. 따라서 특정한 서블릿에게만 정보를 제공하기 위해서 ServletConfig를 사용한다는 것을 알 수 있다.

ServletContext는 모든 서블릿이 공유하는 웹 애플리케이션의 정보를 제공해주기 위한 객체이다. ServletContext를 통해 웹 애플리케이션의 정보를 설정하기 위해서는 WebApplicationInitializer 인터페이스를 구현해야한다. onStartup() 메소드에는 ServletContext가 인자로 전달되는데 이를 통해 전체 서블릿이 공유하는 웹 애플리케이션의 메타 정보를 설정할 수 있다.

ServletContextListner와 ContextLoaderListner


Listener란 어떠한 Event가 발생했을 때 이에 대한 적절한 조치를 수행하는 주체를 의미한다. ServletContextListner는 웹 애플리케이션의 servlet context에 변경이 감지 되었을 때 이에 대한 특별한 조치를 취하기 위해 구현되는 인터페이스이다.

ServletContextListner에는 contextInitialized()contextDestroyed() 두 개의 디폴트 메서드가 정의되어있다. contextInitialized()는 웹 애플리케이션의 초기화가 시작되었을 때 호출되는 메서드이다. contextDestroyed()는 servlet context가 종료되는 시점에 호출되는 메서드이다.

ContextLoaderListner는 스프링에서 제공하는 ServletContextListner의 구현체이다. 따라서 웹 애플리케이션이 초기화되고 servlet context가 종료되는 시점에 스프링에서 제공하는 특정한 조치를 취할 수 있을 것이라 예상 할 수 있다.

ContextLoaderListner 를 생성하기 위해서는 기본 생성자를 호출하는 방법이 있고 WebApplicationContext를 인자로 전달하여 생성자를 호출하는 방법이있다. 기본 생성자 호출은 web.xml을 사용하는 방식이기 때문에 오늘은 넘어가고 WebApplicationContext를 파라미터로 받는 생성자를 살펴본다. WebApplicationContext는 웹 애플리케이션의 설정 정보를 제공하는 인터페이스이다. 자세한 것은 차후 다루겠다.

이 생성자 호출 방법은 Servlet 3.0 이상에서 부터 Programatically하게 자바 코드를 통해 리스너를 등록하는 방법이다. WebApplicationContext를 인자로 받는 이유는 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 라는 이름으로 서블릿 컨텍스트에 등록하고 서블릿 컨텍스트가 종료될 때 WebApplicationContext도 안전하게 종료하기 위함이다.

ContextLoaderListner에 구현된 두 메소드는 상속하고 있는 ContextLoaderinitWebApplicationContext()closeWebApplicationContext()를 호출하고 있다. 메소드의 이름을 보면 웹 애플리케이션 컨텍스트를 초기화하고 종료하는 메서드임을 짐작할 수 있다.

initWebApplicationContext() 메소드 내부를 보면 ServletContextsetAttribute() 메소드를 호출하여 웹 애플리케이션 컨텍스트를 등록하고 있다. 따라서 모든 서블릿은 등록된 컨텍스트의 정보를 모두 공유할 수 있다.

그리고 closeWebApplicationContext() 메소드 내부를 보면 ServletContextremoveAttribute()를 호출 하여 Servlet Context에서 웹 애플리케이션 컨텍스트를 제거하는 것을 알 수 있다.

코드 이해하기


public class KmfWebApplicationInitializer implements WebApplicationInitializer { // 1

    @EnableWebMvc
    @Configuration
    @ComponentScan(basePackages ="com.kmf.hun",
            includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
            useDefaultFilters = false) // 2
    static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { 

        ApplicationContext applicationContext;

        ...
    }


    @ComponentScan(basePackages = "com.kmf.hun",
            excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)) // 3
    @Configuration
    @EnableTransactionManagement
    static class RootConfig {
        ...
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException { // 4

        AnnotationConfigWebApplicationContext rootApplicationContext = new AnnotationConfigWebApplicationContext();
        rootApplicationContext.register(RootConfig.class); // 5
        ContextLoaderListener loaderListener = new ContextLoaderListener(rootApplicationContext); // 6
        servletContext.addListener(loaderListener); // 7

        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(ServletConfig.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);

        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("kmf", dispatcherServlet);
        servletRegistration.addMapping("/");
        servletRegistration.setLoadOnStartup(0);
    }
}

DispatcherServlet에 대해서 알아 보진 않았지만 전체적인 구조를 설명하기 위해 작성했다.

1| WebApplicationInitializer 를 구현한 클래스이다. ServletContext를 통해 웹 애플리케이션의 메타 정보를 설정하는 클래스라는 것을 알 수 있다.

2| Controller 컴포넌트를 스캔한다. ServletConfig로 설정한 메타 정보를 가진 웹 애플리케이션 컨텍스트에서만 사용할 빈을 등록한다.

3| Controller를 제외한 컴포넌트를 스캔한다. 모든 웹 애플리케이션 컨텍스트에서 공유할 빈을 등록한다.

4| 웹 애플리케이션이 시작할 때 자동적으로 호출되어 메타 정보를 설정한다.

5| Root ApplicationContext에 RootConfig에 설정된 메타 정보를 등록한다.

6| Root ApplicationContext를 인자로 전달하고 ContextLoaderListener를 생성한다. 따라서 웹 애플리케이션이 시작하고 종료될 때 Root ApplicationContext가 ServletContext에 등록되고 제거될 것이다.

7| ServletContext의 리스너에 ContextLoaderListener를 등록한다.


참고

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함