Spring MVC 학습 노트 의 Controller 찾기(Spring 4.0.3 기반)

요약
본 고 는 소스 코드 차원 에서 SpringMVC 의 프로세서 맵 절 차 를 간단하게 설명 하고 자 한다.즉,Controller 의 상세 한 과정 을 찾 는 것 이다.
1 SpringMVC 요청 절차

Controller 는 위의 그림 에서 대응 하 는 절차 1~2 의 과정 을 찾 습 니 다.

SpringMVC 상세 운행 흐름 도
2 SpringMVC 초기 화 과정
2.1 두 가지 유형 을 먼저 알 아야 한다.
1.RequestMappingInfo
패키지 RequestMapping 주석
HTTP 요청 헤더 에 대한 정보 포함
하나의 인 스 턴 스 가 RequestMapping 주석 에 대응 합 니 다.
2.HandlerMethod
패키지 컨트롤 러 의 처리 요청 방법
이 방법 에 속 하 는 bean 대상,이 방법 에 대응 하 는 method 대상,이 방법의 매개 변수 등 을 포함 합 니 다.

Request Mapping Handler Mapping 의 계승 관계
SpringMVC 초기 화 할 때
우선 Request Mapping Handler Mapping 의 after PropertiesSet 을 실행 합 니 다.
그리고 AbstractHandler MethodMapping 의 after PropertiesSet 에 들 어 갑 니 다.
이 방법 은 이러한 종류의 initHandler Methods 에 들 어 갑 니 다.
applicationContext 에서 beans 를 스 캔 한 다음 bean 에서 프로세서 방법 을 찾 아 등록 합 니 다.

//Scan beans in the ApplicationContext, detect and register handler methods.
protected void initHandlerMethods() {
 ...
 //  applicationContext    bean name
 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
 getApplicationContext().getBeanNamesForType(Object.class));
 
 //  beanName  
 for (String beanName : beanNames) {
 //isHandler   bean   bean       Controller   RequestMapping  
 if (isHandler(getApplicationContext().getType(beanName))){
 detectHandlerMethods(beanName);
 }
 }
 handlerMethodsInitialized(getHandlerMethods());
}

RequestMappingHandlerMapping#isHandler
위의 그림 방법 은 현재 bean 정의 에 Controller 주석 이나 RequestMapping 주석 이 있 는 지 판단 하 는 것 입 니 다.
RequestMapping 만 유효 합 니까?아니 야!
이러한 상황 에서 Spring 초기 화 할 때 이 종 류 를 Spring bean 으로 등록 하지 않 고,beanNames 를 옮 겨 다 닐 때 이 종 류 를 옮 겨 다 니 지 않 기 때문에 여기 서 Controller 를 Compoent 로 바 꿔 도 되 지만,일반적으로 이렇게 하지 않 습 니 다.
bean 을 handler 로 확정 하면 이 bean 에서 구체 적 인 handler 방법(즉,Controller 류 에서 구체 적 으로 정 의 된 요청 처리 방법)을 찾 습 니 다.코드 를 찾 으 면 다음 과 같 습 니 다.

 /**
 * Look for handler methods in a handler
 * @param handler the bean name of a handler or a handler instance
 */
protected void detectHandlerMethods(final Object handler) {
 //    Controller bean class  
 Class<?> handlerType = (handler instanceof String) ?
 getApplicationContext().getType((String) handler) : handler.getClass();
 //       getMappingForMethod     RequestMappingInfo   
 final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
 //  ,   Controller bean class  
 final Class<?> userType = ClassUtils.getUserClass(handlerType); 
 //    bean   handler method
 //   method        RequestMapping 
 //     RequestMappingInfo  
 Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
  @Override
  public boolean matches(Method method) {
  T mapping = getMappingForMethod(method, userType);
  if (mapping != null) {
   mappings.put(method, mapping);
   return true;
  }
  else {
   return false;
  }
  }
 });

 //       bean   handler method
 for (Method method : methods) {
  //  handler method,      
  registerHandlerMethod(handler, method, mappings.get(method));
 }
상기 코드 는 두 곳 에서 getMappingForMethod 를 호출 하 였 습 니 다.
RequestMapping 주 해 를 사용 하여 RequestMappingInfo 를 만 듭 니 다.

 @Override
 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
 RequestMappingInfo info = null;
 //  method @RequestMapping
 RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
 if (methodAnnotation != null) {
  RequestCondition<?> methodCondition = getCustomMethodCondition(method);
  info = createRequestMappingInfo(methodAnnotation, methodCondition);
  //  method  bean @RequtestMapping  
  RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
  if (typeAnnotation != null) {
  RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
  //    @RequestMapping  
  info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
  }
 }
 return info;
 }
