Development/Spring

Dispatcher Servlet

반응형

DispatcherServlet 구조

DispatcherServlet Javadoc 원문 설명

DispatcherServlet은 Spring의 모든 HTTP Call을 클라이언트(browser, http client …) 받아서 View 혹은 데이터를 내려주는 역할을 한다. 그리고 Handler를 등록해서 편리하게 url 매핑을 하거나, Exception Handling을 하는 것을 지원한다.

Spring에서 제공하는 servlet은 유연한 interface를 가지고 있고, 적절한 adapter class를 사용해서 이를 handling할 수 있으며, MVC Framework의 기본적인 기능을 제공한다.

MVC Pattern을 먼저 알아봐야 할 것 같은데,

mvc pattern

이 아키텍쳐가 기본적인 MVC Pattern이며, 아래의 구성요소를 가지고 있다

  • Controller : 유저가 사용하는 url로 구성이 되며, business logic을 처리해서 model에 값을 전달한다

  • Model : Controller에서 처리된 데이터를 View로 내려주기 위해서 사용된다

  • View : User에게 실제적으로 보여질 데이터를 처리한다

매번 이런 패턴을 익히 들었을 법 한데, 실제로 Spring에서 이러한 패턴이 어떻게 활용되는지를 알아보자

  1. Controller
@Controller
public class ExampleController {

    @GetMapping("/foo")
    @ResponseBody
    public ResponseEntity foo(Bar bar){
        // write logic of processing `foo`
        return ResponseEntity.ok().body(new Bar());
    }    
}

Controller는 간단하게 위와 같이 나타낼 수 있다. 유저가 요청한 url에 대한 mapping(/foo)에 대해서 요청을 처리 받고, 해당 logic을 처리한 다음에 값을 return해주는 역할을 담당한다.

위의 경우는 @ResponseBody Annotation이 달려있기 때문에 View를 return하는게 아니라, Bar 라는 객체를 JSON으로 wrapping 해서 내려주는 케이스인데, Spring에서 View를 내려주려면 아래와 같이 해야 한다

@Controller
public class ViewController {
    @GetMapping("/foo/view")
    public String foo() {
        return "foo";
    }
}

위와 같이 코드를 작성하게 되면 Spring 내부에서 ViewResolver가 동작해서 foo 라는 view를 찾아서 View를 유저에게 return을 해주게 된다. 공식적으로 지원하게 되는 View template는 JSP, Thymeleaf, FreeMarker, Groovy, Jade4j, Velocity, JMustache, Pebble 이 있다.

DispatcherServlet에서는 @Controller 어노테이션이 달린 클래스나, @RequestMapping 어노테이션이 달린 메서드가 기본적으로 auto-scan 되고 해당 url 혹은 beanName을 기반으로 등록이 된다. 이는 ApplicationContext 에 추가가 되며(@Bean 으로 등록이 된다는 말), RequestMappingHandlerMapping이 초기화 될 때 ApplicationContext에 추가된 @Controller 에 해당하는 handler들을 모두 불러와서 해당 url에 맞게 DispatcherServlet에 등록이 되게 된다.

Exception resolution strategy가 존재하는데 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver. 등을 사용해서 기본적인 Exception Handling을 한다. Whitelabel Error 라고 하면서 나오는 에러는 이 Resolver들에 의해서 Exception이 Catch되어 나타나게 되는 페이지이다.

그 밖에도 View를 찾기 위한 ViewResolver , Multipart를 Handling하기 위한 MultipartResolver , 접근하는 사용자의 locale을 알기 위한 LocaleResolver. 그리고 페이지의 테마를 위한(?) ThemeResolver 정도가 기본으로 제공되는 Resolver 라고 보면 되겠다.

이런식으로 기본적인 MVC패턴을 이용해서 웹서비스를 하기 위한 기본적인 핸들러들이 등록이 되어 있으며, 필요에 따라 Customizing 하거나 Interface를 이용해서 새로 만들어서 추가를 하여 사용하기도 한다.

그럼 실제로 Request가 들어왔을 때 어떤 call stack으로 호출이 되는지 한번 알아보자

임의로 Controller에서 RuntimeException을 throw해봤다. ("/" url 을 GET으로 호출)

         ┌───────────────────────────┐
         │  java.lang.Thread.run()   │
         └───────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│java.util.concurrent.ThreadPoolExecutor.run()│
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│  undertow.server.HttpServerExchange.run()   │
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│      undertow.servlet.handleRequest()       │
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│  AbstractSecurityContextAssociationHandler  │ // SpringSecurity로 인해 호출됨
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│       AuthenticationMechanismsHandler       │ // SpringSecurity 인증flow 
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│          ServletDispatchingHandler          │ // 드디어 이때 call
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│               FilterChainImpl               │ // Charset, Security filter 호출
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│          ServletHandler.service()           │ 
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│            HttpServlet.service()            │ // undertow -> spring servlet
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│          FrameworkServlet.doGet()           │ // doGet()을 호출한다
└─────────────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│      FrameworkServlet.processRequest()      │ // 최종
└─────────────────────────────────────────────┘

아마 상황에 따라 호출되는 filter와 handler가 다르겠지만, 기본적으로는 위와 같이 호출이 되는걸 볼 수 있다 (spring boot tomcat -> undertow로 교체하여 호출해본 결과)

원래 Spring boot에 오기 전에 Spring Framework를 사용했을 때는 web.xml을 수정해야 했던적이 있었다. web.xml에 url을 등록해서 해당 url로 들어왔을때의 Servlet Handler를 직접 지정해주는 방식을 사용했는데, 현재는 대부분 DispatcherServlet을 따로 설정하지 않고 기본적으로 모든 Request에 대해서 요청을 받는 DispatcherServlet을 1개 두고, 뒤에 Handler(@Controller) 를 둬서 처리하는 방식이 일반적이다.

<web-app>

    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>

</web-app>

예전에는 위와 같이 DispatcherServlet을 servlet으로 등록한 뒤, 해당 servlet을 직접 mapping(servlet-mapping) 해주는 작업을 해주곤 했었다..

번외로 Spring에서 기본적으로 지원하는 Servlet 엔진으로는 Tomcat, Undertow, Jetty 가 있다. (기본으로는 Embedded Tomcat이 사용된다) 인터페이스를 정의해두고 내부적으로 돌아가는 Setvlet Engine은 이를 구현함으로써 다양한 ServletEngine으로 교체하여 사용할 수 있다는 장점이 있다.

반응형

'Development > Spring' 카테고리의 다른 글

Spring Redis Template Transaction  (2) 2021.09.09
Spring RequestContextHolder  (8) 2020.07.05
Spring Password Encoder  (4) 2019.04.06
Spring Boot Prometheus Converter 406 Not Acceptable  (0) 2019.04.06
[JAVA/Spring] BeanUtils 관련  (2) 2018.07.20