Spring MVC- HandlerMapping의 동작방식 이해하기 2편

Sungwoo Hwang·2021년 11월 4일
2

스프링 MVC

목록 보기
2/2

MappingRegistry

MappingRegistry는 아까 살펴본 AbstractHandlerMethodMapping의 내부 클래스다. MappingRegistryhandler method에 대한 모든 mapping을 유지 관리하고 lookup을 수행하는 method를 가지고 있고 동시성을 가진 접근을 가능하게 해주는 레지스트리다.

A registry that maintains all mappings to handler methods, exposing methods to perform lookups and providing concurrent access.
Package-private for testing purposes.

가장 중요한 부분이 handler method에 대한 모든 mapping을 유지 관리하고 lookup을 수행하는 method를 가지고 있다는 것이다.

class MappingRegistry {
  private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

  private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

  private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

  private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

  private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

  private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  ...
  ...
  
  // methods
}

MappingRegistry 안에서 Map 자료구조를 가진 멤버 변수들이 있다. 그 중에서LinkedMultiValueMap이라는 자료구조를 사용한다. 이건 한개의 key에 여러 value들을 저장하는 MultiValueMapLinkedHashMap으로 감싼 자료구조로 Spring이 만든 자료구조다.

urlLookupLinkedMultiValueMap의 자료구조인데, key는 url을 가지고, value는 RequestMappingInfo를 가진다. LinkedMultiValueMap을 쓰는 이유는 하나의 url에 여러 handlerMethod들에 대한 정보가 담기기 때문이다.

예를 들어 "/app/user"라는 url 아래 user에 대한 정보를 조회하는 GET,user를 추가하는 POST가 매핑될때, 아래처럼 RequestMappingInfo가 들어가는 것이다.

key : "/app/user/ 
value : [GET /app/user,POST /app/user]

위와 같은 구조를 통해 MappingRegistryurl에 해당하는 handlerMethod를 구별할 수 있게 된다. 코드로 보자.

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<>();
  List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
  if (directPathMatches != null) {
    addMatchingMappings(directPathMatches, matches, request);
  }
  ...
  ...
  if (!matches.isEmpty()) {
    ...
    ...
    matches.sort(comparator);
    Match bestMatch = matches.get(0);
    ...
    ...
    request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
    handleMatch(bestMatch.mapping, lookupPath, request);
    return bestMatch.handlerMethod;
  }

위는 아까 잠깐 언급한 lookupHandlerMethod이다. 적절한 handlerMethod를 가져온 후 return 한다 고 했는데 그 과정이 담겨있다.
길다고 겁먹지 말고 한줄씩 보자. (match되는 것이 없거나, 2개 이상인 경우는 제외함)

List<Match> matches = new ArrayList<>();

match를 담는 matches라는 리스트가 있다.

List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

현재 url에 mapping되는 handler method들의 RequestMappingInfo들을 getMappingsByUrl로 가져온 후 directPathMatches에 저장한다. 예를 들어 url/app/user이면 directPathMatches에는 [GET /app/user, POST /app/user] 와 같은 정보가 들어오는 것이다.

if (directPathMatches != null) {
    addMatchingMappings(directPathMatches, matches, request);
}

그 후 [GET /app/user, POST /app/user] 중에서 request 정보와 일치하는 것들을 addMatchingMappings을 통해서 matches에 추가한다.

matches.sort(comparator);
Match bestMatch = matches.get(0);

matches들을 우선순위에 맞게끔 정렬하고, request와 가장 일치하는 0번째 matchbestMatch에 저장한다.

request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;

bestMatch의 멤버인 handlerMethod를 return해서 최종적으로 적합한 handler method를 찾게 된다.

private class Match {

  private final T mapping;

  private final HandlerMethod handlerMethod;

}

Reflection

이제 마지막 궁금증만이 남았다.

출처

MappingReigstry javadoc
LinkedMultiValueMap javadoc

profile
becomeweasel.tistory.com로 이전

0개의 댓글