
DispatcherServlet에서 요청 처리에 사용될 핸들러를 찾아주고, 실행할 때 사용된다.HandlerMapping은 요청에 따른 핸들러를 찾아준다.HandlerAdapter는 찾아낸 핸들러를 실행시켜줄 Adapter이다.코드와 동작을 디버깅하며 어떤 매커니즘으로 돌아가는지 봐보자.
@RestController
public class HelloController {
@RequestMapping("/")
public String sayHello() {
return "Hello, kblife intern";
}
}
[localhost:8080/](http://localhost:8080/) 경로로 접근했을 때 “Hello, kblife intern” 라는 텍스트를 띄워줄 것을 안다.DispatcherServlet으로 해당 요청을 보내고 HandlerMapping과 HandlerAdapter를 이용해 해당 요청을 처리한다.참고로
DispatcherServlet이 초기화 되는 시점은 스프링 애플리케이션을 키면서가 아닌 한 번 서블릿의 요청을 받은 이후이다. 그래서 이 초기화 과정이 무겁다면, 스프링을 켠 이후 첫 요청에 대한 응답 시간이 조금 걸리는 편이다. 반면에 이러한 레이지 로딩을 사용하는 덕분에 초기 스프링부트의 실행 시간은 매우 빠르다.
- 처음에 서블릿을 초기화하며
initWebApplicationContext()라는 메서드가 호출된다.
- 이는
DispatcherServlet이 상속받는FrameworkServlet에서 이루어진다.- 이 과정에서
onRefresh()가 호출된다.DispatcherServlet이 구현하는onRefresh()내부에는 여러가지 초기화 메서드가 존재한다.
```java
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context); // 핸들러 매핑 초기화
initHandlerAdapters(context); // 핸들러 어댑터 초기화
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
```
- 여기서 다양한 `HandlerMapping`과 `HandlerAdapter`를 초기화한다.
- 다른 `Resolver`와 같은 것들도 초기화 하는데, 이는 관심사가 아니므로 일단은 무시하겠다.
initHandlerMappings()의 소스코드이다.private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
BeanFactoryUtils.beansOfTypeIncludingAncestors() 메서드는 특정 타입 혹은 특정 타입을 상속한 타입까지 찾아 Map형태로 반환해주는 역할을 한다.여기서 자동으로 아래의 빈이 검출된다.

HandlerMapping은 추후 AnnotationAwareOrderComparator를 통해 정렬된다.HandlerMapping 에도 @Order 등을 통해 먼저 동작할 것을 설정할 수 있기 때문이다.
위의 initHandlerMappings() 소스코드에서도 구경해볼 수 있다.

AnnotationAwareOrderComparator를 통해 정렬된 후의 모습이다.HandlerMapping 타입의 빈을 만들어 DispatcherServlet에서 사용되게 만들 수 있다.HandlerMapping은 우선순위도 가장 높은 RequestMappingHandlerMapping이다.HandlerMapping이 가장 중요한 이유는 우리가 가장 흔하게 구경하는 @Controller 에너테이션이 붙은 클래스 내부에 존재하는 @RequestMapping 에너테이션이 붙은 메서드를 처리한다.RequestMappingInfo객체를 생성하여 ‘어떤 요청이 들어왔을 때, 해당 요청이 조건에 맞는 요청인지 확인하고, 조건에 맞다면, 해당 요청은 이 HandlerMapping을 통해 처리할 수 있다’ 라는 것을 알려주는 역할을 해준다.RequestMappingInfo는 처리할 수 있는 HTTP 요청 메서드, 요청 경로 등을 가지고 있어서 해당 요청을 처리할 수 있는지에 대한 정보를 가지고 있다.private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
initHandlerMappings()와 거의 동일하다.HandlerMapping을 찾지 않고, HandlerAdapter 타입의 빈을 찾을 뿐이다. 
HandlerAdapter 빈들이다.RequestMappingHandlerAdapter가 가장 중요하다.@Controller, @RequestMapping 애너테이션을 통해 등록한 메서드를 실제로 실행시켜주는 어댑터이다.AbstractHandlerMethodAdapter를 구현한 것으로 가장 중요한 메서드는 handlerInternal() 이다.@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
@RequestMapping 애너테이션을 단 메서드가 동작할 수 있게 해주는 역할을 한다.ArgumentResolver이고, 두번째가 ReturnValueResolver이다.ArgumentResolver는 우리가 @RequestParam(”id”) String id와 같이 파라미터를 줬을 때, HTTP 통신 요청 값 중 이에 해당하는 값을 가져와 파싱해서 id라는 파라미터에 직접 문자열로 넣어준다.ReturnValueResolver는 우리가 @RequestMapping에서 반환한 타입을 확인하여, 실제로 어떤 응답을 할지 결정한다.@Controller클래스 내부의 @RequestMapping 메서드에서 String을 반환한다면, ViewResolver에서 알맞는 view페이지를 찾아 반환할 수 있다.@RestController 클래스 내부의 @RequestMapping 메서드에서 String을 반환하면, Content-Type을 text/plain 으로 설정하고 단순 텍스트만 존재하는 페이지를 반환할 수도 있다.doDispatch() 에 브레이크 포인트를 거는 이유는 DispatchServlet의 사실상 핵심 동작을 담당하는 메서드이기 때문이다.HttpServlet에 있는 메서드들을 상속해 조건에 맞는 요청이 들어왔을 때 작성한 비즈니스 로직을 통해 의도한 동작을 수행하고 응답을 주는 것이다.DispatcherServlet은 HttpServlet을 상속하는 FrameworkServlet을 상속한다.FrameworkServlet은 모든 HTTP 요청을 processRequest() 라는 메서드에서 처리한다.processRequest() 내부에는 doService() 라는 추상 메서드를 통해 핵심 로직을 구현할 수 있게 되어있다.doService()는 FrameworkServlet을 상속받은 DispatcherServlet에서 구현되는데, 모든 요청 처리에 공통으로 적용되는 로직을 제외하면, 사실상의 핵심 로직은 doDispatch() 에서 구현하고 있다.doDispatch()에 브레이크 포인트를 걸면 사실상의 요청에 대한 응답이 스프링에서 어떻게 처리되는지에 대한 핵심 로직을 구경해볼 수 있다.protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
핵심 동작을 담당하는 함수들을 요약하면 다음과 같다.
getHandler()getHandlerAdapter()handlerAdapter.handle()@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;
}
DispatcherServlet내부의 getHandler() 메서드이다.HandlerMapping을 순회하며 request를 인자로 getHandler()를 계속 호출하는 게 동작의 전부이다.HandlerExecutionChain을 찾으면 거기서 핸들러를 반환하고 끝난다.getHandler()를 따라가다보면 실제로 구현한 클래스가 AbstractHandlerMapping이라는 것을 발견하게 된다.AbstarctHandlerMapping는 getHandlerInternal() 구현을 떠넘기는 방식으로 getHandler()를 구현해두었다.AbstractHandlerMethodMapping에서 getHandlerInternal()을 구현해두었다.@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
request가 어떤 경로를 향하는지 확인하고, 해당 경로에 해당하는 핸들러 메서드를 lookupHandlerMethod를 통해 찾는다.AbstractHandlerMethodMapping.afterPropertiesSet() 메서드에서 모든 핸들러 메서드를 전부 AbstartHandlerMethodMapping.mappingRegistry에 등록해놓았기 때문이다.
getHandlerInternal() 메서드 내부에 존재하는 lookupHandlerMethod() 메서드가 해당하는 핸들러를 찾아낸다.@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
getMappingByUrl() 이다./user 경로에 POST, GET, PATCH 등의 HTTP 메서드에 각각 작성된 핸들러도 있을 것이다.RequestMappingInfo에 존재하는 아래 compareTo() 함수에 의해 어떤 것이 우선순위가 높은지 따져보게 된다.@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
int result;
// Automatic vs explicit HTTP HEAD mapping
if (HttpMethod.HEAD.matches(request.getMethod())) {
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
}
}
result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
}
result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
if (result != 0) {
return result;
}
result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
if (result != 0) {
return result;
}
result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
if (result != 0) {
return result;
}
result = this.producesCondition.compareTo(other.getProducesCondition(), request);
if (result != 0) {
return result;
}
// Implicit (no method) vs explicit HTTP method mappings
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
}
result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
if (result != 0) {
return result;
}
return 0;
}
handlerMethod에 들어간 것을 확인할 수 있다.
@RestController
public class HelloController {
@RequestMapping("/")
public String sayHello() {
return "Hello, kblife intern";
}
}

