스프링 공식 문서 뿌수기(9), DispatcherServlet - 동작 과정

2023. 5. 6. 10:28자바/스프링

728x90
반응형

일주일동안 푹 쉬었다가 다시 돌아왔다! 해야지 마음을 먹으면서도 웹 자동차 경주 미션 이후로 미션 기간에는 미션에만 집중하자!라는 생각으로 이번주는 미션만 했었다.(조금 놀기도 하고) 그래서 공식문서를 읽으며 공부하는게 조금 늦어졌다. ㅎㅎ

 

이번에는 DispatcherServlet에 대해서 한번 정리해보고자 한다. 사실 스프링 스터디를 진행 중인데 이번에 맡은 파트는 MVC Config 였지만 그 전에 DispatcherServlet에 대해 정리하면 학습에 더 도움이 될 것 같아 이렇게 먼저 정리하고자 한다.


 

DispatcherServlet

 

 ...

 

딱 봐도 내용이 많다. 실제로 공식문서 들어가서 보면 내용이 정말 많다는 것을 알 수 있다. 뭐 이 시간에 다 아는 것을 목표로 하지 말고 가볍게 동작 원리에 대해 이해하는 것을 목표로 해야겠다.

 

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.

 

디스패처 서블릿을 설명하는 첫줄은 위와 같다. HTTP request에 대한 핸들러/컨트롤러를 위한 중앙 디스패처이다. 웹 요청을 처리하기 위해 핸들러를 등록하여 디스패치하고 매핑이나 예외 핸들링 등에 대한 기능을 제공한다. 라고 한다. 요청이 들어왔을 때 컨트롤러나 핸들러에 적절하게 매핑해주는 역할을 하는 것처럼 보인다. 이거를 조금 더 파고 들기 전에 서블릿이 무엇인지 먼저 확인해보자.

 

 

Servlet

 


 

 

서블릿은 웹서버 내부에서 돌아가는 자바 프로그램을 의미한다. 웹 서버 내부에서 돌아가기 때문에 웹 클라이언트로부터 요청을 받고 응답을 하는 등의 동작을한다. 자바에서는 서블릿이라는 것이 인터페이스로 존재한다. HttpServlet이나 GenericServlet 등이 이 예시이다. 

 

서블릿의 life cycle과 관련된 메서드도 아래에 설명이 되어있다. init 메서드는 서블릿을 처음 만들 때 사용한다. 그리고 service 메서드를 통해 클라이언트로부터 요청을 받을 수 있다. 이후 모든 동작을 완료하고 서블릿을 종료하고 싶으면 destroy 메서드를 사용하면 된다. 그러면 가비지 컬렉터가 이를 정리한다고 한다. 음... 설명이 조금 부족한 것 같다. 그러면 위키피디아를 한번 펼쳐보자.

 

첫 문단에서도 알 수 있듯이 서블릿의 주요한 역할은 요청에 대한 응답임을 알 수 있다. 아래로 내려보면 서블릿의 라이프 사이클에 대해 더 자세하게 나온다.

 

여기서 지금 우리에게 필요한 부분은 service 메서드이다. service 메서드는 클라이언트의 요청을 처리할 때 사용한다고 위에서 언급했다. service 메서드는 생성되는 요청의 종류를 결정하고, 그 요청을 처리할 적절한 메서드로 디스패치하는 동작을 한다. 우리가 스프링으로 웹 미션을 수행할 때 코드를 짜는 부분이 바로 요청을 처리할 적절한 메서드를 만드는 것이라고 생각하면 된다.

 

이때 우리가 구현하지 않은 메서드를 요청하면 서블릿의 부모 클래스의 메서드가 호출되어서 클라이언트에게 오류가 반환된다. 

 

브라우저 get 요청

 

postman get 요청

 

우리가 컨트롤러에서 RequestMapping으로 설정하지 않은 url로 요청이 들어왔을 때 위와 같이 화이트라벨 페이지가 뜨고, 404 status를 가지고 있는 json이 반환되는 것을 확인할 수 있다. 

 

 그런데 우리가 만든 서버에서는 에러 로그가 뜨지 않는다. 왜냐하면 service()를 통해 우리가 작성한 메서드로 매핑되어서 호출되지 않고, 바로 servlet에서 설정한 예외 페이지가 반환되는 것이기 때문이다. 즉, 우리가 작성한 컨트롤러까지 들어오지도 않는다. 궁금했던 부분이었는데 이런 원리가 있는지 몰랐다.

 

조금 더 자세히 서블릿의 동작 원리를 살펴보자.

