SpringMVC中Controller的查找原理是什么

這篇文章給大家介紹SpringMVC中Controller的查找原理是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創新互聯是一家集網站建設,源城企業網站建設,源城品牌網站建設,網站定制,源城網站建設報價,網絡營銷,網絡優化,源城網站推廣為一體的創新建站企業,幫助傳統企業提升企業形象加強企業競爭力??沙浞譂M足這一群體相比中小企業更為豐富、高端、多元的互聯網需求。同時我們時刻保持專業、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們為更多的企業打造出實用型網站。

SpringMVC請求流程

SpringMVC中Controller的查找原理是什么

  • Controller查找在上圖中對應的步驟1至2的過程

SpringMVC初始化過程

理解初始化過程之前,先認識兩個類
  1. RequestMappingInfo類,對RequestMapping注解封裝。里面包含http請求頭的相關信息。如uri、method、params、header等參數。一個對象對應一個RequestMapping注解

  2. HandlerMethod類,是對Controller的處理請求方法的封裝。里面包含了該方法所屬的bean對象、該方法對應的method對象、該方法的參數等。
    SpringMVC中Controller的查找原理是什么

  • 上圖是RequestMappingHandlerMapping的繼承關系。在SpringMVC初始化的時候,首先執行RequestMappingHandlerMapping中的afterPropertiesSet方法,然后會進入AbstractHandlerMethodMapping的afterPropertiesSet方法(line:93),這個方法會進入當前類的initHandlerMethods方法(line:103)。這個方法的職責便是從applicationContext中掃描beans,然后從bean中查找并注冊處理器方法,代碼如下。

protected void initHandlerMethods() {
  if (logger.isDebugEnabled()) {
      logger.debug("Looking for request mappings in application context: ">
  • isHandler方法其實很簡單,如下

@Override
protected boolean isHandler(Class<?> beanType) {
  return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
        (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
  • 就是判斷當前bean定義是否帶有Controlller注解或RequestMapping注解,看了這里邏輯可能會想如果只有RequestMapping會生效嗎?答案是不會的,因為在這種情況下Spring初始化的時候不會把該類注冊為Spring bean,遍歷beanNames時不會遍歷到這個類,所以這里把Controller換成Compoent注解也是可以,不過一般不會這么做。當確定bean為handlers后,便會從該bean中查找出具體的handler方法(也就是我們通常定義的Controller類下的具體定義的請求處理方法),查找代碼如下

protected void detectHandlerMethods(final Object handler) {
  //獲取到當前Controller bean的class對象
  Class<?> handlerType = (handler instanceof String) ?
        getApplicationContext().getType((String) handler) : handler.getClass();
  //同上,也是該Controller bean的class對象
  final Class<?> userType = ClassUtils.getUserClass(handlerType);
  //獲取當前bean的所有handler method。這里查找的依據便是根據method定義是否帶有RequestMapping注解。如果有根據注解創建RequestMappingInfo對象
  Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
      public boolean matches(Method method) {
        return getMappingForMethod(method, userType) != null;
      }
  });
  //遍歷并注冊當前bean的所有handler method
  for (Method method : methods) {
      T mapping = getMappingForMethod(method, userType);
      //注冊handler method,進入以下方法
      registerHandlerMethod(handler, method, mapping);
  }
}
  • 以上代碼有兩個地方有調用了getMappingForMethod方法

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方法創建RequestMappingInfo對象。首先判斷該mehtod是否含有RequestMpping注解。如果有則直接根據該注解的內容創建RequestMappingInfo對象。創建以后判斷當前method所屬的bean是否也含有RequestMapping注解。如果含有該注解則會根據該類上的注解創建一個RequestMappingInfo對象。然后在合并method上的RequestMappingInfo對象,最后返回合并后的對象?,F在回過去看detectHandlerMethods方法,有兩處調用了getMappingForMethod方法,個人覺得這里是可以優化的,在第一處判斷method時否為handler時,創建的RequestMappingInfo對象可以保存起來,直接拿來后面使用,就少了一次創建RequestMappingInfo對象的過程。然后緊接著進入registerHandlerMehtod方法,如下

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 \n" + newHandlerMethod + "\nto " + mapping + ": There is already '"
            + oldHandlerMethod.getBean() + "' bean method\n" + 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的類型是RequestMappingInfo。這個對象就是封裝的具體Controller下的方法的RequestMapping注解的相關信息。一個RequestMapping注解對應一個RequestMappingInfo對象。HandlerMethod和RequestMappingInfo類似,是對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配置是否有歧義,這是其中一處檢查歧義的(后面還會提到一個在運行時檢查歧義性的地方)。然后確認配置正常以后會把該RequestMappingInfo和HandlerMethod對象添加至handlerMethods(LinkedHashMap<RequestMappingInfo,HandlerMethod>)中,靜接著把RequestMapping注解的value和ReuqestMappingInfo對象添加至urlMap中。

registerHandlerMethod方法簡單總結

該方法的主要有3個職責

  1. 檢查RequestMapping注解配置是否有歧義。

  2. 構建RequestMappingInfo到HandlerMethod的映射map。該map便是AbstractHandlerMethodMapping的成員變量handlerMethods。LinkedHashMap<RequestMappingInfo,HandlerMethod>。

  3. 構建AbstractHandlerMethodMapping的成員變量urlMap,MultiValueMap<String,RequestMappingInfo>。這個數據結構可以把它理解成Map<String,List>。其中String類型的key存放的是處理方法上RequestMapping注解的value。就是具體的uri
    先有如下Controller

@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";
    }
}
  • 初始化完成后,對應AbstractHandlerMethodMapping的urlMap的結構如下
    SpringMVC中Controller的查找原理是什么

  • 以上便是SpringMVC初始化的主要過程

    查找過程

  • 為了理解查找流程,帶著一個問題來看,現有如下Controller

@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";
    }
}
  • 有如下請求
    SpringMVC中Controller的查找原理是什么

  • 這個請求會進入哪一個方法?

  • web容器(Tomcat、jetty)接收請求后,交給DispatcherServlet處理。FrameworkServlet調用對應請求方法(eg:get調用doGet),然后調用processRequest方法。進入processRequest方法后,一系列處理后,在line:936進入doService方法。然后在Line856進入doDispatch方法。在line:896獲取當前請求的處理器handler。然后進入AbstractHandlerMethodMapping的lookupHandlerMethod方法。代碼如下

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 ">
  • 進入lookupHandlerMethod方法,其中lookupPath="/LookupTest/test1",根據lookupPath,也就是請求的uri。直接查找urlMap,獲取直接匹配的RequestMappingInfo list。這里會匹配到3個RequestMappingInfo。如下
    SpringMVC中Controller的查找原理是什么

  • 然后進入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方法,回到lookupHandlerMethod。這時候matches還有3個能匹配上的RequestMappingInfo對象。接下來的處理便是對matchers列表進行排序,然后獲取列表的第一個元素作為最佳匹配。返回Match的HandlerMethod。這里進入RequestMappingInfo的compareTo方法,看一下具體的排序邏輯。代碼如下

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更能先匹配。所以那個請求會進入test3()方法。再回到lookupHandlerMethod,在找到HandlerMethod。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/LookupTest/test5 請求,便會在lookupHandlerMethod方法中拋
    java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/SpringMVC-Demo/LookupTest/test5'異常。這里拋該異常是因為RequestMethodsRequestCondition的compareTo方法是比較的method數。代碼如下

