본문 바로가기

Programing/Framework

[Spring] mvc - DispatcherServlet 1부

서블릿과 스프링 MVC는 밀접하면서 독립적이다.

 

DispatcherServlet 클래스는 HTTP 요청을 처리하는 중심 디스패처 클래스이다.

DispatcherServlet 클래스는 아래와 같은 계층 구조를 가진다.

DispatcherServlet 의 클래스 계층도

Servlet 인터페이스

서블릿의 핵심은 요청과 응답이다. ServletRequest과 ServletResponse가 요청 및 응답을 추상화해놓은 인터페이스이다.

public interface Servlet {
    // ..
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

HttpServlet 추상 클래스

Servlet 인터페이스의 구현체인 HttpServlet 라는 추상클래스가 있다. 이 클래스는 Servlet 인터페이스가 정의한 service 메서드를 HTTP 메서드 별로 호출하도록 분기를 해놓았다.

public abstract class HttpServlet extends GenericServlet {
    // ..
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
                // ..
                doGet(req, resp);
            }
        } else if (method.equals(METHOD_HEAD)) {
            // ..
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            // ..
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

스프링이 아닌 서블릿을 직접 구현을 해보았다면 익히 보았을 doGet, doHead, doPost, doPut, doDelete 등의 protected 메서드들이 있다. 이 메서드들을 보통 오버라이딩해서 구현하는 코드를 짰을 것이다.

public abstract class HttpServlet extends GenericServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
	// ..
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

 

FrameworkServlet 추상클래스

스프링에서는 FrameworkServlet가 HttpServlet 추상클래스의 doXXX 메서드들을 오버라이드한다.

doGet, doHead, doPost, doPut, doDelete 등은 모두 processRequest 라는 내부의 메서드를 호출하게 된다.

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
    // ..
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// ..
		try {
			doService(request, response);
		}
        // ..

	protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
			throws Exception;

 

processRequest에서 핵습은 doService를 호출하는 것이다.

재미있는 것은 Servlet 인터페이스의 핵심은 원래 service 하나였다.

그런데 HttpServlet 추상클래스가 쪼개어 놓은 각 메서드를 스프링이 다시 하나로 합치고 있는 것이다.

DispatcherServlet 클래스

DispatcherServlet는 FrameworkServlet 추상클래스에서 추상메서드로 정의한 doService 메서드를 구현을 하고 있다.

DispatcherServlet은 doService를 doDispatch를 호출한다.

public class DispatcherServlet extends FrameworkServlet {
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// ..
		try {
			doDispatch(request, response);
		// ..
        
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// ..
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				// ..
				mappedHandler = getHandler(processedRequest);
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                
				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

 

doDispatch에서 핵심로직은 HandlerAdapter를 가져와서 handle 메서드를 호출하는 일이다.

HandlerAdapter 인터페이스는 아래와 같이 간단하다.

public interface HandlerAdapter {
	boolean supports(Object handler);
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);
}

handle 메서드는 AbstractHandlerMethodAdapter에서 오버라이드 하고 있는데 handleInternal 메서드를 호출하도록 되어 있다.

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}

구현체인 RequestMappingHandlerAdapter 클래스의 handleInternal 는 invokeHandlerMethod 를 호출한다.

invokeHandlerMethod 내부에서는 ServletInvocableHandlerMethod의 invokeAndHandle을 호출하게 된다.

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;
	}

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		try {
			// ..
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			// ..
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
			// ..
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
    // ..
	protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
		return new ServletInvocableHandlerMethod(handlerMethod);
	}

ServletInvocableHandlerMethod 의 계층구조는 아래와 같다.

 

ServletInvocableHandlerMethod 클래스에서 invokeAndHandle 메서드에서는 핵심은 invokeForRequest 및  HandlerMethodReturnValueHandlerComposite의 handleReturnValue의 호출이다.

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;
		}
	}

InvocableHandlerMethod 클래스에 invokeForRequest 메서드가 구현이 되어 있다. 내부의  doInvoke 메서드가 호출된다.

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

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		return doInvoke(args);
	}

	protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
			return getBridgedMethod().invoke(getBean(), args);
		}

getBridgedMethod 메서드는 HandlerMethod 클래스에 구현이 되어 있는데 아래와 같다.

public class HandlerMethod {
	private final Method method;
	private final Method bridgedMethod;
	private final MethodParameter[] parameters;
    private HandlerMethod resolvedFromHandlerMethod;
    
	protected Method getBridgedMethod() {
		return this.bridgedMethod;
	}

bridgedMethod 가 호출하는 메서드가 컨트롤러의 요청을 처리하는 메서드이다.

만약 아래와 같은 컨트롤러가 있다고 하면 home() 같은 메서드가 bridgedMethod 가 될 수 있을 것이다.

인자가 있었다면 parameters에 해당 인자들이 배열로 구성이 된다.

@Controller
public class JspViewTestController {
    @RequestMapping(value = "/")
    public String home() {
        return "index";
    }

invoke는 말그대로 Controller의 메서드가 호출되는 것이다.

 

메서드가 리턴이 되면 invokeAndHandle의 invokeForRequest 호출이 반환되고 결과를 처리하는 handleReturnValue 가 호출이 된다.

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    
	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;
		}
	}

HandlerMethodReturnValueHandlerComposite 는 HandlerMethodReturnValueHandler 인터페이스를 구현하고 있는 컴포지트 클래스인데 내부에 HandlerMethodReturnValueHandler 들의 리스트를 가지고 있다.

리턴타입을 지원하는 핸들러를 찾아서 해당 핸들러를 적용할 수 있게 해준다.

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
	private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}
    
public interface HandlerMethodReturnValueHandler {
	boolean supportsReturnType(MethodParameter returnType);
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

HandlerMethodReturnValueHandler 인터페이스를 구현한 처리기는 아래 목록 처럼 꽤 많다.

등록되는 핸들러만 15개이다.

위의 내용을 도식화하면 아래와 같다.