상세 컨텐츠

본문 제목

Dispatcher Servlet 파헤치기 (Argument Resolver, ReturnValue Handler, Controller 실행시점)

Spring/개념

by Chan.94 2024. 6. 30. 16:53

본문

반응형

Argument Resolver, ReturnValue Handler는 Dispatcher Servlet이 Handler Mapping 과정에서 등록, 사용되는 것을 이전 포스팅에서 확인할 수 있었다.

 

[Spring] Argument Resolver 개념 / Custom Argument Resolver 생성

[Spring] Return Value Handler 개념 / Custom Return Value Handler 생성

 

이번 포스팅에서는 소스레벨레벨에서 데이터가 어떻게 호출되고 관리되는지 확인해 보도록 하겠다.

 

Dispatcher Servlet (Argument Resolver, ReturnValue Handler) 동작구조


 

DispatcherServlet.java

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        ModelAndView mv = null;

        ...   
        // (2) Handler 조회 
        // URL에 맵핑된 Handler(Controller)를 찾는다.
        mappedHandler = getHandler(processedRequest);
 
        // (3) Handler Adapter 조회
        // Handler(Controller)를 수행할 수 있는 HandlerAdapter를 찾는다.
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 
        ...
 
        // (4) Handler Adapter 호출
        // (5) Handler 호출
        // (6) ModelAndView 반환
        // HandlerAdapter를 통해서 Handler를 실행한다.
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
        // (7) ViewResolver 호출
        // (8) View반환
        // (9) 응답
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
}

 

doDispatch 메서드는 dispatch 영문 해석 그대로 Server와 Client에게 정보를 전달하는 역할을 한다.

동작구조의 순서를 메서드에 매핑한 내용이다.

 

// (4) Handler Adapter 호출
// (5) Handler 호출
// (6) ModelAndView 반환
// HandlerAdapter를 통해서 Handler를 실행한다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

(4) Handler Adapter 호출, (5) Handler 호출, (6) ModelAndView 반환의 내용을 소스로 분석해 보자.

 

AbstractHandlerMethodAdapter.java

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }

    protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

}

AbstractHandlerMethodAdapter의 handle메서드 호출.

 

RequestMappingHandlerAdapter.java

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ModelAndView mav;
        ...
        mav = invokeHandlerMethod(request, response, handlerMethod);
        ...
        return mav;
    }


    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        …
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        …

        // Handler 호출
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
}

RequestMappingHandlerAdapter는 AbstractHandlerMethodAdapter를 상속받고 있어 결국 handleInternal 메서드가 호출된다.

handleInternal에서 invokeHandlerMethod를 호출한다.

ModelAndViewContainer가 중요한 객체이다.

 

// Handler 호출
invocableMethod.invokeAndHandle(webRequest, mavContainer);

Handler(Controller) 실행결과가 mavContainer에 담겨있다.

 

return getModelAndView(mavContainer, modelFactory, webRequest);
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    //생략    
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    //생략
    return mav;
}

실행결과는 mavContainer에 담겨있다고 했다. mavContainer에서 결과(Model)를 꺼내 ModelAndView 생성하여 DispatcherServlet로 반환한다.

 

ServletInvocableHandlerMethod.java

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        try {
            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }
}

위에서 RequestMappingHandlerAdapter의 invokeHandlerMethod메서드에서 invokeAndHandle메서드가 호출되는 것을 확인하였다.

 

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

Handler(Controller)에 적합한 ArgumentResolver가동작 한 후 Handler(Controller)가 실행된다.

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
       logger.trace("Arguments: " + Arrays.toString(args));
   }
   return doInvoke(args);
}

genMethodArgumentValues메서드를 들어가 보면 ArgumentResolver의 중요 메서드 supportsParameter, resolveArgument가 호출되는 것을 확인할 수 있다.

 

doInvoke(args);

우리가 그림으로 확인했던 Handler(Controller)가 호출되는 부분이 여기다.

 

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

Handle(Controller) 실행결과를 처리하는 ReturnValueHandler가 실행된다.

 

ArgumentResolver, ReturnValueHandler가 이 부분에서 호출된다.

 

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

DispatcherServlet의 HandlerAdaper의 handle메서드호출이 끝나고 ModelAndView에 실행결과가 담겨있게 되었다.

 

(7) ViwResolver 호출, (8) View반환, (9) 응답 부분을 확인해 보자.


DispatcherServlet.java

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        // (7) ViewResolver 호출
        // (8) View반환
        // (9) 응답
        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();
        if(viewName != null){
            // (7) ViewResolver 호출
            // (8) View반환
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        }
        else {        
            // ModelAndView 객체에 View 객체가 포함되어 있다.
            view = mv.getView();       
        }

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

    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,  Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
}

 

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

Handler(Controller) 실행 결과 ModelAndView를 적절한 response 형태로 처리한다.

 

    protected void render(ModelAndView mv, HttpServletRequest request,HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();
        if(viewName != null){
            // (7) ViewResolver 호출
            // (8) View반환
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        }
        else {        
            // ModelAndView 객체에 View 객체가 포함되어 있다.
            view = mv.getView();       
        }

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

등록된 ViewResolver 중 뷰 이름을 통해 처리할 수 있는 뷰 리졸버를 찾거나 ModelAndView에 이미 View가 포함되어 있으면 꺼내서 사용한다.

 

view.render(mv.getModelInternal(), request, response);

View 구현체마다 렌더링을 하는 방식이 다르다.

반응형

관련글 더보기

댓글 영역

>