이 방법의 역할 은 handler method 방법 에 따라 Request MappingInfo 대상 을 만 드 는 것 이다.우선 이 mehtod 에 RequestMpping 주석 이 있 는 지 판단 합 니 다.있 으 면 이 주해 의 내용 에 따라 Request MappingInfo 대상 을 직접 만 듭 니 다.생 성 후 현재 method 에 속 한 bean 에 도 RequestMapping 주석 이 있 는 지 판단 합 니 다.이 주석 이 포함 되 어 있 으 면 이 클래스 의 주석 에 따라 RequestMappingInfo 대상 을 만 듭 니 다.그리고 method 에 있 는 RequestMappingInfo 대상 을 합 쳐 합 친 대상 을 되 돌려 줍 니 다.이제 와 서 detectHandlerMethods 방법 을 보면 getMappingForMethod 방법 이 두 군데 호출 되 었 습 니 다.개인 적 으로 여기 가 최적화 될 수 있다 고 생각 합 니 다.첫 번 째 로 method 가 handler 인지 아 닌 지 를 판단 할 때 만 든 RequestMappingInfo 대상 을 저장 할 수 있 습 니 다.직접 가 져 와 서 사용 하면 RequestMappingInfo 대상 을 만 드 는 과정 이 한 번 줄 어 듭 니 다.그리고 이어서 registerHandler Mehtod 에 들 어 가 는 방법 은 다음 과 같다.

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
 //  HandlerMethod
 HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
 HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
 //           
 if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
  throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean()
   + "' bean method 
" + newHandlerMethod + "
to " + mapping + ": There is already '" + oldHandlerMethod.getBean() + "' bean method
" + oldHandlerMethod + " mapped."); } this.handlerMethods.put(mapping, newHandlerMethod); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod); } // @RequestMapping value, value->RequestMappingInfo urlMap Set<String> patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this.urlMap.add(pattern, mapping); } } }
여기 T 타 입 은 Request MappingInfo 입 니 다.이 대상 은 포 장 된 구체 적 인 컨트롤 러 아래 방법의 RequestMapping 주해 에 관 한 정보 입 니 다.RequestMapping 주 해 는 RequestMappingInfo 대상 에 대응 합 니 다.Handler Method 는 Request MappingInfo 와 유사 하 며,Controlelr 의 구체 적 인 처리 방법 에 대한 패키지 입 니 다.방법의 첫 줄 을 보고 handler 와 mehthod 에 따라 HandlerMethod 대상 을 만 듭 니 다.두 번 째 줄 은 handlerMethods map 를 통 해 현재 mapping 에 대응 하 는 HandlerMethod 를 가 져 옵 니 다.그리고 같은 RequestMapping 설정 이 있 는 지 판단 합 니 다.아래 와 같은 설정 은 이곳 에 던 질 수 있 습 니 다.Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map...이상 하 다

@Controller
@RequestMapping("/AmbiguousTest")
public class AmbiguousTestController {
 @RequestMapping(value = "/test1")
 @ResponseBody
 public String test1(){
  return "method test1";
 }
 @RequestMapping(value = "/test1")
 @ResponseBody
 public String test2(){
  return "method test2";
 }
}
SpingMVC 시작(초기 화)단계 에서 RequestMapping 설정 에 잘못된 의미 가 있 는 지 확인 합 니 다.이것 은 잘못된 의 미 를 검사 하 는 곳 입 니 다.(다음 에 실행 할 때 잘못된 의 미 를 검사 하 는 곳 도 언급 됩 니 다)그리고 설정 이 정상 인 지 를 확인 한 후 이 Request MappingInfo 와 HandlerMethod 대상 을 handlerMethods(LinkedHashMap)에 추가 하고,이어서 Request Mapping 주해 의 value 와 Reuqest MappingInfo 대상 을 url Map 에 추가 합 니 다.
registerHandlerMethod 방법 간단하게 요약
이 방법 은 주로 세 가지 직책 이 있다.
1.RequestMapping 주석 설정 에 잘못된 의미 가 있 는 지 확인 합 니 다.
2.RequestMappingInfo 를 HandlerMethod 에 매 핑 맵 을 구축 합 니 다.이 맵 은 AbstractHandler MethodMapping 의 구성원 변수 handlerMethods 입 니 다.LinkedHashMap。
3.AbstractHandler MethodMapping 의 구성원 변수 url Map,MultiValueMap 을 구축 합 니 다.이 데이터 구 조 는 그것 을 지도>로 이해 할 수 있다.그 중에서 String 형식의 key 는 처리 방법 에 RequestMapping 주해 의 value 를 저장 합 니 다.구체 적
먼저 다음 컨트롤 러 가 있 습 니 다.

