사전적 의미)
Handler : 매니저, 동물 조련사 등과같은 다양한 의미
HandlerMapping은 Mapping정보를 저장하고 전달해주는 등의 관리역할을 하는 인터페이스이다
가장 널리쓰이고 많이 알려진 @RequestMapping 어노테이션을 이용한 Handler방식에 대해서만 알아보자
RequestMappingHandlerMapping은 RequestMapping Annotation이 붙은 Class, Method들을 대상으로 실제 URL에 Mapping하는 작업을 하는 클래스
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
...
return builder.options(this.config).build();
}
Annotation이 붙은 타입이 Class인지 Method인지 구분 후 createRequestMappingInfo(requestMapping, condition)메서드에 위임한다
해당 메서드는 path, method, param, header, content-type, accept-type, mapping-name에 대한 정보를 갖고있게된다
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
...
}
return info;
}
@RequestMapping이 붙은 Method를 토대로 RequestMappingInfo를 생성해낸다
만약 Info가 Null이아니면 Class에 있을수도 있으므로 Class부분도 확인합니다
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
...
}
protected void processCandidateBean(String beanName) {
...
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
...
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
...
return getMappingForMethod(method, userType);
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); // GetMethod
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
AbstractHandlerMethodMapping클래스또한 Bean이기에 해당 Bean이 init될 때 RequestMapping에 대한 Scan이 시작된다
getMappingForMethod를 통해 우리가 위에서 알아왔던 Info Object를 받아온다
이 후 invocableMethod에서 해당하는 Class(userType)가 Proxy가 아니고 Method의 접근제어자가 private가 아니고 static이 아닌 Method를 식별하며
registerHandlerMethod에 의해 해당 Method가 최종적으로 등록된다
registerHandlerMethod에 대해 좀더 알아보자
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
...
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
다음과같이 Lock을 걸어준 뒤 registry에 Mapping정보와 Cors정책에 대한 내용을 저장한다
재밌는점은 RequestMapping작업을 하면서 Cors정책과 @ResponseBody의 Consumer Type정보를 확인한다는 것 이다
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
updateConsumesCondition(mapping, method);
}
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
ConsumesRequestCondition condition = info.getConsumesCondition();
if (!condition.isEmpty()) {
for (Parameter parameter : method.getParameters()) {
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
if (annot.isPresent()) {
condition.setBodyRequired(annot.getBoolean("required"));
break;
}
}
}
}
super.registerHandlerMethod에 상위클래스인 AbstractHandlerMethodMapping에 registerHandlerMethod가 호출된다
이 후 Parameter에 @RequestBody가 있다면 Content-Type필터 여부를 해당 어노테이션의 required값으로 지정한다
AbstractHandlerMapping.class
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
...
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
...
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandler를 HandlerMapping에 해당하는 HandlerAdapter, Interceptor에 대한 모든 정보를 가져온다
그리고 이들을 HandlerExecutionChain으로 묶어 PreInterceptor -> Handle -> PostInterceptor -> Complete순으로 처리할 수 있게 도와준다
DispatcherServlet을 확장하는데 사용되어지는 아주 핵심적인 인터페이스로서 외부 인터페이스 혹은 프레임워크와의 관계를 이어주는 용도로 사용된다
간단한 ServletAdapter와 @RequestMapping에대한 Adapter에 대해서 알아보자
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
정말 간단한 Adapter로 Servlet을 service하는 용도이다
RequestMapping에서의 Metadata정보를 토대로 Input -> Process -> Output 세단계의 기능을 구현하는 클래스이다
HttpRequest에 대한 처리와 HttpResponse에 대한 처리를 중점적으로 진행한다
BeanFactoryAware, InitializingBean을 구현해 실제 Bean에 접근할 수 있으며 동시에 Bean의 초기화를 구현할 수 있다
@Override
public void afterPropertiesSet() {
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initControllerAdviceCache() {
...
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
...
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
ArgumentResolver, InitBinderArgumentResolver, ReturnValueHandler, Request&ResponseBodyAdvice가 초기설정된다
이때이제 커스터마이징한 각종 Resolver들이 저장된다 (기본객체들또한 다수존재)
밑에보면 RequestBody.Advice.alss와 ResponseBodyAdvice.class부분이 있는데 이부분을 통해 우리가 자주사용하는 Json타입에대한 JsonViewRequest&ResponseBodyAdvice가 이때 등록된다
public RequestMappingHandlerAdapter() {
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
...
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
추가적으로 생성자를 통해 MessageConverter도 등록된다
AbstractHandlerMethodAdapter.class
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
RequestMappingHAndlerAdapter.class
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
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);
...
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
return getModelAndView(mavContainer, modelFactory, webRequest);
}
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
...
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
...
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;
}
DispatcherServlet에서 handler 메서드를 실행하면 위 메서드가 순차적으로 실행된다
ArgumentResolver, MethodReturnValueHandler 등의 기타정보를 등록하고 initModel을 통해 Session정보와 ModelAttribute정보를 가져온다 그 후 invokeAndHandler 통해 이들을 처리함
그 후 mavConatiner에 저장된 값을 토대로 View를 처리한다
이 때 RedirectAttrributes인경우는 Redirect를 진행시킨다
invokeAndHandler처리에 대해 조금더 들어가보자
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
...
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
...
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
invokeForRequest에서 Parameter를 주입받고 Reflection을 통해 메서드에 접근한뒤 결과값을 받아온다 그 후 reponseStatus를 구하고 returnValue를 Handler에게 처리를 위임시킨다
대표적인 Handler는 RequestResponseBodyMethodProcessor의 Json처리, ModelAndViewResolverMethodReturnValueHandler의 View처리 등이 있다
RequestResponseBodyMethodProcessor.class
@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);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
ModelAndViewResolverMethodReturnValueHandler.class
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (this.mavResolvers != null) {
for (ModelAndViewResolver mavResolver : this.mavResolvers) {
Class<?> handlerType = returnType.getContainingClass();
...
ModelAndView mav = mavResolver.resolveModelAndView(method, handlerType, returnValue, model, webRequest);
...
mavContainer.addAllAttributes(mav.getModel());
mavContainer.setViewName(mav.getViewName());
if (!mav.isReference()) {
mavContainer.setView(mav.getView());
}
...
}
}
}
[ RequestResponseBodyMethodProcessor ]
MessageConverter를 이용해 Input/Output의 Message를 수정한다
RequestMappingHandlerAdapter에서 RequestResponseBodyMethodProcessor 생성시 생성자로 MessageConverter를 넘겨준다
MessageConverter는 AbstractMessageConverterMethodArgumentResolver에서 처리가되는데 단순하게 read, write를 통해 body내용을 수정한다
[ ModelAndViewResolverMethodReturnValueHandler ]
Resolver를 통해 ModelAndView객체를 얻어 이를 View에 반영시킨다
1.RequestMappingHandlerMapping Bean이 초기화될 때 모든 RequestMapping을 Scan해 RequestMappingInfo객체에 저장한다
2.Mapping순서는 메서드 -> 클래스 순서이다
3.Adapter의 경우 Request(Argument Resovler)와 Response(ReturnValue Resolver)에대한 처리를 중점적으로 진행한다
4.Json관련 어노테이션 : @RequestBody는 기본적으로 Consume의 Type을 검사하며 @ResponseBody는 리턴값이 없으며 RequestResponseBodyProcessor.MessageConverter를 이용해 body값을 String으로 작성하고 Request&ResponseBodyAdvice를 통해 body값을 검증한다
5.Session주입 -> ModelAtrribute주입 -> ArgumentResolver진행 -> ReturnViewHandler