JAVA

[Java] 디스패치 서블릿 DispatcherServlet이란?

로춘남 2022. 4. 11. 18:09
728x90


Dispatcher-Servlet(디스패처 서블릿) 이란?

디스패처 서블릿의 dispatch는 "보내다"라는 뜻을 가지고 있다. 그렇기 때문에 Dispatcher-Servlet은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)라고 정의할 수 있다.

이 과정을 이해하기전에 앞서 클라이언트로부터 어떠한 요청이 들어오게되면 톰캣과 같은 서블릿 컨테이너가 요청을 받게 된다. 그리고 이 모든 요청을 프론트 컨트롤러인 디스패치 서블릿이 가장 먼저 받게 된다. 그러면 디스패치 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야하는 컨트롤러를 찾아서 작업을 위임하게 된다. 여기서 프론트 컨트롤러는 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러로서 MVC 구조에서 함께 사용되는 디자인 패턴이다.

 

Dispatcher-Servlet(디스패처 서블릿)의 장점

Spring MVC는 DispatcherServlet이 등장함에 따라 web.xml의 역할을 상당히 축소시켜주었다. 과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했지만, dispatcher-servlet이 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리면서 상당히 편리하게 이용할 수 있게 되었다. 컨트롤러를 구현해두기만 하면 디스패치 서블릿이 알아서 적합한 컨트롤로 위임을 해주는 구조라 편리하게 이용이 가능하다.

 

정적 자원(Static Resources)의 처리

Dispatcher Servlet이 요청을 Controller로 넘겨주는 방식은 효율적인것처럼 보일 수 있겠지만 모든 요청을 처리하다보니 이미지나 html/css/javascript 등과 같은 정적 파일에 대한 요청마저 모두 불러오기떄문에 이러한 과정에서 정적자원(Static Resources)을 불러오지 못하는 상황도 발생 할 수 있다. 이러한 문제를 해결하기위한 2가지 방법이 있다고한다.

1) 정적 자원에 대한 요청과 애플리케이션에 대한 요청을 분리

2) 애플리케이션에 대한 요청을 탐색하고 없으면 정적 자원에 대한 요청으로 처리

1. 정적 자원에 대한 요청과 애플리케이션에 대한 요청을 분리

이에 대한 해결책은 두가지가 있는데 첫번째는 클라이언트의 요청을 2가지로 분리하여 구분하는 것.

  • /apps 의 URL로 접근하면 Dispatcher Servlet이 담당.
  • /resources 의 URL로 접근하면 Dispatcher Servlet이 컨트롤할 수 없으므로 담당하지 않는다.

이러한 방식은 괜찮지만 상당히 코드가 지저분해지며, 모든 요청에 대해서 저런 URL을 붙여주어야 하므로 직관적인 설계가 될 수 없다. 그래서 이러한 방법의 한계를 느끼고 다음의 방법으로 처리를 하게 되었다.

2. 애플리케이션에 대한 요청을 탐색하고 없으면 정적 자원에 대한 요청으로 처리

두번째 방법은 Dispatcher Servlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource) 경로를 탐색하여 자원을 탐색하는 것. 이렇게 영역을 분리하면 효율적인 리소스 관리를 지원할 뿐 아니라 추후에 확장을 용이하게 해준다는 장점이 있다.

 

Dispatcher-Servlet(디스패처 서블릿)의 동작 방식

앞서 설명한대로 디스패처 서블릿은 적합한 컨트롤러와 메소드를 찾아 요청을 위임해야 한다. Dispatcher Servlet의 처리 과정을 살펴보면 다음과 같다.

클라이언트의 요청을 디스패처 서블릿이 받음 → 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음

→ 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함 → 핸들러 어댑터가 컨트롤러로 요청을 위임함

→ 비지니스 로직을 처리함 → 컨트롤러가 반환값을 반환함 →HandlerAdapter가 반환값을 처리함.

→ 서버의 응답을 클라이언트로 반환함.

 

