DispatcherServlet
을 통해 들어온 클라이언트 요청을 처리할 적절한Handler
를 찾아오는 역할을 한다.
즉,HandlerMapping
을 통해 요청을 처리할 수 있는 적절한Handler
를 찾아 올 수 있다.
스프링이 제공하는 HandlerMapping
의 종류는 여러가지가 있다.
왜냐하면 클라이언트 요청을 처리하는 Handler
즉, Controller
를 만드는 방법이 다양하기 때문이다.
그래서 Spring
은 다양한 Handler
들 중 요청 처리에 적절한 Handler
를 가져오는 HandlerMapping
또한 여러가지 제공해준다.
여러가지 HandlerMapping
중 이번 포스팅에서는 BeanNameUrlHandlerMapping
을 알아보자.
빈의 이름에 들어 있는 URL을 HTTP 요청의 URL과 비교하여 일치하는 빈을 찾아 준다.
예를들면 Handler
이름이 "/gudonghee"
와 같은 Url형식
일 때, BeanNameUrlHandlerMapping
에 등록해두었다가 "/gudonghee"
라는 Url 형식
의 요청이 오면 일치하는 "/gudonghee"
이름의 Handler
를 찾아준다는 것이다.
아직 감이 잘안온다.😮
테스트 코드를 통해서 BeanNameUrlHandlerMapping
이 어떻게 Handler
들을 등록하는지 살펴보자.
우선 위와 같이 @Configuration
를 통해 ApplicationContext
에 BeanNameUrlHandlerMapping
객체를 빈 등록한다.
그리고 빈 이름이 "/test"
인 Controller
를 등록한다. BeanNameUrlHandlerMapping
은 "/"
로 이름을 시작하는 Handler
들을 상태로 가지고 있기 때문에 WelcomeController
를 상태로 가지고 있을것으로 예상이 된다.😮
테스트 코드 디버그 통해 확인 해보자.
우선 테스트코드를 통해 12번 줄과 같이 ApplicationContext
를 읽어온 다음 16번 줄과 같이 ApplicationContext
를 통해서 BeanNameUrlHandlerMapping
을 가져온다.
디버그를 통해 확인해보면 예상과 같이 BeanNameUrlHandlerMapping
객체가 필드인 handlerMap
내부에 WelcomeController
를 가지고 있음을 알 수 있다.
이번에는 빈 네임이 "/test"
였던 빈의 이름을 위 사진과 같이 reaver
라는 이름으로 빈 등록하고 디버그를 해보자.
BeanNameUrlHandlerMapping
의 hanlderMap
이 비어있음을 확인 할 수 있다.
왜냐하면 BeanNameUrlHandlerMapping
은 빈의 이름이 url로 시작하는 빈들만
handlerMap`에 저장하기 때문이다.
즉, URL
은 /
로 시작하기 때문에 빈 이름이 /
시작하는 경우에만 BeanNameUrlHandlerMapping
이 handler
로 가지고 있는다.
아래에서 실제 코드를 통해 자세히 살펴보자.🙂
물론 지금은 애노테이션을 기반으로 하는 HandlerMapping
이 있기 때문에 BeanNameUrlHandlerMapping
을 사용하는 것이 좋지 않아보인다.
하지만, 아직 BeanNameUrlHandlerMapping
을 사용하는 기업 또는 개인이 있을수 있기 때문에 스프링은 호환성을 위해서 BeanNameUrlHandlerMapping
를 코드에서 남겨두지 않았을까 한다.
위에서 BeanNameUrlHandlerMapping
이 어떤 Handler
들을 필드로 등록하는지 살펴보았다.
그렇다면 실제로 Handler
를 등록하는 과정을 BeanNameUrlHandlerMapping
코드를 통해 살펴보자.
우선, BeanNameUrlHandlerMapping은 위와 같은 상속 구조를 가진다.
왼쪽 상단을 보면 HandlerMapping
인터페이스를 구현하고 있음을 알 수 있다.
오른쪽에는 ApplicationContext
와 관련한 상속 구조로 보이지만 다음에 자세히 살펴보자.
Tomcat
이 실행되고 Servlet
을 초기화 하기 위한 init()
메서드가 호출 될 때, DispatcherServlet
은 위와 같이 initHandlerMappings()
메서드를 통해 HandlerMapping
들을 초기화 한다.
그리고 DispatcherServlet
의 initHandlerMappings()
가 실행되면
BeanNameUrlHandlerMapping
도 필드에 담을 handler
들을 찾아서 초기화를 진행한다.
위 사진은 BeanNameUrlHandlerMapping
의 부모 객체인 AbstractDetectingUrlHandlerMapping
의 handlerMapping
초기화를 위한 메서드이다.
BeanNameUrlHandlerMapping
의 handler
초기화 과정을 따라가 보면 AbstractDetectingUrlHandlerMapping
의 initApplicationContext()
메서드에서 이루어진다.
위 사진은 BeanNameUrlHandlerMapping
의 부모 클래스 중 하나인 AbstractUrlHandlerMapping
클래스 이다.
72번 줄을 보면 handlerMap
을 필드로 가진다.
initApplicationContext()
메서드 과정이 끝나면,
앞선 테스트 코드 디버그로 살펴보았듯이 BeanNameUrlHandlerMapping
은 url
을 key로 handler
를 value로 handler들을 가지게 된다.
BeanNameUrlHandlerMapping
을 초기화 하는데 initApplicationContext()
라는 네이밍의 메서드를 사용하는 것이 어색하게 느껴졋다.
왜냐하면 initApplicationContext()라는 이름은 ApplicationContext 전반을 초기화한다고 느껴지기 때문이다.
그럼에도 BeanNameUrlHandlerMapping
을 초기화 하는데 initApplicationContext()
를 사용하는 이유는 BeanNameUrlHandlerMapping
이 Bean초기화를 도와주는 ApplicationObjectSupport
를 상속하기 때문이지 않을까 싶다.😮
실제로 initApplicationContext()
는 ApplicationObjectSupport
객체에 정의되어 있는데,
ApplicationObjectSupport
의 주석을 보면 컨텍스트 참조 및 초기화 콜백 메서드를 제공합니다.
라는 구문이있다.
그래서 BeanNameUrlHandlerMapping
은 초기화를 위해서 이미 존재하는ApplicationObjectSupport
의 initApplicationContext()
를 활용한게 아닐까 추측해본다.
다시 돌아와서 AbstractDetectingUrlHandlerMapping
의 initApplicationConext()
메서드를 따라가보자.
super.initApplicationContext()
내부를 따라가 보면 위와같이 AbstractHandlerMapping
의 initApplicationContext()
메서드를 호출한다.
얼핏 보아도 Interceptor를 초기화하는 일련의 작업을 해줌을 알 수 있다.
이번 포스팅에서는 HandlerMapping이 Handler를 초기화하는 것에 집중하기 위해 넘어가겠다.
다시 AbstractDetectingUrlHandlerMapping
의 initApplicationContext()
메서드를 살펴보면 detectHandlers()
메서드를 호출한다.
네이밍을 보면 handler
들을 handlerMap
에 등록 시켜주는 handler 초기화 메서드
의 느낌이 진하게 느껴진다.
따라가보자.🙂
우선 71~72번 줄을 보면 ApplicationContext
객체를 통해서 beanName
들을 가져온다.
그리고 beanName
들을 기반으로 url
을 찾고 Handler
를 등록한다.
78번 줄의 determineUrlsForHandler()
메서드가 beanName에 적절한 url
을 찾는 메서드이다.
한번 따라가 보자.
determineUrlsForHandler()
를 따라가 보면 위와같이 추상메서드로 선언부만 있다.
따라가보면 determineUrlsForHandler()
구체적인 구현은 BeanNameUrlHandlerMapping
에 정의되어 있다.
위 사진은 BeanNameUrlHandlerMapping
객체의 determineUrlsForHandler()
메서드의 구현부이다.
58~ 60번 줄을 보면 맨 처음 설명과 같이 BeanName
이 "/"
로 시작하는 경우에 빈 이름을 기반으로 url들을 지정
해주는 것을 알 수 있다.
determineUrlsForHandler()
를 통해서 BeanNameUrlHandlerMapping
은 url
기반의 BeanName
을 가진 Bean
만을 HandlerMap
에 등록 한다는 것을 알 수 있다.
이제 BeanName
을 기반으로 url
들을 가져왔다. 실제로 BeanName
과 Url
을 기반으로 Handler
를 등록하는 메서드는 81번 줄의 registerHandler()
이다.
거의 다 왔다. 따라가 보자.
위 사진은 AbstractUrlHandlerMapping
의 registerHandler()
메서드 이다.
for문을 통해 Handler
를 등록하는 메서드를 호출 함을 알 수 있다.
위 메서드가 실제로 Handler
를 HandlerMapping
에 등록하는 메서드이다.
코드가 굉장히 길다.🙂
그래서 핵심만 살펴보면url
이 "/"
인 경우에는 433번 줄과 같이 rootHandler
로 등록하고 "/*"
인 경우에는 439번줄 과 같이 defaultHandler
로 등록 함을 알 수 있다.
최종적으로는 446번 줄과 같이 HandlerMap
에 url
을 키값으로 Handler
를 등록함을 볼 수 있다.
이전 포스팅에서도 살펴보았듯이 DispatcherServlet
은 doDispatch()
메서드로 클라이언트 요청을 처리할 때,
위와 같이 getHandler()
메서드로 HandlerMapping
들로 부터 적절한 Handler
를 가져온다.
그리고 getHanlder()
메서드는 HttpServletRequest
에 적절한 Handler
를 찾아서 반환한다.
1261번 줄을 보면 HandlerMapping
의 getHandler()
로 적절한 Handler를 가져옴을 알 수 있다.
BeanNameUrlHandlerMapping
는 AbstractHandlerMapping
의 getHandler()
를 통해서 DispathcherServlet
에게 Handler
를 전달한다.
특이한점은 Handler
를 바로 전달하지 않고 516번 줄과 같이 HandlerExecutionChain
객체에 한번 감싸서 전달한다는 점이다.
한번 getHandlerExecutionChain()
메서드를 따라가 보자.
코드를 보면 파라미터인 handler
를 HandlerExecutionChain
객체로 변경한 다음 interceptor
들을 추가 함을 알 수 있다.
즉, DispatcherServlet
에게 바로 Handler
를 전달 하는 것이 아닌 HandlerExecutionChain
객체로 감싸서 전달하는 이유는 Interceptor
기능을 요청을 처리하기 위한 Handler
에 추가하기 위함 이라는 것을 알 수 있다.
실제 HandlerExecutionChain
내부를 보면 위와 같이 필드로 Handler
와 interceptor
들을 가지고 있다.
이상으로 HandlerMapping
객체 중, BeanNameUrlHandlerMapping
에 대해 알아보았다.
살펴본 코드 이외에도 많은 부분들이 있지만 DispatcherServlet
과 마찬가지로 궁금하거나 필요하다고 느껴지면 추가로 학습해 볼 예정이다.
실제 코드를 통해 다른분들의 HandlerMapping
에 대한 이해에 조금이나 마 도움이 되면 좋겠다.😊