Java의 Spring에서 @RequestMapping가 구현되어 있는 방식을 알아보자.

java의 spring에 대하여 공부를 하고 있는 중이다. 요즘은 오픈소스를 사용하는 법 보다는 동작 원리 자체가 궁금한 상태 이다, 이에 대하여 가장 간단한 RequestMapping이라는 어노테이션을 보기로 정하였다. 해당 어노테이션은 스프링에서 지원해주는 기능이지만 다른 언어에도 mvc 프레임워크는 있으므로 이것만 알면 다른 언어에서의 동작 원리는 비슷하게 이해할 수 있으리라 본다.

RequestMapping

매핑할 메서드에 아래와 같이 매핑을 시켜주면 스프링에서 알아서 읽어 들어와서 api를 생성해준다.
@RequestMapping(value="/post")
여기서 내가 궁금해진 것은 @RequestMapping과 같은 Annotation을 도대체 누가 들고 오는건가 하는 생각이다. 어디선가 어떤 클래스가 알아서 파싱 할것 같지만 그 주체가 누군인지가 궁금하다.

RequestMappingHandlerAdapter
RequestMapping에 대한 공식 문서를 확인해보면 해당 어노테이션을 어디서 사용 할 수 있는지 볼 수 있다. GetMapping들과 같은 어노테이션은 단순히 RequestMapping의 Get 기능인듯 하다. 여기서 보면 RequestMappingHandlerAdapter가 연결되어 있음을 볼 수 있다. 공식 문서에서도 또한 RequestMapping을 서포트 한다고 작성되어 있다. 코드를 보니 RequestMappingHandlerAdapter의 코드에서 RequestMapping을 검색하면 직접적으로 해당 클래스를 가져오는것 같은 코드는 없다. 

@Nullable
private ControllerMethodResolver methodResolver;

ControllerMethodResolver
직접적으로는 안쓰지만 위의 ControllerMethodResolver라는 클래스에서 getRequestMappingMethod라는 메서드를 통해서 가져오는듯 한 느낌이 있다. 해당 메서드를 확인해보면 아래와 같은 코드에서 MethodFilter라는 인터페이스를 직접 구현하고 있다. 이는 메서드를 받아서 람다식으로 구현이 되어 있다.


private static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
		(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
			AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));

위의 함수는 initControllerAdviceCaches에서와 getModelAttributeMethods에서 사용을 한다.  여기서 빈을 가져올때는 ControllerAdviceBean라는 애를 통해서 가져오고 있다. 

ControllerAdviceBean
위의 클래스는 스프링에서 관리하는 빈들을 캡슐화 해서 관리한다고 작성되어 있다. 이 클래스는 이 자체로도 빈을 담은 객체의 기능을 하고 있으며 내부의 findAnnotatedBeans 함수를 통해서 빈들을 리턴 해준다. 
빈을 가져오는 코드는 아래와 같다. 컨텍스트를 통해서 빈을 가져오고 이때 가져오는데 도움을 주는 클래스는 BeanFactoryUtils 클래스 이다.
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
		List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
		for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
			if (!ScopedProxyUtils.isScopedTarget(name)) {
				ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
				if (controllerAdvice != null) {
					// Use the @ControllerAdvice annotation found by findAnnotationOnBean()
					// in order to avoid a subsequent lookup of the same annotation.
					adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
				}
			}
		}
		OrderComparator.sort(adviceBeans);
		return adviceBeans;
	}
BeanFactoryUtils
빈팩토리유틸에서는 아래와 같이 빈들의 이름을 리턴 한다. 아래에서는 특정 클래스를 지정할 수 있어 보이지만 위의 함수에서는 Object.class를 사용하기 때문의 의미는 없다. ApplicationContext는 아래의 HierarchicalBeanFactory를 상속받고 있기 때문에 아래의 instanceof문이 적용될것으로 보인다. 빈간의 부모 자식 관계가 있는듯 하지만 RequestMapping자체에는 큰 연관은 없어 보인다. 
public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class<?> type) {
		Assert.notNull(lbf, "ListableBeanFactory must not be null");
		String[] result = lbf.getBeanNamesForType(type);
		if (lbf instanceof HierarchicalBeanFactory) {
			HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
			if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
				String[] parentResult = beanNamesForTypeIncludingAncestors(
						(ListableBeanFactory) hbf.getParentBeanFactory(), type);
				result = mergeNamesWithParent(result, parentResult, hbf);
			}
		}
		return result;
	}
ApplicationContext

위의 ListableBeanFactory는 인터페이스이며 ConfigurableApplicationContext가 구현한다. 이는 RequestMappingHandlerAdapter에서 setApplicationContext를 통해서 설정이 된다. 해당 함수는 ApplicationContextAware라는 인터페이스에서 구현하며 아마 컨텍스트를 넣어주는 기능으로 보인다. 해당 컨텍스트를 가져오는 것으로 보아서 bean 객체들은 모두 저기에 저장이 되어있는듯 하다. 결국 Bean자체를 저장하는 것은 ApplicationContext에서 구현한다고 봐도 무방 하다. 더 위로 올라가는 것은 RequestMapping 구현 자체와는 거리가 멀은것 같으므로 넘어 가기로 한다.


DispatcherHandler
위의 RequestMapping함수가 get 되는 부분은 RequestMappingHandlerAdapter.handle 함수이고 이는 DispatcherHandler에서 호출 한다. 흠 그러나 알아보니 DispatcherHandler는 스프링 5이후에 있는 클래스인것 같다. RequestMapping기능은 4 이전에도 있던 기능인데 잘못 알아본건가..? 라고 생각하였으나 지금 보니까 spring4와 5의 동작 방식이 바뀐듯 하다. handle을 HttpWebHandlerAdapter와 DefaultWebFilterChain가 호출하는듯 하다. ServletHttpHandlerAdapter -> HttpWebHandlerAdapter -> DispatcherHandler 이런식으로 호출하는 것 같다. 주석에 default라고 작성되어 있는것으로 보아서 추가로 핸들러를 변경하지 않는 이상 해당 경로를 통해서 함수가 실행될것으로 본다.

이 위로는 JettyHttpServer, TomcatHttpServer이라는 정도인걸로 보아 WAS 단일걸로 생각이 된다. 

Spring 4에서는?
이전 버전이긴 하지만 Spring 4가 비교적 더 익숙하기 때문에 해당 버전에서도 찾아 보았다. 

댓글

이 블로그의 인기 게시물

고려대학교 야간대학원 중간 후기

포켓몬 고 17셀 확인 포고맵 사용 방법

HTTP 오류 500.19 - Internal Server Error 에러 처리법