@Controller
@RequestMapping("/UrlMap")
public class UrlMapController {
 @RequestMapping(value = "/test1", method = RequestMethod.GET)
 @ResponseBody
 public String test1(){
  return "method test1";
 }

 @RequestMapping(value = "/test1")
 @ResponseBody
 public String test2(){
  return "method test2";
 }

 @RequestMapping(value = "/test3")
 @ResponseBody
 public String test3(){
  return "method test3";
 }
}
초기 화 완료 후 AbstractHandler MethodMapping 에 대응 하 는 url Map 의 구 조 는 다음 과 같 습 니 다.

이상 이 바로 SpringMVC 초기 화의 주요 과정 입 니 다.
검색 프로 세 스
검색 절 차 를 이해 하기 위해 문 제 를 가지 고 보면 다음 과 같은 컨트롤 러 가 있 습 니 다.

@Controller
@RequestMapping("/LookupTest")
public class LookupTestController {

 @RequestMapping(value = "/test1", method = RequestMethod.GET)
 @ResponseBody
 public String test1(){
  return "method test1";
 }

 @RequestMapping(value = "/test1", headers = "Referer=https://www.baidu.com")
 @ResponseBody
 public String test2(){
  return "method test2";
 }

 @RequestMapping(value = "/test1", params = "id=1")
 @ResponseBody
 public String test3(){
  return "method test3";
 }

 @RequestMapping(value = "/*")
 @ResponseBody
 public String test4(){
  return "method test4";
 }
}
다음 과 같은 요청 이 있 습 니 다.

이 요청 은 어떤 방법 으로 들 어 갈 까요?
웹 용기(Tomcat,Jetty)가 요청 을 받 은 후 Dispatcher Servlet 에 맡 깁 니 다.Framework Servlet 에서 대응 하 는 요청 방법(eg:get 호출 doGet)을 호출 한 다음 processRequest 방법 을 호출 합 니 다.processRequest 방법 에 들 어간 후 일련의 처리 후 line:936 에서 doService 방법 에 들 어 갑 니 다.그리고 Line 856 에서 doDispatch 에 들 어 가 는 방법.line:896 에서 현재 요청 한 프로세서 handler 를 가 져 옵 니 다.그리고 AbstractHandler MethodMapping 의 lookup Handler Method 방법 에 들 어 갑 니 다.코드 는 다음 과 같다.

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
 List<Match> matches = new ArrayList<Match>();
 //  uri       RequestMappingInfos
 List<T> directPathMatches = this.urlMap.get(lookupPath);
 if (directPathMatches != null) {
  addMatchingMappings(directPathMatches, matches, request);
 }
 //        RequetMappingInfo,    RequestMappingInfo
 if (matches.isEmpty()) {
  // No choice but to go through all mappings
  addMatchingMappings(this.handlerMethods.keySet(), matches, request);
 }
 //       RequestMappingInfo   HandlerMethod
 if (!matches.isEmpty()) {
  Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  Collections.sort(matches, comparator);

  if (logger.isTraceEnabled()) {
  logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
  }
  //           
  Match bestMatch = matches.get(0);
  if (matches.size() > 1) {
  Match secondBestMatch = matches.get(1);
  if (comparator.compare(bestMatch, secondBestMatch) == 0) {
   Method m1 = bestMatch.handlerMethod.getMethod();
   Method m2 = secondBestMatch.handlerMethod.getMethod();
   throw new IllegalStateException(
     "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
     m1 + ", " + m2 + "}");
  }
  }

  handleMatch(bestMatch.mapping, lookupPath, request);
  return bestMatch.handlerMethod;
 }
 else {
  return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
 }
}
lookupHandler Method 에 들 어 가 는 방법 입 니 다.그 중에서 lookupPath="/LookupTest/test 1"은 lookupPath 에 따라 요청 한 uri 입 니 다.url Map 을 직접 찾 아 직접 일치 하 는 Request MappingInfo list 를 가 져 옵 니 다.Request MappingInfo 3 개 와 일치 합 니 다.아래 와 같다

