스프링부트 해부학 : RequestFlow(2) - HandlerMapping, HandlerAdapter

정윤성·2022년 6월 3일
0

스프링부트 해부학

목록 보기
2/20

역할

사전적 의미)

Handler : 매니저, 동물 조련사 등과같은 다양한 의미


Handler

HandlerMapping

HandlerMapping은 Mapping정보를 저장하고 전달해주는 등의 관리역할을 하는 인터페이스이다
가장 널리쓰이고 많이 알려진 @RequestMapping 어노테이션을 이용한 Handler방식에 대해서만 알아보자

RequestMappingHandlerMapping.java

RequestMappingHandlerMapping은 RequestMapping Annotation이 붙은 Class, Method들을 대상으로 실제 URL에 Mapping하는 작업을 하는 클래스

RequestMappingHandlerMapping.createRequestMappingInfo

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에 대한 정보를 갖고있게된다

RequestMappingHandlerMapping.getMappingForMethod(Method, Class)

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부분도 확인합니다

AbstractHandlerMethodMapping.java

AbstractHandlerMethodMapping을 확인해보면 InitalizingBean을 구현하고 있다
@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값으로 지정한다

getHandler

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순으로 처리할 수 있게 도와준다

HandlerAdapter

DispatcherServlet을 확장하는데 사용되어지는 아주 핵심적인 인터페이스로서 외부 인터페이스 혹은 프레임워크와의 관계를 이어주는 용도로 사용된다

간단한 ServletAdapter와 @RequestMapping에대한 Adapter에 대해서 알아보자

SimpleServletHandlerAdapter.java

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
	throws Exception {

	((Servlet) handler).service(request, response);
    return null;
}

정말 간단한 Adapter로 Servlet을 service하는 용도이다

RequestMappingHandlerAdapter.java

RequestMapping에서의 Metadata정보를 토대로 Input -> Process -> Output 세단계의 기능을 구현하는 클래스이다

HttpRequest에 대한 처리와 HttpResponse에 대한 처리를 중점적으로 진행한다

BeanFactoryAware, InitializingBean을 구현해 실제 Bean에 접근할 수 있으며 동시에 Bean의 초기화를 구현할 수 있다

RequestMappingHandlerAdapter.afterPropertiesSet

@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도 등록된다

RequestMappingHandlerAdatper.handler
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

profile
게으른 개발자

0개의 댓글