Spring mvc 구조 이해

강정우·2023년 12월 2일
0

Spring-boot

목록 보기
28/73

DisapatcherServlet

  • 그리고 실제 spring mvc가 생긴 모양이다.
    굉장히 유사한 모습이다. 물론 기능은 spring mvc가 훨씬 많겠지만
    동작과정이 비슷하다는 것이다.

  • 아래 다이어그램을 보면 알 수 있는데
    참고로 cmd + O 를 눌러서 검색을 띄운 다음 다어어그램 보기를 우클릭을 선택하면 아래와 같이 볼 수 있다.

  • 우리가 사용한 HttpServlet을 상속받고있는 것으로 나와있다.

  • 스프링 부트는 DispatcherServlet을 서블릿으로 자동등록하면서 urlPatterns = "/" 에 대하여 자동 매핑한다.
    물론 더 자세히 명시를 해두면 그게 우선순위가 높게 동작하여 기존에 등록한 서블릿도 함께 동작한다.

  • 그래서 FrameworkServlet 잘 살펴보면 service 메서드를 오버라이드하여 FrameworkServlet.sevice를 시작으로 굉장히 여러 메서드가 호출되면 결국에는 DispatcherServlet의 doDispatch() 메서드가 실행되는데 이게 핵심이다.

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 호출: 뷰 리졸버를 찾고 실행한다.
    JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.
  7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
    JSP의 경우 InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 로직이 있다.
  8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.

커스텀 방법

  • 인터페이스들을 구현하여 구현체를 DispatcherServlet에 등록하면 우리만의 컨트롤러를 만들 수 있다.
    물론 쉽진 않다.
    그리고 아마 커스텀할 일은 없을 것이다.

HandlerMapping, HandlerAdapter

@Component("springmvc/old--controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return new ModelAndView("new-form");
    }
}
  • 스프링 mvc의 컨트롤러는 이렇게 생겼다.
    그래서 HandlerMapping이 위 이름에 맞춰서 컨트롤러를 찾고
    HandlerAdapter가 핸들러 매팅을 통해 찾은 핸들러를 동작시키는데 이때 Controller 인터페이스를 통하여 편하게 구현하도록 작성되어있다.

  • 아래는 스프링 부트가 자동으로 등록하는 핸들러 매핑과 핸들러 어댑터에 대한 설명이다. (극히 일부)

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

HandlerAdapter
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리 
  • HandlerMapping에서 0순위의 RequestMappingHandlerMapping는 위 에서 작성한 컨트롤러에서는 무시된다.
    왜냐하면 @Controller를 등록하지 않았기 때문이다.

  • 그래서 HandlerAdapter에서도 0,1순위를 무시하고 2순위로 가게되는데 이 코드를 살짝 분석해보면

  • 우리가 작성한 것과 마찬가지로 support가 되는지 와 handle이라는 메서드로 모델(뷰)을 반환하는 컨트롤러의 핸들리퀘스트가 있다. 마치 버전3와 비슷한 동작 과정이다.

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

    }
}
  • 그리고 위 코드는 Adapter중 0순위 다음 1순위인 HttpRequestHandlerAdapter에 걸리도록 HttpRequestHandler를 구현하여 작성하였다.

viewResolver

1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능
에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
  • 여기도 마찬가지로 0순위인 BeanNameViewResolver가 있고 우리는 @view를 등록하지 않았기 때문에 자동으로 다음 InternalResourceViewResolver로 넘어간다.

  • InternalResourceViewResolver는 InternalResourceView 를 반환한다.
    그리고 InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.

  • view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.

  • 참고로 Thymeleaf에도 각각에 대응되는 리졸버와 뷰가 따로 존재한다.

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글