最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring MVC學習筆記之Controller查找(基于Spring4.0.3)

 更新時間:2018年03月12日 10:27:46   作者:芥末無疆sss  
這篇文章主要給大家介紹了關于Spring MVC學習筆記之Controller查找(基于Spring4.0.3)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。

0 摘要

本文從源碼層面簡單講解SpringMVC的處理器映射環(huán)節(jié),也就是查找Controller詳細過程

1 SpringMVC請求流程


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


SpringMVC詳細運行流程圖

2 SpringMVC初始化過程

2.1 先認識兩個類

1.RequestMappingInfo

封裝RequestMapping注解

包含HTTP請求頭的相關信息

一個實例對應一個RequestMapping注解

2.HandlerMethod

封裝Controller的處理請求方法

包含該方法所屬的bean對象、該方法對應的method對象、該方法的參數(shù)等

RequestMappingHandlerMapping的繼承關系

在SpringMVC初始化的時候

首先執(zhí)行RequestMappingHandlerMapping的afterPropertiesSet

然后進入AbstractHandlerMethodMapping的afterPropertiesSet

這個方法會進入該類的initHandlerMethods

負責從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數(shù)組
 for (String beanName : beanNames) {
 //isHandler會根據(jù)bean來判斷bean定義中是否帶有Controller注解或RequestMapping注解
 if (isHandler(getApplicationContext().getType(beanName))){
 detectHandlerMethods(beanName);
 }
 }
 handlerMethodsInitialized(getHandlerMethods());
}

RequestMappingHandlerMapping#isHandler

上圖方法即判斷當前bean定義是否帶有Controlller注解或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
 //根據(jù) method 定義是否帶有 RequestMapping 
 //若有則創(chuàng)建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注解來創(chuàng)建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;
 }

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

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
 //創(chuàng)建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下具體處理方法的封裝。先看方法的第一行,根據(jù)handler和mehthod創(chuàng)建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)中,靜接著把RequestMapping注解的value和ReuqestMappingInfo對象添加至urlMap中。

registerHandlerMethod方法簡單總結

該方法的主要有3個職責

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

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

3. 構建AbstractHandlerMethodMapping的成員變量urlMap,MultiValueMap。這個數(shù)據(jù)結構可以把它理解成Map>。其中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初始化的主要過程

查找過程

為了理解查找流程,帶著一個問題來看,現(xiàn)有如下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";
 }
}

有如下請求

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

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>();
 //根據(jù)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);
 }
}

進入lookupHandlerMethod方法,其中l(wèi)ookupPath="/LookupTest/test1",根據(jù)lookupPath,也就是請求的uri。直接查找urlMap,獲取直接匹配的RequestMappingInfo list。這里會匹配到3個RequestMappingInfo。如下

然后進入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能否匹配上,如果能匹配上,創(chuàng)建一個相同的RequestMappingInfo對象。再獲取RequestMappingInfo對應的handlerMethod。然后創(chuàng)建一個Match對象添加至matches list中。執(zhí)行完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有檢查配置的歧義性,這里為什么還會檢查一次。假如現(xiàn)在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";
}

現(xiàn)在執(zhí)行 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數(shù)。代碼如下

public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
 return other.methods.size() - this.methods.size();
}

什么時候匹配通配符?當通過urlMap獲取不到直接匹配value的RequestMappingInfo時才會走通配符匹配進入addMatchingMappings方法。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • SpringBoot中到底該如何解決跨域問題

    SpringBoot中到底該如何解決跨域問題

    跨域問題更是老生常談,隨便用標題去google或百度一下,能搜出一大片解決方案,這篇文章主要給大家介紹了關于SpringBoot中到底該如何解決跨域問題的相關資料,需要的朋友可以參考下
    2022-02-02
  • 詳解Lombok快速上手(安裝、使用與注解參數(shù))

    詳解Lombok快速上手(安裝、使用與注解參數(shù))

    這篇文章主要介紹了詳解Lombok快速上手(安裝、使用與注解參數(shù)) ,這里整理了一些日常編碼中能遇到的所有關于它的使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • Java語言十大基礎特性分析

    Java語言十大基礎特性分析

    這篇文章介紹了Java語言十大基礎特性,它有哪些優(yōu)勢,需要的朋友可以參考下。
    2017-08-08
  • 詳解Spring Boot Security工作流程

    詳解Spring Boot Security工作流程

    Spring Security,這是一種基于 Spring AOP 和 Servlet 。這篇文章主要介紹了Spring Boot Security的相關知識,需要的朋友可以參考下
    2019-04-04
  • Spring?Boot?整合持久層之MyBatis

    Spring?Boot?整合持久層之MyBatis

    在實際開發(fā)中不僅僅是要展示數(shù)據(jù),還要構成數(shù)據(jù)模型添加數(shù)據(jù),這篇文章主要介紹了SpringBoot集成Mybatis操作數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • Spring Boot 整合 JWT的方法

    Spring Boot 整合 JWT的方法

    這篇文章主要介紹了Spring Boot 整合 JWT的方法,文中實例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • Java回調機制解讀

    Java回調機制解讀

    本文主要介紹了Java回調機制的相關知識,具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • Java逃逸分析詳解及代碼示例

    Java逃逸分析詳解及代碼示例

    這篇文章主要介紹了Java逃逸分析詳解及代碼示例,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • Java SpringBoot攔截器詳解

    Java SpringBoot攔截器詳解

    這篇文章主要介紹了Java SpringBoot攔截器的使用方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2021-10-10
  • mybatisPlus實現(xiàn)邏輯刪除,自動生成創(chuàng)建時間和更新時間方式

    mybatisPlus實現(xiàn)邏輯刪除,自動生成創(chuàng)建時間和更新時間方式

    MyBatisPlus框架中,通過@TableField(fill=FieldFill.INSERT)和@TableField(fill=FieldFill.UPDATE)注解可以實現(xiàn)在插入和更新時自動填充字段,比如創(chuàng)建時間和更新時間,使用@TableLogic注解標識邏輯刪除字段
    2024-09-09

最新評論

抚顺市| 获嘉县| 哈密市| 抚宁县| 松原市| 宁远县| 黔南| 万全县| 东丰县| 涡阳县| 麻江县| 仁布县| 于田县| 巴青县| 莱西市| 安溪县| 集安市| 恩施市| 梨树县| 攀枝花市| 平远县| 睢宁县| 南漳县| 佛教| 泗阳县| 富宁县| 安国市| 邳州市| 泸溪县| 布尔津县| 许昌市| 南召县| 新龙县| 中超| 都兰县| 铁岭县| 龙海市| 扎赉特旗| 左权县| 昭苏县| 道真|