쌓고 쌓다
Spring MVC 구조 본문
Sprng MVC 구조
Spring MVC도 FrontController 패턴이다.
DispatcherServlet가 FrontController이다.
DispatcherServlet은 부모를 타고타고 들어가보면 HttpServlet을 상속 받아있기에 서블릿으로 동작한다.
스프링부트는 DispatcherServlet을 서블릿으로 등록하며 모든 경로(urlPatterns="/")에 대해서 매핑한다.
*DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
요청 흐름
서블릿이 호출되면 HttpServlet의 service()가 호출된다.
그러나 FrameworkServlet에서 service()를 오버라이드해놨기에
FrameworkServlet.service()를 시작으로 여러 메소드를 호출한 후
DispatcherServlet.doDispatch()가 호출된다.
DispacherServlet.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());
// 순서 5 이후의 과정
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
}
위의 SpringMVC를 만들어본 MVC 구조와 비교해보자.
1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회.
만들었던 MVC 구조를 보면 이해가 잘간다.
@WebServlet(name="frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request);
if (handler == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
}
요청에 맞는 핸들러(Controller)를 조회하는 과정이다.
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
@WebServlet(name="frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MyHandlerAdapter adapter = getHandlerAdapter(handler);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
}
}
어댑터 역할을 하는 핸들러 어댑터가 있는 덕분에 다양한 종류의 컨트롤러를 호출할 수 있는 것이다.
3. 핸들러 어댑터 실행, 4. 핸들러 실행, 5. 모델뷰 반환
핸들러 어댑터를 찾았기에 이제 핸들러 어댑터를 통해서 핸들러를 실행한다.
핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 어떻게든 변환해서 반환한다.
그 결과로 모델뷰를 반환 받는다.
순서 5 이후의 과정
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);
}
6. viewResolver 호출, 7. 뷰 반환
뷰 리졸버를 통해서 뷰를 찾아 반환 받는다.
뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고 렌더링 역할을 하는 뷰를 반환한다.
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
// view.render(mv.getModel(), request, response);
논리 이름, 물리 이름 변환?
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
여기서 viewName처럼 사람이 이해하기 쉬운 이름이 논리 이름이고 물리 이름은 실제 사용되는 정확한 위치를 뜻한다.
8. 뷰 렌더링
뷰를 통해서 뷰를 렌더링하여 보여준다.
view.render(mv.getModel(), request, response);
'프로그래밍 > spring' 카테고리의 다른 글
핸들러 매핑과 핸들러 어댑터 이해하기 (0) | 2023.08.06 |
---|---|
[스프링 부트] 파일 응답 및 이미지 출력, MIME 타입 확인 - 19 (0) | 2023.08.05 |
[스프링 부트] 파일 다운로드 - 18 (0) | 2023.08.04 |
List<MultipartFile> 빈 파일 문제, MultipartFile null 체크 (0) | 2023.08.03 |
[스프링 부트] 파일 업로드 - 17 (0) | 2023.08.02 |