출처: educba

클라이언트가 url 방문 요청을 한다. 일반적으로는 브라우저를 통해서 request 요청을 할 것이기에 브라우저가 해당 url에 대해서 HTTP 요청을 생성하고 이를 서버로 전송한다.

 

웹 서버는 HTTP 요청을 수신하고 이를 서블릿 컨테이너(웹 컨테이너)에게 전달한다. 컨테이너는 이 요청을 특정한 서블릿에 매핑한다. 그리고 서블릿은 동적으로 검색되어 로드가 된다. 서블릿이 메모리에 처음 로드되었다면 init() 서블릿 메서드를 호출한다.  이후 컨테이너는 서블릿의 service 메서드를 호출한다. 이 메서드를 통해 HTTP 요청을 처리한다. 서블릿이 이때 HTTP 요청에 대한 데이터를 읽고 적절한 메서드로 매핑하는 과정을 거치게 된다. 이후 메서드가 그 요청을 모두 처리한 뒤에 응답을 하면 서블릿은 이를 적절하게 처리한 뒤 응답하게된다.

 

이게 서블릿의 동작 과정이다. 요약하자면 요청이 들어오면 서블릿 컨테이너는 해당 요청에 대해 적절한 서블릿을 찾아서 로드하고, 그 서블릿에 HTTP 요청을 전달한다. 서블릿은 전달받은 HTTP 요청을 까보고 적절한 메서드를 찾아서 매핑해주고 요청을 전달하고 응답을 받는 과정을 거친다. 이는 차례로 다시 상위로 올라가면서 클라이언트에게 응답이 전달된다. 

 

뭐... 궁금한 점은 많지만 일단 여기서 넘어가도록 하겠다. 구체적으로 어떤 방식으로 응답을 포장하고 처리하는지는 나중에 찾아보자. 

 

디스패처 서블릿은 이 여러 서블릿 중 하나인 것을 알 수 있다. 이제 서블릿에 대해 조금 알아봤으니 대충 디스패처 서블릿도 이런 동작을 하겠구나하는 느낌적인 느낌이 있다. 조금만 더 자세히 알아보자.

 

 


DispatcherServlet

 

 

출처: 망나니 개발자

 

사실 이전에 디스패처 서블릿에 대한 전체적인 맥락을 이해할 때 망나니 개발자님 블로그의 글이 정말 도움이 많이 됐었기에 그 글에서 디스패처 서블릿 동작 원리에 대한 이미지를 하나 들고 왔다. 아래에 출처를 남길테니 한번 확인해보면 좋을 것 같다.

https://mangkyu.tistory.com/18

 

우리가 위에서 봤던 것은 클라이언트 요청이 들어왔을 때 서블릿까지 도달하는 데에 동작하는 원리였고, 이제는 디스패처 서블릿의 동작에 대해서 조금 더 자세히 알아보도록 하겠다.

 

 

 

HandlerMapping

 

디스패처 서블릿은 위에서 설명했듯이 HTTP 요청에 대한 응답을 처리할 때 적절한 메서드로 매핑해주는 역할을 한다. 이때 요청에 대해 라우팅을 제어하기 위해 HandlerMapping을 사용할 수 있다고 한다. HandlerMapping은 뭐 이름에서도 알 수 있듯이 요청과 핸들러 객체를 매핑해주는 역할을 한다. 따로 HandlerMapping을 설정하지 않으면 디폴트로 BeanNameUrlHandlerMapping과 RequestMappingHandlerMapping이 사용된다고 한다.

 

BeanNameUrlHandlerMapping은 url에서 '/' 이후에 있는 문자를보고 핸들러를 매핑한다. 예를 들어 '/foo'라는 url로 요청을 들어오면 '/foo'로 빈 이름이 설정된 핸들러로 매핑되는 것이다.

 

RequestMappingHandlerMapping은 @Controller 어노테이션이 있는 클래스 내부에 @RequestMapping이라는 어노테이션이 붙은 메서드로부터 RequestMappingInfo를 만든 뒤 이를 매핑해주는 역할을 한다. 즉, 우리가 @Controller나 @RequestMapping 등 여러 어노테이션을 사용하면 이때 RequestMappingHandlerMapping이 우리가 작성한 메서드를 핸들러로 매핑해준다.

 

이러한 HanlderMapping은 어플리케이션 컨텍스트에 빈으로 등록할 수 있다.

 

 


HandlerAdapter

 

 

 

핸들러와 디스패처 서블릿 사이에서 적절한 접점을 제공하기 위해 adapter가 사용된다. adapter는 세 가지 메서드를 제공하고 있다. 

