Spring MVC Structure
김영한님의 스프링 MVC(1), 스프링 MVC(2) 를 보며 정리한 글입니다.
MVC 전체 구조
요청 흐름
- 클라이언트가 요청을 보낸다.
- 클라이언트의 요청을 WAS(Tomcat)이 받고 필터(Filter)를 호출하면서 request, response를 전달한다.
- 필터체인(Filter Chain)을 따라 요청에 대해 공통된 사항을 적용하고 서블릿(DispatcherServlet)에게 request, response 을 전달한다.
여러 필터들이 순서를 가지고 서로 사슬처럼 연결되있기에 필터체인 이라고 하며 필터체인을 따라 요청을 처리한 다음에 요청이 서블릿에게 넘어간다.
- HandlerMappins에서 요청을 처리할 수 있는 핸들러를 가져온다.
- HandlerAdapters에서 핸들러를 호출할 수 있는 어댑터를 가져온다.
- 등록된 인터셉터들을 루프를 돌며 preHandle을 호출한다. (예외가 발생한 경우 15 과정으로 넘어간다.)
- requset를 통해 넘어온 argumet를 처리할 수 있는 argumentResolver를 찾는다.
- argumentResolver가 argument를 핸들러가 받을 수 있도록 변환한다.
요청으로 들어온 argumet만큼 argument resolve가 발생한다.
- 핸들러에서 실제 로직 수행 후 값을 반환한다.
- 반환값을 처리할 수 있는 ReturnValueHandler를 찾는다.
- 반환값을 응답형식에 따라 처리한다.
- ModelAndView 를 반환한다.
클라이언트에게 웹페이지를 보여주지 않고 json으로 응답하는 경우는 response에 json값을 넣고 null을 반환한다.
- 등록된 인터셉터들을 루프를 돌며 postHandle을 호출한다. (예외가 발생한 경우 15 과정으로 넘어간다.)
- 8 ~ 11 과정 중 예외가 발생했으면 다음 과정으로 넘어가지 않고 이 과정으로 온다. 발생한 예외를 처리할 수 있는 Resolver를 찾고 예외처리를 한다.
- 등록된 인터셉터들을 루프를 돌며 afterCompletion 호출한다.
15번 과정은 예외 발생여부와 상관 없이 항상 수행되며 이전 과정까지 처리가 되지않은 예외들은 afterCompletion메소드의 파라미터로 전달된다.
- 12 과정에서 반환된 ModelAndView의 viewName으로 View를 생성한다.
- View를 렌더링해서 response에 넣는다.
12 과정에서 null을 반환했다면 16, 17 과정은 수행되지 않는다.
디버깅
- DispatcherServlet doDispatch 메소드가 실행된다.
//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
}
- 요청을 처리할 수 있는 핸들러를 찾아온다. (요청 URL과 매칭되는 핸들러를 찾아온다.)
//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
mappedHandler = getHandler(processedRequest);
...
}
//org.springframework.web.servlet.DispatcherServlet#getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
- 핸들러 어댑터를 찾아온다.
//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
}
- 인터셉터목록 루프를 돌며 preHandle을 호출한다. 이때 예외가 발생하면 바로 인터셉터의 afterCompletion을 수행하고 서블릿을 빠져나간다.
//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
...
}
//org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
- 실제 핸들러를 호출하기위한 과정이다.
//org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
//이때 json을 반환하는 핸들러라면 mv 는 null이다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
//org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
...
//디버깅을 쭉 하다보면 결국 여기까지 온다.
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
}
- 파라미터를 루프를 돌면서 ArgumentResolver를 찾고 resolve한다.
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
...
MethodParameter[] parameters = getMethodParameters();
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
...
}
- 실제 핸들러를 호출한다.
//jdk.internal.reflect.NativeMethodAccessorImpl#invoke0
private static native Object invoke0(Method m, Object obj, Object[] args);
Comments