這篇文章給大家介紹SpringMVC中Controller的查找原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),源城企業(yè)網(wǎng)站建設(shè),源城品牌網(wǎng)站建設(shè),網(wǎng)站定制,源城網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,源城網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
Controller查找在上圖中對應(yīng)的步驟1至2的過程
RequestMappingInfo類,對RequestMapping注解封裝。里面包含http請求頭的相關(guān)信息。如uri、method、params、header等參數(shù)。一個(gè)對象對應(yīng)一個(gè)RequestMapping注解
HandlerMethod類,是對Controller的處理請求方法的封裝。里面包含了該方法所屬的bean對象、該方法對應(yīng)的method對象、該方法的參數(shù)等。
上圖是RequestMappingHandlerMapping的繼承關(guān)系。在SpringMVC初始化的時(shí)候,首先執(zhí)行RequestMappingHandlerMapping中的afterPropertiesSet方法,然后會進(jìn)入AbstractHandlerMethodMapping的afterPropertiesSet方法(line:93),這個(gè)方法會進(jìn)入當(dāng)前類的initHandlerMethods方法(line:103)。這個(gè)方法的職責(zé)便是從applicationContext中掃描beans,然后從bean中查找并注冊處理器方法,代碼如下。
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: ">
isHandler方法其實(shí)很簡單,如下
@Override protected boolean isHandler(Class> beanType) { return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) || (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null)); }
就是判斷當(dāng)前bean定義是否帶有Controlller注解或RequestMapping注解,看了這里邏輯可能會想如果只有RequestMapping會生效嗎?答案是不會的,因?yàn)樵谶@種情況下Spring初始化的時(shí)候不會把該類注冊為Spring bean,遍歷beanNames時(shí)不會遍歷到這個(gè)類,所以這里把Controller換成Compoent注解也是可以,不過一般不會這么做。當(dāng)確定bean為handlers后,便會從該bean中查找出具體的handler方法(也就是我們通常定義的Controller類下的具體定義的請求處理方法),查找代碼如下
protected void detectHandlerMethods(final Object handler) { //獲取到當(dāng)前Controller bean的class對象 Class> handlerType = (handler instanceof String) ? getApplicationContext().getType((String) handler) : handler.getClass(); //同上,也是該Controller bean的class對象 final Class> userType = ClassUtils.getUserClass(handlerType); //獲取當(dāng)前bean的所有handler method。這里查找的依據(jù)便是根據(jù)method定義是否帶有RequestMapping注解。如果有根據(jù)注解創(chuàng)建RequestMappingInfo對象 Setmethods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { public boolean matches(Method method) { return getMappingForMethod(method, userType) != null; } }); //遍歷并注冊當(dāng)前bean的所有handler method for (Method method : methods) { T mapping = getMappingForMethod(method, userType); //注冊handler method,進(jìn)入以下方法 registerHandlerMethod(handler, method, mapping); } }
以上代碼有兩個(gè)地方有調(diào)用了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); //合并兩個(gè)@RequestMapping注解 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); } } return info; }
這個(gè)方法的作用就是根據(jù)handler method方法創(chuàng)建RequestMappingInfo對象。首先判斷該mehtod是否含有RequestMpping注解。如果有則直接根據(jù)該注解的內(nèi)容創(chuàng)建RequestMappingInfo對象。創(chuàng)建以后判斷當(dāng)前method所屬的bean是否也含有RequestMapping注解。如果含有該注解則會根據(jù)該類上的注解創(chuàng)建一個(gè)RequestMappingInfo對象。然后在合并method上的RequestMappingInfo對象,最后返回合并后的對象?,F(xiàn)在回過去看detectHandlerMethods方法,有兩處調(diào)用了getMappingForMethod方法,個(gè)人覺得這里是可以優(yōu)化的,在第一處判斷method時(shí)否為handler時(shí),創(chuàng)建的RequestMappingInfo對象可以保存起來,直接拿來后面使用,就少了一次創(chuàng)建RequestMappingInfo對象的過程。然后緊接著進(jìn)入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中 Setpatterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this.urlMap.add(pattern, mapping); } } }
這里T的類型是RequestMappingInfo。這個(gè)對象就是封裝的具體Controller下的方法的RequestMapping注解的相關(guān)信息。一個(gè)RequestMapping注解對應(yīng)一個(gè)RequestMappingInfo對象。HandlerMethod和RequestMappingInfo類似,是對Controlelr下具體處理方法的封裝。先看方法的第一行,根據(jù)handler和mehthod創(chuàng)建HandlerMethod對象。第二行通過handlerMethods map來獲取當(dāng)前mapping對應(yīng)的HandlerMethod。然后判斷是否存在相同的RequestMapping配置。如下這種配置就會導(dǎo)致此處拋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配置是否有歧義,這是其中一處檢查歧義的(后面還會提到一個(gè)在運(yùn)行時(shí)檢查歧義性的地方)。然后確認(rèn)配置正常以后會把該RequestMappingInfo和HandlerMethod對象添加至handlerMethods(LinkedHashMap
registerHandlerMethod方法簡單總結(jié)
該方法的主要有3個(gè)職責(zé)
檢查RequestMapping注解配置是否有歧義。
構(gòu)建RequestMappingInfo到HandlerMethod的映射map。該map便是AbstractHandlerMethodMapping的成員變量handlerMethods。LinkedHashMap
構(gòu)建AbstractHandlerMethodMapping的成員變量urlMap,MultiValueMap
先有如下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"; } }
初始化完成后,對應(yīng)AbstractHandlerMethodMapping的urlMap的結(jié)構(gòu)如下
以上便是SpringMVC初始化的主要過程
為了理解查找流程,帶著一個(gè)問題來看,現(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"; } }
有如下請求
這個(gè)請求會進(jìn)入哪一個(gè)方法?
web容器(Tomcat、jetty)接收請求后,交給DispatcherServlet處理。FrameworkServlet調(diào)用對應(yīng)請求方法(eg:get調(diào)用doGet),然后調(diào)用processRequest方法。進(jìn)入processRequest方法后,一系列處理后,在line:936進(jìn)入doService方法。然后在Line856進(jìn)入doDispatch方法。在line:896獲取當(dāng)前請求的處理器handler。然后進(jìn)入AbstractHandlerMethodMapping的lookupHandlerMethod方法。代碼如下
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { Listmatches = new ArrayList (); //根據(jù)uri獲取直接匹配的RequestMappingInfos List 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對應(yīng)的HandlerMethod if (!matches.isEmpty()) { Comparator comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); if (logger.isTraceEnabled()) { logger.trace("Found ">
進(jìn)入lookupHandlerMethod方法,其中l(wèi)ookupPath="/LookupTest/test1",根據(jù)lookupPath,也就是請求的uri。直接查找urlMap,獲取直接匹配的RequestMappingInfo list。這里會匹配到3個(gè)RequestMappingInfo。如下
然后進(jìn)入addMatchingMappings方法
private void addMatchingMappings(Collectionmappings, List matches, HttpServletRequest request) { for (T mapping : mappings) { T match = getMatchingMapping(mapping, request); if (match != null) { matches.add(new Match(match, handlerMethods.get(mapping))); } } }
這個(gè)方法的職責(zé)是遍歷當(dāng)前請求的uri和mappings中的RequestMappingInfo能否匹配上,如果能匹配上,創(chuàng)建一個(gè)相同的RequestMappingInfo對象。再獲取RequestMappingInfo對應(yīng)的handlerMethod。然后創(chuàng)建一個(gè)Match對象添加至matches list中。執(zhí)行完addMatchingMappings方法,回到lookupHandlerMethod。這時(shí)候matches還有3個(gè)能匹配上的RequestMappingInfo對象。接下來的處理便是對matchers列表進(jìn)行排序,然后獲取列表的第一個(gè)元素作為最佳匹配。返回Match的HandlerMethod。這里進(jìn)入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更能先匹配。所以那個(gè)請求會進(jìn)入test3()方法。再回到lookupHandlerMethod,在找到HandlerMethod。SpringMVC還會這里再一次檢查配置的歧義性,這里檢查的原理是通過比較匹配度最高的兩個(gè)RequestMappingInfo進(jìn)行比較。此處可能會有疑問在初始化SpringMVC有檢查配置的歧義性,這里為什么還會檢查一次。假如現(xiàn)在Controller中有如下兩個(gè)方法,以下配置是能通過初始化歧義性檢查的。
@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'
異常。這里拋該異常是因?yàn)镽equestMethodsRequestCondition的compareTo方法是比較的method數(shù)。代碼如下
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) { return other.methods.size() - this.methods.size(); }
什么時(shí)候匹配通配符?當(dāng)通過urlMap獲取不到直接匹配value的RequestMappingInfo時(shí)才會走通配符匹配進(jìn)入addMatchingMappings方法。
關(guān)于SpringMVC中Controller的查找原理是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。