여기서 supports 메서드는 핸들러가 전달되었을 때 adapter가 해당 핸들러를 지원하는지 판단할 때 사용된다. supports가 true를 반환하면 해당 adapter를 통해 handle을 하게 된다.

handle은 adapter에게 request와 response, 그리고 handler를 전달해서 전달받은 handler에 request와 response를 전달하는 역할을 한다. 이때 파라미터로 전달되는 HttpServletRequest와 HttpServletResponse는 각각 HTTP request와 response라고 보면 된다. 

 

요약하자면 서블릿은 adapter에게 우선 handler를 전달해서 '너 이 handler 다룰 수 있어?'를 묻고(supports()), 다룰 수 있다고 한다면 adapter에게 handler와 request/response를 전달해서 클라이언트의 요청을 처리한다.(handle())


adapter에 기본적으로 등록되는 것은 HttpRequestHandler를 다루기 위해서 HttpRequestHandlerAdapter가, Controller를 다루기 위해서는 SimpleControllerHandlerAdapter가 사용된다. 그리고 HandletMethods의 어노테이션인 @RequestMapping을 다루기 위해서는 RequestMappingHandlerAdapter도 기본적으로 등록 된다. 

 

일단 HttpRequestHandler는 handlerRequest라는 메서드 하나만 가지고 있다.

이 메서드는 request와 response를 처리할 수 있도록 돕는다. 

 

Controller도 마찬가지로 handlerRequest라는 메서드를 하나만 가지고 있다.

그런데 HttpRequestHandler와는 다르게 ModelAndView를 리턴하는 것을 알 수 있다. 핸들러에서 이를 리턴하면 디스패처 서블릿은 랜더링을 하는 역할을 한다. 

 

여기까지 공식문서에서 소개하는 handlerMapping, handler, handlerAdapter 에 대한 내용이다. 

영어로 계속 작성을 하다보니 글이 잘 읽히지도 않고, 내가 다시 돌아봐도 어려운 것 같다. 밑에서 한번 정리해보자.

 

뭐가 이리 복잡해! 정리해줘~

 

서블릿은 요청에 대해서 응답을 처리해주는 역할을 한다. 서블릿 컨테이너(혹은 웹 컨테이너)는 여러 개의 서블릿을 가지고 있어서 요청이 들어왔을 때 이에 맞는 서블릿을 동작시킨다. 여러 개의 서블릿 중에서 스프링에서 사용하는 서블릿은 dispatcherServlet이다.

 

가장 먼저 외부에서 요청이 들어오면 dispatcherServlet은 그 요청에 맞는 handler를 매핑한다. 기본적으로 등록된 handlerMapping은 BeanNameUrlHandlerMapping과 RequestMappingHandlerMapping이다. BeanNameUrlHandlerMapping은 빈에 등록된 핸들러 이름을 보고 매핑을 시켜준다. RequestMappingHandlerMapping은 @RequestMapping 어노테이션을 사용해 등록한 핸들러의 url 주소를 보고 매핑해준다.

 

HandlerMapping을 통해 찾은 handler가 있다면 이제 dispatcherServlet에 등록된 adapter들에게 mapping된 handler를 다룰 수 있는지 묻는다. 만약 다룰 수 있는 adapter가 있다면 그 adapter에게 handler를 전달해서 그 handler의 동작을 수행한다. HttpRequestHandler를 구현한 handler의 경우에는 HttpRequestHandlerAdapter를 adapter로 사용한다. Controller를 구현한 handler의 경우에는 SimpleControllerHandlerAdapter를 adapter로 사용한다. 그리고 @RequestMapping 어노테이션을 사용해 등록한 handler는 RequestMappingHandlerAdapter를 adapter로 사용한다.

만약 HandlerMapping을 통해 handler가 없다면 servlet에서 지정한 예외 페이지와 json이 전달된다.

 

이후 요청에 대해서 handler가 동작한 뒤 adapter에서 적절하게 처리해서 ModelAndView를 리턴하면 dispatcherServlet에서 이를 받아서 응답 처리를 한다.

 

자... 솔직히 정리한 글만 보더라도 이게 영어인지 한국어인지 잘 모르겠다. 그렇다면 뭐? 한번 실습을 통해서 지금 정리했던 내용을 이해해보는 시간을 가지면 좋을 것 같다.

 

다음 포스팅은 지금 설명한 내용이 코드 상에서는 어떻게 동작하는지 확인해보는 내용을 적도록 하겠다!

728x90
반응형