Spring MVC는 front controller pattern으로 설계되어 있는데, 여기서 front controller가 바로 DispatcherServlet입니다.
DispatcherServlet은 요청에 필요한 공통된 알고리즘을 제공하고, 실제 동작은 각각의 적절한 컴포넌트에게 위임합니다.
또한 다른 Servlet과 마찬가지로 Java configuration이나 web.xml에 선언하여 매핑되어야 하는데요.
DispatcherServlet은 Spring configuration을 통해 request mapping, view resolution, exception handling에 필요한 컴포넌트를 찾습니다.
DispatcherServlet은 설정을 위해 WebApplicationContext을 사용합니다. WebApplicationContext에는 ServletContext 및 연관된 Servlet에 대한 link가 있습니다.
Root WebApplicationContext은 일반적으로 여러 Servlet 인스턴스에서 공유하는 repositories나 service같은 빈을 포함합니다. Servlet WebApplicationContext는 Root WebApplication을 상속하고 있으며, Controllers, ViewResolver, HandlerMapping을 포함하고 있습니다.
우선, DispatcherServlet의 동작 과정을 이해하기 위해서는 Interceptor에 대해 알아야합니다.
HandlerInterceptor
- 핸들러 인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터입니다.
- preHandle(): 컨트롤러가 호출되기 전에 실행
- 컨트롤러 실행 이전에 처리해야 할 작업이 있거나, 요청 정보를 가공 또는 추가하는 경우에 사용
- 리턴 값이 true이면 핸들러 실행 체인의 다음 단계로 진행하지만, false이면 작업을 중단
- postHandle(): 컨트롤러 실행 후 호출
- 컨트롤러 작업 결과를 참조, 조작할 수 있다.
- afterCompletion(): 모든 뷰에서 최종 결과를 생성하는 일을 포함한 모든 작업이 모두 완료된 후 실행
DIspatcherServlet 동작 과정
위 그림의 상속 구조를 보며 아래의 설명을 봐주시면 이해가 쉬울 것 같습니다.
브라우저에서 실제 요청을 해보니, DispatcherServlet이 상속한 FrameworkSerlvet의 service() 메서드가 실행되었습니다.
service() 메서드를 시작으로, DispatcherServlet doService()가 실행되고, 해당 메서드에서 doDispatch() 메서드를 호출합니다.
여기서 나온 doDispatch() 메서드가 가장 핵심 메서드입니다.
그럼 DispatcherServlet의 doDispatch() 메서드 코드를 살펴보겠습니다.
1) getHandler() 메서드를 통해, 요청을 처리할 수 있는 handler(controller를)를 찾습니다.
이때 만약 조건에 맞는 핸들러가 없을 경우 noHandlerFound()를 호출하여 404 에러를 세팅합니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
** mappedHandler = getHandler(processedRequest); **
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
...
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
2) 위에서 찾은 Handler를 getHandlerAdapter()에 넘겨주어, Handler에 맞는 HandlerAdapter를 찾습니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
** HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); **
...
}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
3) applyPreHandle() 메서드를 호출하여 Interceptor의 preHandle()메서드를 호출합니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
** if (!mappedHandler.applyPreHandle(processedRequest, response)) { **
return;
}
...
}
- HandlerExecutionChain.java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (isPreflight(request)) {
return true;
}
String token = AuthorizationExtractor.extract(request);
validateToken(token);
ServletContext servletContext = request.getServletContext();
servletContext.setAttribute("payload", jwtTokenProvider.getPayload(token));
return HandlerInterceptor.super.preHandle(request, response, handler);
}
현재 프로젝트의 preHandle() 구현체입니다. 여기서 반환값이 boolean인데, 만약 false가 나오면 뒤의 로직을 수행하지 않습니다.
4) 앞에서 찾은 HandlerAdapter로 Handler(controller)를 호출하여 비즈니스 로직을 처리하고, 반환값으로 ModelAndView를 받습니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
** mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); **
...
}
5) handle() 메서드를 수행하고 난 후, Interceptor의 postHandle() 메서드를 호출합니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
** mappedHandler.applyPostHandle(processedRequest, response, mv); **
...
}
- HandlerExecutionChain.java
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
6) 마지막으로 processDispatchResult()를 호출하여, Interceptor의 afterCompletion()를 실행합니다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
mappedHandler.applyPostHandle(processedRequest, response, mv);
** processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); **
...
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
...
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
Special Bean Types
DispatcherServlet은 요청을 처리하고 적절한 응답을 렌더링하기 위해, special bean들에게 역할을 위임합니다.
해당 빈들은 Spring에 기본적으로 구현되어 있지만, 속성을 재정의하거나 확장, 교체할 수 있습니다.
1) HandlerMapping
- 요청을 pre 또는 post Interceptor들에 따라 Handler에 매핑합니다. 매핑 기준이 있으며, 세부 사항은 HandlerMapping 구현체에 따라 다릅니다.
- @RequestMapping을 지원하는 RequestMappingHandlerMapping과 SimpleUrlHandlerMapping URI 경로 패턴을 사용하는 두 가지 주요 구현체가 있습니다.
2) HandlerAdapter
핸들러가 실제로 호출되는 방식에 관계없이, DispatcherServlet이 Handler를 실행할 수 있도록 도와줍니다.
예를 들어, @RequestMapping이 달린 컨트롤러를 호출하기 위해 해당 애노테이션에 필요한 일을 처리합니다.
3) ViewResolver
Handler에서 반환된 view 이름(String)을 response에 렌더링할 실제 View로 만듭니다.
4) HandlerExceptionResolver
예외를 해결하기 위한 전략(에러 페이지 등)
References
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-special-bean-types
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-handlermapping-interceptor
https://tecoble.techcourse.co.kr/post/2021-07-15-dispatcherservlet-part-2/
'Spring' 카테고리의 다른 글
[JPA] SimpleJpaRepository의 EntityManager는 어디서 생성될까? (0) | 2024.02.24 |
---|---|
[Spring, AOP] 테스트 격리를 위한 MockRestServiceServer 초기화 (0) | 2022.10.11 |
[Spring] 테스트 시간 최적화 (feat.Context Caching) (0) | 2022.08.15 |
[Spring] Pageable 커스텀 예외 처리(feat. @PageableDefault) (2) | 2022.07.05 |
[Test] 컨트롤러 테스트해야 할까? (feat. 테스트에 대한 혼동) (2) | 2022.05.25 |