쌓고 쌓다

Spring MVC 구조 본문

프로그래밍/spring

Spring MVC 구조

승민아 2023. 8. 5. 17:18

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);
Comments