이번 글에서는 SpringBoot에서 DispatcherServlet을 활용하여 HTTP 요청을 처리하는 과정을 살펴보려 합니다.
* 해당 과정은 spring boot 2.7.5를 기준으로 작성하였습니다.
그전에 간단히 DispatcherServlet이 HTTP 요청을 처리하는 과정을 살펴보면 위의 사진과 같습니다.
- DispatcherServlet에서 요청을 받습니다.
- HandlerMapping에서 적합한 Handler를 찾습니다.
- 적합한 Handler를 반환합니다.
- HandlerAdapter에게 Handler를 전달하고 실행합니다.
- Handler를 실행합니다.
- Handler 실행 결과를 반환합니다.
- HandlerAdapter의 실행 결과를 반환합니다.
- 요청 처리 결과를 반환합니다.
그럼 요청을 처리하는 DispatcherServlet, HandlerMapping, HandlerAdapter 그리고 우리가 작성한 빈을 포함하고 있는 Handler 객체는 어떻게 생성되는 걸까요?
ApplicationContext가 생성될 때
DispatcherServelt
DispatcherServletRegistrationBean 빈 생성하며 DispatcherServelt 객체 생성합니다.
// DispatcherServletAutoConfiguration line 108 ~ 127
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
HandlerMapping
RequestMappingHandlerMapping 빈 생성합니다.
// WebMvcConfigurationSupport line 304 ~ 343
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
...
}
RequestMappingHandlerMapping은 InitializingBean 구현체이기 때문에 의존관계 주입 완료 이후에 초기화 콜백인 afterPropertiesSet 메서드 수행합니다.
initHandlerMethods 메서드에서 Handler를 생성하기 위한 빈을 찾습니다.
// AbstractHandlerMethodMapping line 212 ~ 229
// Handler method detection
/**
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
그리고 찾은 빈과 함께 Handler를 HandlerMapping에 등록합니다.
// AbstractHandlerMethodMapping line 331 ~ 333
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
이때 등록되는 Handler는 HandlerMethod 클래스로 아래와 같이 생성됩니다.
// HandlerMethod line 161 ~ 182
public HandlerMethod(
String beanName, BeanFactory beanFactory,
@Nullable MessageSource messageSource, Method method) {
Assert.hasText(beanName, "Bean name is required");
Assert.notNull(beanFactory, "BeanFactory is required");
Assert.notNull(method, "Method is required");
this.bean = beanName;
this.beanFactory = beanFactory;
this.messageSource = messageSource;
Class<?> beanType = beanFactory.getType(beanName);
if (beanType == null) {
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
}
this.beanType = ClassUtils.getUserClass(beanType);
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
ReflectionUtils.makeAccessible(this.bridgedMethod);
this.parameters = initMethodParameters();
evaluateResponseStatus();
this.description = initDescription(this.beanType, this.method);
}
HandlerAdpater
RequestMappingHandlerAdpater 빈 생성합니다.
// WebMvcConfigurationSupport line 665 ~ 694
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
* Applicationcontext 생성 후 로그
첫 요청을 처리할 때
ApplicationContext가 생성되며 DispatcherServlet, HandlerMapping, HandlerAdapter가 생성되었지만 DispatcherServlet에 연결되지는 않았습니다.
DispatcherServlet에 각 객체들은 애플리케이션 시작 후 첫 요청을 처리하며 연결됩니다.
DispatcherServlet 초기화
요청을 처리하며 DispatcherServlet이 초기화가 진행되었는지 확인합니다.
// StandardWrapper line 803 ~ 805
if (!instanceInitialized) {
initServlet(instance);
}
...
// StandardWrapper line 827
return instance;
DispatcherServlet의 초기화가 진행되지 않았다면 초기화를 진행합니다.
그렇게 DispatcherServlet의 초기화를 통해 HandlerMapping 그리고 HandlerAdapter를 연결한 이후 요청을 처리합니다.
// DispatcherServlet 489 ~ 507
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
DispatcherServlet의 요청 처리
DispatcherServelt을 초기화한 이후 요청을 처리하기 위해 DispatcherServelt이 상속한 HttpServlet에 위치한 service 메서드를 실행합니다.
요청의 Http Method에 따라 doXXX 메서드가 실행됩니다. *해당 글에서는 GET 요청으로 진행
// HttpServlet line 766 ~ 780
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);
}
// HttpServlet line 660 ~ 723
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
if (lastModified == -1) {
doGet(req, resp);
} else {
try {
...
} catch (IllegalArgumentException iae) {
...
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
...
doGet(req, resp);
} else {
...
}
}
} 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 {
...
}
}
HandlerMapping에서 요청에 맞는 Handler 찾기
이후 DispatcherServlet의 doDispatch 메서드에서 요청에 해당하는 Handler를 찾습니다.
// DispatherServlet line 1047 ~ 1054
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
getHandler 메서드에서 Handler를 결정하는 과정에서는 기존 HandlerMapping에 등록된 HandlerMethod가 아닌 해당 객체의 bean 값을 활용하여 beanfactory로 적합한 bean 객체를 주입한 새로운 HandlerMethod를 생성하여 사용합니다.
// HandlerMethod line 371 ~ 379
public HandlerMethod createWithResolvedBean() {
Object handler = this.bean;
if (this.bean instanceof String) {
Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
String beanName = (String) this.bean;
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler);
}
Handler를 처리할 HandlerAdapter 찾기
그리고 HandlerMapping에서 찾은 Handler를 처리할 수 있는 HandlerAdapter를 결정합니다.
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
HandlerAdapter에게 요청 처리 위임
HandlerAdapter에 요청 처리를 위임합니다.
// DispatherServlet line 1070
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
HandlerAdapter가 요청을 처리하는 과정에선 handler를 그대로 사용하는 것이 아닌 handler를 InvocableHandlerMethod로 전달하여 요청을 처리하기 위한 HandlerMethod를 생성하여 이후 과정을 수행합니다.
* 이때 사용되는 빈은 앞서 Handler를 찾으며 주입한 빈입니다.
// RequestMappingHandlerAdapter line 895
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// InvocableHandlerMethod 198 ~ 206
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}
...
}
* 첫 요청에 의해 DispatcherServlet이 초기화되고 요청이 수행된 로그
'스프링' 카테고리의 다른 글
BeanPostProcessor (1) | 2025.01.16 |
---|---|
@Async 정리 (0) | 2025.01.02 |
이벤트 리스너 정리 (0) | 2025.01.02 |
@EnableWebMvc를 붙이지 않은 이유 (0) | 2024.09.23 |
Spring에서의 Filter 간단 정리 (2) | 2024.09.19 |