이러한 과정을 구체적으로 한 번 알아보자면,

1. 클라이언트의 요청을 디스패처 서블릿이 받음

앞서 설명하였듯 디스패처 서블릿은 가장 먼저 요청을 받는 프론트 컨트롤러다. 서블릿 컨텍스트(웹 컨텍스트)에서 필터들을 지나 스프링 컨텍스트에서 디스패처 서블릿이 가장 먼저 요청을 받게된다.

아래가 참고그림인데, 실제로 Interceptor가 Controller로 요청을 위암하지는 않기때문에 그림은 단순하게 처리 순서를 도식한것에 중점을 두면 좋을것 같다.

2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음

디스패처 서블릿은 컨트롤러에게 요청을 위임해야 하는데 그러기 위해서는 요청을 위임할 컨트롤러를 찾아야 한다.

HandlerMapping의 구현체 중 하나인 RequestMappingHandlerMapping은 모든 컨트롤러 빈을 파싱하여 HashMap으로 (요청 정보, 요청을 처리할 대상)을 관리한다.

위에서는 쉬운 이해를 위해 컨트롤러라고 했지만 엄밀히 말해서는 요청에 매핑되는 컨트롤러, 메소드 등을 갖는 HandlerMethod 객체입니다. 

그래서 HadlerMapping은 요청이 오면 Http Method, URI 등을 사용해 요청 정보를 객체로 만들고, Map의 Key로 사용해 요청을 처리할 HandlerMethod를 찾고 HandlerMethodExecutionChain으로 감싸서 반환한다. HandlerMethodExecutionChain으로 감싸는 이유는 컨트롤러로 요청을 넘겨주기 전에 처리해야 하는 인터셉터 등을 포함하기 위해서다.

 

3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함

디스패처 서블릿은 컨트롤러로 요청을 직접 위임하는 것이 아니라 HandlerAdapter를 통해 컨트롤러로 요청을 위임한다.이때 Adapter를 통해 컨트롤러를 호출하는 이유는 공통적인 전/후처리 과정이 필요하기 때문. 대표적으로 요청 시에 @RequestParam, @RequestBody 등을 처리하기 위한 ArgumentResolver들과 응답 시에 ResponseEntity의 Body를 Json으로 직렬화하는 ReturnValueHandler들이 어댑터를 통해 처리된다.

그래서 디스패처 서블릿은 대신 컨트롤러로 요청을 위임할 HandlerAdapter 구현체인 RequestMappingHandlerAdapter를 찾는다. 그리고 앞서 찾은 HandlerMethodExecutionChain이 갖는 인터셉터들을 모두 실행한 다음에 HandlerAdapter를 통해 컨트롤러의 메소드를 호출하도록 요청을 위임한다.

 

4. 핸들러 어댑터가 컨트롤러로 요청을 위임함

요청을 처리할 대상 정보인 HandlerMethod 객체에는 컨트롤러 정보와 메소드 객체가 있으므로 리플렉션의 메소드 객체를 invoke 한다.

5. 비지니스 로직을 처리함

이후에 컨트롤러는 서비스를 호출하고 비지니스 로직들이 진행된다.

6. 컨트롤러가 반환값을 반환함

비지니스 로직이 처리된 후에는 컨트롤러가 반환값을 반환한다. 요즘 프론트엔드와 백엔드를 분리하고, MSA로 가고 있는 시대에서는 주로 ResponseEntity를 반환한다. 

7. HandlerAdapter가 반환값을 처리함

컨트롤러로 요청을 위임한 HandlerAdapter는 컨트롤러로부터 받은 응답을 ReturnValueHandler를 통해 후처리한 후에 디스패처 서블릿으로 돌려준다. 만약 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정한다. 만약 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환하게 된다.

8. 서버의 응답을 클라이언트로 반환함

디스패처 서블릿을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환된다.


출처: https://mangkyu.tistory.com/18 [MangKyu's Diary]

728x90