그리고 addMatchingMappings 방법 에 들 어 갑 니 다.

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
 for (T mapping : mappings) {
  T match = getMatchingMapping(mapping, request);
  if (match != null) {
  matches.add(new Match(match, handlerMethods.get(mapping)));
  }
 }
}
이 방법 은 현재 요청 한 uri 와 mappings 의 RequestMappingInfo 가 일치 할 수 있 는 지,일치 할 수 있다 면 같은 RequestMappingInfo 대상 을 만 드 는 것 입 니 다.RequestMappingInfo 에 대응 하 는 handlerMethod 를 가 져 옵 니 다.그리고 match 대상 을 만 들 고 matches list 에 추가 합 니 다.addMatchingMappings 방법 을 실행 하고 lookup Handler Method 로 돌아 갑 니 다.이때 matches 에는 Request MappingInfo 대상 과 일치 하 는 3 개가 있 습 니 다.다음 처 리 는 matchers 목록 을 정렬 한 다음 목록 의 첫 번 째 요 소 를 가 져 오 는 것 입 니 다.Match 의 HandlerMethod 를 되 돌려 줍 니 다.Request MappingInfo 의 compare To 방법 에 들 어가 서 구체 적 인 정렬 논 리 를 살 펴 보 겠 습 니 다.코드 는 다음 과 같다.

public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
 int result = patternsCondition.compareTo(other.getPatternsCondition(), request);
 if (result != 0) {
  return result;
 }
 result = paramsCondition.compareTo(other.getParamsCondition(), request);
 if (result != 0) {
  return result;
 }
 result = headersCondition.compareTo(other.getHeadersCondition(), request);
 if (result != 0) {
  return result;
 }
 result = consumesCondition.compareTo(other.getConsumesCondition(), request);
 if (result != 0) {
  return result;
 }
 result = producesCondition.compareTo(other.getProducesCondition(), request);
 if (result != 0) {
  return result;
 }
 result = methodsCondition.compareTo(other.getMethodsCondition(), request);
 if (result != 0) {
  return result;
 }
 result = customConditionHolder.compareTo(other.customConditionHolder, request);
 if (result != 0) {
  return result;
 }
 return 0;
}
코드 에서 알 수 있 듯 이 일치 하 는 우선 순 서 는 value>params>headers>consumes>produces>methods>custom 입 니 다.여기 서 앞의 문 제 를 보면 쉽게 답 을 얻 을 수 있 습 니 다.value 와 같은 상황 에서 params 가 먼저 일치 합 니 다.그래서 그 요청 은 test 3()방법 으로 들 어 갑 니 다.lookup Handler Method 로 돌아 가 Handler Method 를 찾 고 있 습 니 다.SpringMVC 는 또 이곳 에서 설정 의 유의 성 을 다시 한 번 검사 할 것 이다.여기 서 검사 하 는 원 리 는 일치 도가 가장 높 은 두 개의 RequestMappingInfo 를 비교 하 는 것 이다.SpringMVC 를 초기 화 하 는 데 설정 을 검사 하 는 오류 가 있 을 수 있 습 니 다.여기 서 왜 한 번 더 검 사 를 합 니까?현재 Controller 에 다음 과 같은 두 가지 방법 이 있다 면 다음 설정 은 잘못된 의 미 를 초기 화 할 수 있 습 니 다.

@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String test5(){
 return "method test5";
}
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.DELETE})
@ResponseBody
public String test6(){
 return "method test6";
}
현재 실행http://localhost:8080/SpringMVC-demo/LookuptTest/test 5 요청 은 lookup Handler Method 방법 에서 던 집 니 다.
이상이 이상 을 던 진 것 은 RequestMethods RequestCondition 의 compare To 방법 이 비교 methods 수 이기 때 문 입 니 다.코드 는 다음 과 같다.

public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
 return other.methods.size() - this.methods.size();
}
마스크 는 언제 일치 합 니까?url Map 을 통 해 value 와 직접 일치 하 는 RequestMappingInfo 를 얻 지 못 할 때 만 어댑터 가 addMatchingMappings 방법 에 들 어 갑 니 다.
총결산
이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기