HandlerExecutionChain으로 만드는 getHandlerExecutionChain() 메서드를 실행한느 절차를 밟아야 한다.protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
HandlerExecutionChain이 된다.
@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;
}
getHandler()의 동작을 분석하기 위해 먼 곳까지 가봤다.getHandler() 메서드에서 무사히 HandlerExecutionChain을 얻었다.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
getHandlerAdapter는 어떻게 동작하는지 알아보자.protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
handlerAdapters에 등록된 HandlerAdapter를 순회하며 support() 메서드를 수행해보고 지원하는 핸들러를 반환한다.
HandlerAdapter는 위와 같다.HandlerAdapter는 RequestMappingHandlerAdapter이다.@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
RequestMappingHandlerAdapter의 supports() 메서드는 직접 구현한 것이 아니라 AbstractHandlerMethodAdapter.supports() 에서 구현한 것을 상속했다.RequestMappingHandlerAdapter는 HandlerMethod만 실행할 수 있으므로 handler가 HandlerMethod의 한 종류인지 확인한다.supportsInternal()에 숨겨두었다.@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
true를 반환하게 되어있다.HandlerMethod 타입이면 크게 문제가 없던 것이다.Handler를 실행해줄 HandlerAdapter도 찾았다.String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
GET메서드이기 때문에, 캐시로 처리할 수 있는지 확인해야 한다.lastModified를 확인하는 부분이 있다.RequestMappingHandlerAdapter.getLastModified()는 AbstractHandlerMethodAdapter에 구현되어 있는 것을 사용한다.@Override
public final long getLastModified(HttpServletRequest request, Object handler) {
return getLastModifiedInternal(request, (HandlerMethod) handler);
}
@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
return -1;
}
-1을 반환하게 해두었다.RequestMappingHandlerAdapter을 만나는 것보다 앞단인 WebRequest 에서 이미 WebRequest.checkNotModified() 메서드를 통해 검증했기 때문이다.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
handle() 메서드는 AbstractHandlerMethodAdapter 에서 구현한 것을 사용한다.@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
handler실행의 실제 구현이 떠넘겨진 곳이다.checkRequest()메서드를 수행한다.WebContainerGenerator에 구현되어 있다.protected final void checkRequest(HttpServletRequest request) throws ServletException {
// Check whether we should support the request method.
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
}
// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
mav = invokeHandlerMethod(request, response, handlerMethod);
invokeHandlerMethod()를 구경해보자.@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
new ServletWebRequest(request, response)부터 한줄씩 살펴보자.public ServletWebRequest(HttpServletRequest request, @Nullable HttpServletResponse response) {
super(request, response);
}
HttpServletRequest와 HttpServletResponse를 받아 ServletWebRequest를 생성하려 한다.public ServletRequestAttributes(HttpServletRequest request, @Nullable HttpServletResponse response) {
this(request);
this.response = response;
}
request만을 이용해서 ServletRequestAttributes생성자에 넣는다.public ServletRequestAttributes(HttpServletRequest request) {
Assert.notNull(request, "Request must not be null");
this.request = request;
}
request가 null인지 간단히 확인한 후 request필드에 request를 할당하고 끝난다.
ServletWebRequest객체가 생성되었다.getDataBinderFactory() 메서드를 실행한다.private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first
this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
Object bean = controllerAdviceBean.resolveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
});
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);
}
@InitBinder나 @ControllerAdvice를 통해 만들어진 검증 메서드가 있는지 확인한다.initBinderMethods를 찾아 InitBinderDataBinderFactory 객체를 만든다.getModelFactory()를 구경해보자.private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
// Global methods first
this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
Object bean = controllerAdviceBean.resolveBean();
for (Method method : methodSet) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
});
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
@ModelAttribute를 처리하며 유효성 검사도 함께한다.protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
return new ServletInvocableHandlerMethod(handlerMethod);
}
ServletInvocableHandlerMethod객체를 생성한다.
이후의 초기화 과정을 살펴보면 다음과 같다.
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
MethodArgumentResolver를 세팅한다.MethodReturnValueResolver를 세팅한다.binderFactory를 세팅한다.ParameterNameDiscoverer를 세팅한다.ModelAndViewContainer를 생성한다.invocableMethod.invokeAndHandle(webRequest, mavContainer);를 실행한다.public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
returnValue를 계산해주기 위해 invokeForRequest() 메서드의 구현을 구경하러 가야한다.@Nullable
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);
}
getMethodArgumentValues()를 통해 argument 값들을 얻어온다.protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
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);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
@Nullable
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
public java.lang.String com.codesoom.assignment.controllers.HelloController.sayHello() 이다.getBridgedMethod().invoke(getBean(), args);를 통해 호출한다.public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
Reflection 트릭을 통해 실행한다.Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
returnValue에는 “Hello, kblife intern”이 할당되었다.setResponseStatus()를 수행한다.private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
HttpStatus status = getResponseStatus();
if (status == null) {
return;
}
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
response.sendError(status.value(), reason);
}
else {
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
@ResponseStatus 애노테이션을 처리하기 위한 함수이다.this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue),
mavContainer, webRequest);
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
selectHandler() 메서드로 넘어간다.@Nullable
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;
}
isAsyncValue는 스프링 Web MVC와 상관없으니 넘어가자.this.returnValueHandlers에는 무려 15개의 returnValueHandler가 담겨있다.
RequestResponseBodyMethodProcessor에 의해 처리될 것이 분명하다.@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
ResponseBody.class 애노테이션이 있으면 true를 반환하도록 되어있다.selectHandler() 메서드가 끝난다.handlerReturnValue() 메서드가 수행된다.@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
setRequestHandled(true) 를 수행 후에 inputMessage와 outputMessage를 생성 후에 종료된다.getModelAndView()를 반환하게 된다.@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
ModelAndView를 세팅한다.@ResponseBody는 RequestResponseBodyMethodProcessor에 의해 처리되었으므로 ModelAndView로 null을 반환한다.RequestMappingHandlerAdapter.handleInternal()가 끝난다.mappedHandler.applyPostHandle(processedRequest, response, mv);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
if (exception ≠ null) {} 에서는 예외가 있다면 처리한다.if (mv ≠ null && !mv.wasCleared()) {} 에서는 뷰가 있다면 렌더링한다.if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
afterCompletion콜백에 작성된 코드를 수행하는 부분이다.doDispatch()가 끝나고 doService()가 실행된다.doGet()SocketProcessorBase.java의 run()까지 가게 된다.