public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
  return other.methods.size() - this.methods.size();
}
  • 什么時候匹配通配符?當通過urlMap獲取不到直接匹配value的RequestMappingInfo時才會走通配符匹配進入addMatchingMappings方法。

關于SpringMVC中Controller的查找原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

分享名稱:SpringMVC中Controller的查找原理是什么
文章起源:http://m.kartarina.com/article8/pgosop.html

成都網站建設公司_創新互聯,為您提供全網營銷推廣、手機網站建設虛擬主機、網站維護、靜態網站、網站收錄

廣告

聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯

網站建設網站維護公司
主站蜘蛛池模板: 亚洲av无码专区国产乱码在线观看| 日本精品无码一区二区三区久久久| 精品无码久久久久久久久水蜜桃 | 成人av片无码免费天天看| 亚洲国产精品无码观看久久| 亚洲AV无码久久久久网站蜜桃| 日韩精品人妻系列无码av东京 | 亚洲真人无码永久在线观看| 永久免费无码网站在线观看| 亚洲精品无码mv在线观看网站| 亚洲乱亚洲乱妇无码| av无码人妻一区二区三区牛牛| 西西大胆无码视频免费| 国产网红无码精品视频| 国产精品亚洲专区无码唯爱网| 人妻丝袜中文无码av影音先锋专区| 无码人妻精品一二三区免费| 国产午夜无码片在线观看影院| 精品亚洲AV无码一区二区| 18禁超污无遮挡无码免费网站国产| 亚洲AV无码一区二区三区人| 国产成人无码免费网站| 成人免费无码大片A毛片抽搐| 亚洲av无码片在线播放| 色综合热无码热国产| 亚洲色无码国产精品网站可下载| 国产精品无码a∨精品| 人妻精品久久无码区| 亚洲综合无码无在线观看| 无码人妻丰满熟妇区免费 | 亚洲精品无码不卡在线播放| 亚洲av无码天堂一区二区三区| 精品无码一区二区三区水蜜桃| 精品无码免费专区毛片| 成人无码视频97免费| 日韩精品无码久久一区二区三| 无码少妇丰满熟妇一区二区| 亚洲精品无码你懂的| 亚洲AV无码成人精品区日韩 | 极品无码国模国产在线观看| 亚洲精品无码久久久久久久|