개발은 아름다워

[ Spring ] DispatcherServlet 니 누구야? 본문

스프링

[ Spring ] DispatcherServlet 니 누구야?

do_it_zero 2024. 10. 12. 11:32

 

DispatcherServlet 니 누구야?

 

DispatcherServlet는 무슨 역할을 하는 것일까?

DispatcherServlet은 크게 보면 서버로 들어온 요청들을, 그 요청들을 담당하는 controller(handler)에 배정시켜주는 역할을 한다. 마치 작업 반장과 같은 느낌!
(물론 더 많은 역할을 수행한다.)

 

그렇다면 어떻게 동작하는걸까?

서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다. 스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 오버라이드 해두었기 때문에 FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출된다.
서블릿이 호출되면 결국 DispatcherServlet.doDispatch()가 호출된다는 것이다.

doDisptch()는 뭘하는데?

protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {

HttpServletRequest processedRequest = request;

HandlerExecutionChain mappedHandler = null;

ModelAndView mv = null;

// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, 
dispatchException);
}

private void processDispatchResult(HttpServletRequest request, 
HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView
mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}

protected void render(ModelAndView mv, HttpServletRequest request, 
HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();

// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}

이런 일들을 한다.

동작 순서를 정리하자면
1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6.viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다.

위와 같은 기능을 할 수 있도록 스프링은 인터페이스를 제공해준다.

주요 인터페이스 목록

  • 핸들러 매핑 : org.springframework.web.servlet.HandlerMapping
  • 핸들러 어댑터 : org.springframework.web.servlet.HandlerAdapter
  • 뷰 리졸버 : org.springframework.web.servlet.ViewResolver
  • 뷰 : org.springframework.web.servlet.View

주요 인터페이스들만 구현해서 DispatcherServlet에 등록하면 나만의 컨트롤러를 만들 수 있는 것이다.

 

핸들러 매핑과 핸들러 어댑터는 어떻게 동작하는가?

서블릿 요청이 오면 DispatcherServlet.doDispatch() 가 호출된다.
doDisptch() 과정에는 핸들러 조회와 핸들러 어댑터 조회, 핸들러 어댑터 실행, 핸들러 실행이 있다. 이 과정을 한 번 자세히 보자.

클라이언트 요청으로 contoroller가 어떻게 실행되는지 알아볼 것이다.
첫번째로는 Controller 인터페이스를 상속 받은 OldController가 어떻게 실행되는지
두번째는

 

Controller 인터페이스 상속 받은 controller 호출 과정

package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component("/springmvc/old-controller")
public class OldController implements Controller {
 @Override
 public ModelAndView handleRequest(HttpServletRequest request, 
HttpServletResponse response) throws Exception {
 System.out.println("OldController.handleRequest");
 return null;
 }
}

@Component 어노테이션 덕분에 OldController는 /springmvc/old-controller라는 이름의 스프링 빈으로 등록되었다. 서버에 요청이 오면 DispatcherServlet은 요청 URL과 같은 이름의 빈을 매핑할 것이다.

스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다. 따라서 요청이 들어오면 doDispatch() 안에 구현된 핸들러 매핑과 핸들러 어댑터가 실행된다.

스프링부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터는 다음과 같다. (실제로는 더 많음)

HandelerMapping

0 = RequestMappingHandlerMapping : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.

HandlerAdapter

0 = RequestMappingHandlerAdapter : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스 처리

핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.

Q. 그렇다면 OldController는 어떻게 호출된걸까?

아래는 doDispatch() 안에서 이루어 지고 있는 과정이다.

  1. 핸들러 매핑으로 핸들러 조회
    1) HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
    2) 이 경우 빈 이름으로 핸들러를 찾아야 하기 떄문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandelrMapping이 실행에 성공하고 핸들러인 OldController를 반환한다.
  1. 핸들러 어댑터 조회
    1) HandlerAdapter의 supports()를 순서대로 호출한다.
    2) SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다.
  2. 핸들러 어댑터 실행
    1) 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다
    2) SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행하고, 그 결과를 반환한다.

정리하자면 OldController를 실행하기 위해 HandlerMapping으로는 BeanNameUrlHandlerMapping이 쓰였고 HandlerAdapter로는 SimpleControllerHandlerAdapter 가 쓰였다.

 

HttpReqeustHandler를 상속 받은 controller 호출 과정

HttpReqeustHandler 핸들러는 서블릿과 가장 유사한 형태의 핸들러이다.

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
 
 @Override
 public void handleRequest(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("MyHttpRequestHandler.handleRequest");
 }
}

Q. MyHttpRequestHandler는 어떻게 호출이 되는걸까?
1. 핸들러 매핑으로 핸들러 조회
1) HandelrMapping을 순서대로 실행해서, 핸들러를 찾는다.
2) 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping가 실행에 성공하고 핸들러인 MyHttpRequestHandler를 반환한다.

  1. 핸들러 어댑터 조회
    1) HandlerAdapter의 supports()를 순서대로 호출한다.
    2) HttpReqeustHandelrAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
  2. 핸들러 어댑터 실행
    1) 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
    2) HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환한다.

정리하자면 MyHttpRequestHandler를 실행하기 위해 HandlerMapping으로는 BeanNameUrlHandlerMapping이 쓰이고 HandlerAdapter로는 HttpRequestHandleAdapter가 쓰였다.

 

뷰 리졸버는 어떻게 동작하는걸까?

이 쯤 되면 흐름이 끊길 수 있다. 지금 서버에 요청이 들어오고 DispatcherServlet.doDispatch() 호출되는 과정 중에 있다.
그 과정을 다시 리마인드 하자면
1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6.viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다.

서버에 요청이 들어온 후 DispatcherSevlet이 핸들러 어댑터가 정보를 반환하는 과정까지 살펴보았다.

이번에 위에서 예시든 OldController와 MyHttpRequestHandler에 view조회할 수 있도록 해서 알아보자

 

OldController가 view를 조회할 수 있도록 변경

@Component("/springmvc/old-controller")
public class OldController implements Controller {
 @Override
 public ModelAndView handleRequest(HttpServletRequest request, 
HttpServletResponse response) throws Exception {
 System.out.println("OldController.handleRequest");
 return new ModelAndView("new-form");
 }
}

return 값을 null 에서 new ModelAndView("new-form") 으로 바꾸었다.

스프링 부트가 자동 등록하는 뷰 리졸버
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다.
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.

viewResolver 동작 과정

  1. 핸들러 어댑터 호출 : 핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득
  2. viewResolver 호출
    1) new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출
    2) BeanNameViewResolver는 new-form이라는 이름의 스프링 빈으로 등록된 뷰를 찾야하는데 없음
    3) InternalResourcViewResolver가 호출된다.
  3. InternalResourcViewResolver : 이 뷰 리졸버는 InternalResoureceView를 반환한다.
  4. InternalResourceView : InternalResourceView는 JSP forward()를 호출해서 처리할 수 있는 경우에 사용한다.
  5. view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.

정리
DispatcherSevelt은 뭘까? 에 대한 늘 궁금증이 있었지만 복잡해서 보여서 쉽사리 정리하지 못했다. 누군가 DispatcherServlet이 뭐냐고 물어본다면 대답해 줄 수 있다. 그건 바로 "작업반장!" 이라고 말이다!