SpringMvc RequestMappingHandlerMapping
RequestMappingHandlerMapping是SpringMvc中一個比較核心的類,檢視下它的類結構圖:
InitializingBean是個很神奇的介面,在Spring每個容器的bean構造方法、屬性設定之後,會先呼叫InitializingBean介面的afterPropertiesSet方法;
RequestMappingHandlerMapping的afterPropertiesSet方法: 初始化了config物件,以及呼叫父類AbstractHandlerMethodMapping的afterPropertiesSet,父類方法afterPropertiesSet 邏輯是 initHandlerMethods,這也是SpringMvc初始化尋找Controller以及對映載入的核心邏輯;
@Override public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager()); // 初始化config物件,主要屬性就是pathMatcher; 以及呼叫父類 afterPropertiesSet 方法,這是SpringMvc對映關係載入的核心; super.afterPropertiesSet(); }
AbstractHandlerMethodMapping 的 initHandlerMethods程式碼:
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); // detectHandlerMethodsInAncestorContexts 預設為false,代表不會檢測SpringMvc父容器中的bean的對映關係 for (String beanName : beanNames) { //遍歷容器中的beanName, 代理的物件跳過,獲取當前bean的型別,呼叫isHandler判斷是否是處理器(handler\controller) if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = getApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { //isHandler方法判斷是否是controller,判斷邏輯下面有; detectHandlerMethods(beanName); //載入Controller和請求對映關係 } } } handlerMethodsInitialized(getHandlerMethods()); // 該方法是個空實現 }
isHandler方法: 判斷當前bean的class屬性,標註了Controller或者RequestMapping註解,就會去載入Controller和請求對映關係,如果不是handler,迭代下一個bean物件;
protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
detectHandlerMethods方法:
protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());//之前傳入handler為string型別,此處去容器獲取handler的class final Class<?> userType = ClassUtils.getUserClass(handlerType); //處理class為CGLIB生成class,如果是CGLIB的獲取父類class Map<Method, T> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<T>() { @Override public T inspect(Method method) { return getMappingForMethod(method, userType); } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } for (Map.Entry<Method, T> entry : methods.entrySet()) { Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); T mapping = entry.getValue(); registerHandlerMethod(handler, invocableMethod, mapping); } }
MethodIntrospector.selectMethods(..)方法是個很全面的解析方法:註釋寫得很詳細,☆方法處,metadataLookup.inspect方法,往上看,呼叫的就是getMappingForMethod方法獲取RequestMappingInfo物件;
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { final Map<Method, T> methodMap = new LinkedHashMap<Method, T>(); Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>(); Class<?> specificHandlerType = null; if (!Proxy.isProxyClass(targetType)) { //handlerclass不是JDK代理生成的,加入到handlerTypes集合,specificHandlerType為當前handler class handlerTypes.add(targetType); specificHandlerType = targetType; } handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));/ /handler class實現的介面加入到handlerTypes for (Class<?> currentHandlerType : handlerTypes) { final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { //該工具類方法,遍歷了該currentHandlerType本類中所有的方法 // 呼叫的是getDeclaredMethods(),然後遍歷method陣列,呼叫doWith回撥處理method方法 public void doWith(Method method) { Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); T result = metadataLookup.inspect(specificMethod); // ☆ 核心!!! 這裡處理了方法以及類上的對映關係,並且返回泛型T,實際型別是RequesMappingInfo if (result != null) { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { methodMap.put(specificMethod, result); } } } }, ReflectionUtils.USER_DECLARED_METHODS); // ReflectionUtils.USER_DECLARED_METHODS是個methodFilter,作用是過濾方法是使用者定義、且非橋接型別的方法; } return methodMap; }
RequestMappingHandlerMapping 的 getMappingForMethod 方法:先分析方法上的對映關係,再分析類所在方法上的對映關係,然後結合處理;
下面一點點記錄我檢視這個方法的發現;
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); // 解析類上RequestMapping註解 if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);// 解析方法上@RequestMapping註解 if (typeInfo != null) { info = typeInfo.combine(info); //方法上RequestMapping註解不為空,就需要結合分析 } } return info; }
createRequestMappingInfo 方法:
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { //呼叫Spring註解工具類AnnotatedElementUtils獲取方法上註解 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<?> condition = (element instanceof Class<?> ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); //RequestMappingHandlerMapping兩個方法都是返回null,空實現 return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }
具體的RequestMappingInfo的構造採用建造者模式還是其他模式的?
protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, RequestCondition<?> customCondition) {//customCondition一般都為null return RequestMappingInfo .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))// @RequestMapping(path={....}) 將path屬性設定上去 .methods(requestMapping.method()) // @RequestMapping(method={....}) 將method屬性設定上去 .params(requestMapping.params()) // @RequestMapping(method={....}) 將method屬性設定上去 .headers(requestMapping.headers()) // @RequestMapping(headers={....}) 將headers屬性設定上去 .consumes(requestMapping.consumes()) // @RequestMapping(consumes={....}) 將consumes屬性設定上去 .produces(requestMapping.produces()) // @RequestMapping(produces={....}) 將produces屬性設定上去 .mappingName(requestMapping.name()) // @RequestMapping(name={....}) 將name屬性設定上去 .customCondition(customCondition) .options(this.config) .build(); }
這裡只分析一個開頭、一個結尾這樣;
RequestMappingInfo 的 paths 方法:
public static Builder paths(String... paths) { // paths是@RequestMapping的path屬性,字串陣列,這裡用可變引數來接收,效果一樣 return new DefaultBuilder(paths); }
Builder介面所有方法都返回Builder物件,DefaultBuilder持有一堆屬性,可以看到都是@ReuqestMapping的屬性;
paths方法就將註解的path屬性注入到DefaultBuilder中,其他方法methods、params、headers、consumes、produces、mappingName、customCondition都是這個套路;
而 options注入的config屬性 ,最開始 afterPropertiesSet 裡 ,this.config = new RequestMappingInfo.BuilderConfiguration();
就是將RequestMappingHandleMapping中的config作為DefaultBuilder的options注入; 最後就是build方法。
DefaultBuilder 的 build方法:
public RequestMappingInfo build() { ContentNegotiationManager manager = this.options.getContentNegotiationManager(); // PatternsRequestCondition構造的主要屬性就是paths,代表了對映的路徑,不以/開頭會新增 / 這個開頭 PatternsRequestCondition patternsCondition = new PatternsRequestCondition( this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(), this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(), this.options.getFileExtensions()); return new RequestMappingInfo(this.mappingName, patternsCondition, new RequestMethodsRequestCondition(methods), new ParamsRequestCondition(this.params), new HeadersRequestCondition(this.headers), new ConsumesRequestCondition(this.consumes, this.headers), new ProducesRequestCondition(this.produces, this.headers, manager), this.customCondition);// customCondition通常為null }
build方法返回 RequestMappingInfo,其中構造入參都是XXXRequestCondition這種,他們都實現了RequestCondition介面;
private PatternsRequestCondition(Collection<String> patterns, UrlPathHelper urlPathHelper, PathMatcher pathMatcher, boolean useSuffixPatternMatch, boolean useTrailingSlashMatch, List<String> fileExtensions) { //這裡就是 prependLeadingSlash 會判斷 @RequestMapping註解的 path屬性,不是以 /開頭會新增 / this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns)); this.pathHelper = (urlPathHelper != null ? urlPathHelper : new UrlPathHelper()); this.pathMatcher = (pathMatcher != null ? pathMatcher : new AntPathMatcher()); this.useSuffixPatternMatch = useSuffixPatternMatch; this.useTrailingSlashMatch = useTrailingSlashMatch; if (fileExtensions != null) { for (String fileExtension : fileExtensions) { if (fileExtension.charAt(0) != '.') { fileExtension = "." + fileExtension; } this.fileExtensions.add(fileExtension); } } }
private static Set<String> prependLeadingSlash(Collection<String> patterns) { if (patterns == null) { return Collections.emptySet(); } Set<String> result = new LinkedHashSet<String>(patterns.size()); for (String pattern : patterns) { if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {//URL不以 /開頭就會自動新增 / pattern = "/" + pattern; } result.add(pattern); } return result; }
回到RequestMappingInfo的構造方法,將@RequestMapping的所有屬性都以 RequestCondition的實現類 形式儲存到 RequestMappingInfo物件中;
介面RequestCondition定義了三個方法, 1.combine: 一般用來 方法級別@RequestMapping與類級別@RequestMapping結合,返回新的(通常是RequestMappingInfo);
2.getMatchingCondition: 檢查request物件是否滿足條件,返回一個新的滿足條件的RequestMappingInfo例項(T泛型用都是RequestMappingInfo);
3.compareTo 用來多個匹配的情況排序挑選最合適的
public interface RequestCondition<T> { T combine(T other); T getMatchingCondition(HttpServletRequest request); int compareTo(T other, HttpServletRequest request); }
至此 回到 RequestMappingHandlerMapping 的 getMappingForMethod方法 ,第一個方法級別的createRequestMappingInfo方法分析完畢,下面兩行解析了標註在 類上的 註解,並且返回 RequestMappingInfo物件,
第188行就是類上標註了@RequestMapping註解,和方法上同樣標註@RequestMapping結合處理的步驟: 呼叫類上的RequestMappingInfo的combine方法
檢視RequestMappingInfo物件的combine方法:
public RequestMappingInfo combine(RequestMappingInfo other) { // RequestMapping的name屬性的處理方法,一般name屬性很少寫,處理方式:兩個都不為空就返回this.name#other.name;有一個為空 就返回另外一個name String name = combineNames(other); //下面邏輯A分析 呼叫AntPathMatcher的combine方法,將類上URL和方法上URL組合並放入新PatternsRequestCondition PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); //下面邏輯B分析,並且接下來的methods、params、headers等等實現方式大體一致 RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); //!!comsume和produce判斷邏輯不是相加,方法上的該屬性優先順序高於類級別上的 ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(name, patterns,//返回一個新的RequestMappingInfo物件,其中所有RequestCondition都是新建立的物件 methods, params, headers, consumes, produces, custom.getCondition()); }
邏輯A: PatternsRequestCondition 之前介紹過,其屬性patterns 就是@RequestMapping的path / value 屬性的集合,且判斷 path是否以 / 開頭,如果不是會自動補全 / 開頭;
其實現了RequestCondition介面,檢視其combine方法
public PatternsRequestCondition combine(PatternsRequestCondition other) { // result作為新的請求路徑集合 Set<String> result = new LinkedHashSet<String>(); //類上註解@RequestMapping path不為空,方法上註解註解@RequestMapping path不為空 //此處的AntPathMatcher就是RequestMappingHandlerMapping物件裡的antPathMatcher物件 //@RequestMapping path屬性是集合型別的,這類似笛卡爾積形式 呼叫AntPathMatcher的combine方式,進行URL組合 加入到result if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) { for (String pattern1 : this.patterns) { for (String pattern2 : other.patterns) { result.add(this.pathMatcher.combine(pattern1, pattern2)); } } } //已經說明有一方為空了,只要判斷另外一方是否為空,不為空直接加入Set<String> else if (!this.patterns.isEmpty()) { result.addAll(this.patterns); } else if (!other.patterns.isEmpty()) { result.addAll(other.patterns); } else { result.add(""); }/返回了一個新的PatternsRequestCondition物件,patterns屬性就是當前方法的請求路徑 return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions); / }
邏輯A-1:AntPathMatcher物件如何對請求路徑進行結合combine?
類上path | 方法上path | 結合後path |
null | null | |
/hotels | null | /hotels |
null | /hotels | /hotels |
/hotels | /bookings | /hotels/bookings |
/hotels | bookings | /hotels/bookings |
/hotels/* | /bookings | /hotels/bookings |
/hotels/** | /bookings | /hotels/**/bookings |
/hotels | {hotel} | /hotels/{hotel} |
/hotels/* | {hotel} | /hotels/{hotel} |
/hotels/** | {hotel} | /hotels/**/{hotel} |
/*.html | hotels.html | /hotels.html |
/*.html | /hotels | /hotels.html |
/*.html | /*.txt | IllegalArgumentException |
邏輯B:RequestMethodsRequestCondition 的 combine 方法,方法上註解@RequestMapping的method加入到類上註解的method屬性裡,然後返回一個全新的RequestMethodsRequestCondition,持有新的method集合;
public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) { Set<RequestMethod> set = new LinkedHashSet<RequestMethod>(this.methods); set.addAll(other.methods); return new RequestMethodsRequestCondition(set); }
getMappingForMethod方法呼叫結束,返回結合後的RequestMappingInfo物件; 回到MethodIntrospector.selectMethods方法,第19行就是呼叫的getMappingForMethod方法,返回RequestMappingInfo物件result,result不為空之後,
會篩選不是橋接方法,存入methodMap這個Map物件,key-type是Method,value-type是RequestMappingInfo型別;
該方法selectMethods將Controller / Handler中所有方法都進行判斷載入請求對映,返回methodMap物件;
1public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { 2final Map<Method, T> methodMap = new LinkedHashMap<Method, T>(); 3Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>(); 4Class<?> specificHandlerType = null; 5 6if (!Proxy.isProxyClass(targetType)) { 7handlerTypes.add(targetType); 8specificHandlerType = targetType; 9} 10handlerTypes.addAll(Arrays.asList(targetType.getInterfaces())); 11 12for (Class<?> currentHandlerType : handlerTypes) { 13final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); 14 15ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { 16@Override 17public void doWith(Method method) { 18Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 19T result = metadataLookup.inspect(specificMethod); 20if (result != null) { 21Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); 22if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { 23methodMap.put(specificMethod, result); 24} 25} 26} 27}, ReflectionUtils.USER_DECLARED_METHODS); 28} 29 30return methodMap; 31}
回到最開始的分析detectHandlerMethods方法:methods物件就是上面返回的methodMap,如果日誌設定了DEBUG,每遍歷一個controller都會輸出日誌;
1 protected void detectHandlerMethods(final Object handler) { 2Class<?> handlerType = (handler instanceof String ? 3getApplicationContext().getType((String) handler) : handler.getClass()); 4final Class<?> userType = ClassUtils.getUserClass(handlerType); 5 6Map<Method, T> methods = MethodIntrospector.selectMethods(userType, 7new MethodIntrospector.MetadataLookup<T>() { 8@Override 9public T inspect(Method method) { 10return getMappingForMethod(method, userType); 11} 12}); 13 14if (logger.isDebugEnabled()) { 15logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); 16} 17for (Map.Entry<Method, T> entry : methods.entrySet()) {//遍歷methods,並且呼叫registerHandlerMethod註冊對映資訊 18Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); 19T mapping = entry.getValue(); 20registerHandlerMethod(handler, invocableMethod, mapping); 21} 22}
registerHandlerMethod:總結 :
RequestMappingHandlerMapping.mappingRegistry屬性 |
key-type | value-type |
mappingLookup |
RequestMappingInfo | HandlerMethod物件 |
urlLookup |
請求路徑URL | RequestMappingInfo |
nameLookup | controller name中大寫字母#方法名(如UC#test) | HandlerMethod物件 |
registry |
RequestMappingInfo | MappingRegistration物件(持有 RequestMappingInfo\HandlerMethod\URL路徑\name) |
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } // this物件指RequestMappingHandlerMapping,mapping是RequestMappingInfo物件,handler是controler的name,method是當前@RequestMapping方法 public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock();//可重入鎖 寫鎖上鎖這裡不太明白為什麼要上鎖 try { //建立新的HandlerMethod物件 下面邏輯C 介紹HandlerMethod 邏輯D 分析createHandlerMethod方法 HandlerMethod handlerMethod = createHandlerMethod(handler, method); //校驗唯一性,一個RequestMappingInfo對應一個Handlermethod,如果根據RequestMappingInfo找到不同的hm 丟擲異常 assertUniqueMethodMapping(handlerMethod, mapping); //INFO級別日誌 比如Mapped "{[/user/test]}" onto public java.lang.String demo2.UserController.test(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); } //this指RequestMappingHandlerMapping.MappingRegistry,mappingLookup儲存著RequestMappingInfo--HandlerMethod物件 this.mappingLookup.put(mapping, handlerMethod); //獲取 mapping 的PatternsRequestCondition的patterns,也就是拼接的URL路徑,並且路徑不包含* ?的就加入到集合返回 ,List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { //MappingRegistry的urlLookup儲存著 url--RequestMappingInfo物件 this.urlLookup.add(url, mapping); } String name = null; if (getNamingStrategy() != null) { //name屬性感覺沒用,如果@RequestMapping有name屬性就是這個屬性 如果沒有就是 controller名字中的大寫字母#方法名字,比如UC#test name = getNamingStrategy().getName(handlerMethod, mapping); //MappingRegistry的nameLookup儲存著 name--HandlerMethod集合 addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } //MappingRegistry的registry儲存著RequestMappingInfo--MappingRegistration,MappingRegistration幾乎有對映的所有資訊 this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock();//可重入鎖 寫鎖 釋放鎖 } }
邏輯C:HandlerMethod物件 屬性有bean,就是controller物件例項;beanFactory當前Spring容器;beanType就是controller的型別;method就是handler method;birdgeMethod是handler method的橋接方法;MethodParameter是handler method的方法引數,handlerMethod一般為null;
HandlerMethod,作用Spring給出了:一個handler method物件,包含了method以及controller物件,此外提供了便捷方式獲取方法入參、返回值、註解等等;
邏輯D:createHandlerMethod方法只是呼叫了HandlerMethod的構造方法,構造方法中對方法入參進行了處理;
1protected HandlerMethod createHandlerMethod(Object handler, Method method) { 2HandlerMethod handlerMethod; 3if (handler instanceof String) { 4String beanName = (String) handler; 5handlerMethod = new HandlerMethod(beanName, 6getApplicationContext().getAutowireCapableBeanFactory(), method); 7} 8else { 9handlerMethod = new HandlerMethod(handler, method); 10} 11return handlerMethod; 12} 13 14 public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { 15Assert.hasText(beanName, "Bean name is required"); 16Assert.notNull(beanFactory, "BeanFactory is required"); 17Assert.notNull(method, "Method is required"); 18this.bean = beanName; //controller beanName 19this.beanFactory = beanFactory; //當前controller所在Spring工廠 20this.beanType = ClassUtils.getUserClass(beanFactory.getType(beanName));//獲取當前controller型別 21this.method = method; //當前handler method 22this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);//查詢method的橋接方法,沒有橋接方法就是返回自身 23this.parameters = initMethodParameters();//初始化MethodParameter物件 設定了每個MethodParameter的method、parameterIndex屬性 具體方法下圖 24this.resolvedFromHandlerMethod = null; 25}
至此,registerHandlerMethod方法分析完畢,detectHandlerMethods方法分析完成,
Spring主要做了哪些工作:將所有請求對映關係儲存到上面RequestMappingHandlerMapping的mappingRegistry的相關屬性中,詳情見上面表格。
分析過SpringMvc的請求流程 SpringMvc流程
篇幅太長,只分析如何找根據請求找到對應的handler? 遍歷HandlerMapping物件,呼叫其getHanlder方法查詢controller / handler , RequestMappingHandlerMapping物件的父類AbstractHandlerMapping實現了getHandler方法,方法最開始Object handler = getHandlerInternal(request); 那麼我們從AbstractHandlerMapping 的 getHandlerInternal開始記錄.
1 protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 根據request請求路徑以及servlet-mapping得到要請求URL 2String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 3if (logger.isDebugEnabled()) { 4logger.debug("Looking up handler method for path " + lookupPath); 5} 6this.mappingRegistry.acquireReadLock(); //讀鎖 上鎖 7try { // 這裡就是MVC尋找controller匹配的方法! 下面花大篇幅介紹下 8HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); 9if (logger.isDebugEnabled()) { 10if (handlerMethod != null) { 11logger.debug("Returning handler method [" + handlerMethod + "]"); 12} 13else { 14logger.debug("Did not find handler method for [" + lookupPath + "]"); 15} 16} //找到handlerMethod,但bean是controller beanName,用beanFactory getBean替換bean 17return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);18} 19finally { 20this.mappingRegistry.releaseReadLock(); 21} 22}
lookupHandlerMethod 方法:
邏輯是這樣的,先根據請求的URL 從 RequestMappingHandlerMapping的 mappingRegistry 的 urlLookup 中嘗試尋找RequestMappingInfo;
尋找大致分為兩種情況:一種請求URL清楚,不需要萬用字元比對,那肯定可以直接找到RequestMappingInfo集合,建立Match物件並且新增到集合裡面,然後根據規則對Match集合排序選出最優解;
第二種情況URL帶有萬用字元,那需要遍歷對映關係再重複第一種情況。
1 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { 2List<Match> matches = new ArrayList<Match>(); //return this.urlLookup.get(urlPath);呼叫mappingRegistry的urlLookup根據URL尋找RequestMappingInfo 3 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 4if (directPathMatches != null) { //遍歷找到的RequestMappingInfo集合, 然後尋找匹配的物件並處理新增到matches集合,見 邏輯E 分析 5addMatchingMappings(directPathMatches, matches, request); 6} 7if (matches.isEmpty()) { //matches為空,有可能是因為萬用字元匹配的情況需要再次匹配 8// No choice but to go through all mappings... 9addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 10} 11 12if (!matches.isEmpty()) { //返回一個MatchComparator物件 // 持有Comparator屬性,並且compare方法是呼叫了RequestMappingInfo的compareTo 13Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); //說到底排序還是呼叫了RequestMappingInfo的compareTo方法, 也存在優先順序之分 URL路徑>params>headers>comsume>produce>method排序分析見文章最後 14Collections.sort(matches, comparator); 15if (logger.isTraceEnabled()) { 16logger.trace("Found " + matches.size() + " matching mapping(s) for [" + 17lookupPath + "] : " + matches); 18} 19Match bestMatch = matches.get(0);//找到最優匹配 20if (matches.size() > 1) { 21if (CorsUtils.isPreFlightRequest(request)) { 22return PREFLIGHT_AMBIGUOUS_MATCH; 23} 24Match secondBestMatch = matches.get(1); 25if (comparator.compare(bestMatch, secondBestMatch) == 0) {//存在兩個匹配且相等 丟擲異常 26Method m1 = bestMatch.handlerMethod.getMethod(); 27Method m2 = secondBestMatch.handlerMethod.getMethod(); 28throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + 29request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); 30} 31} 32handleMatch(bestMatch.mapping, lookupPath, request);//解析URL變數,完成設定request屬性等工作 33return bestMatch.handlerMethod; //返回最優匹配的HandlerMethod物件 34} 35else {//沒找到handlerMethod 就返回null 36return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); 37} 38}
邏輯E:遍歷找到的RequestMappingInfo集合,呼叫RequestMappingInfo的getMatchCondition進行匹配以獲取匹配的RequestMappingInfo物件;尋找到合適的RequestMappingInfo物件之後,建立一個Match物件加入matches集合;
mappingRegistry 的 getMappings方法返回mappingLookup屬性,上述表格mappingLookup 存放 RequestMappingInfo--HandlerMethod,根據RequestMappingInfo物件從map中取物件,(邏輯G分析 RequestMappingInfo重寫了的hashCode以及equals方法)。 Match物件持有RequestMappingInfo以及HandlerMethod屬性;此處方法呼叫結束matches可能包含多個Match結果;
RequestMappingInfo的getMatchingCondition方法
1 public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { //如果RequestMappingInfo沒有指定methods屬性,返回RequestMappingInfo本身,否則方法匹配 2RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); //下面幾個匹配邏輯是一樣的,匹配了返回自身,沒匹配返回null,具體引數作用、如何匹配看吧; 3ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); 4HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); 5ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); 6ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); 7 //有一個條件匹配不上就直接返回null 8if (methods == null || params == null || headers == null || consumes == null || produces == null) { 9return null; 10} 11 //其他匹配上了,最重要的匹配請求URL, 路徑匹配作為 邏輯F 分析 12PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); 13if (patterns == null) { 14return null; 15} 16 17RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); 18if (custom == null) { 19return null; 20} 21 22return new RequestMappingInfo(this.name, patterns, //尋找到匹配之後,構造一個新的RequestMappingInfo物件,持有上述匹配之後的結果返回 23methods, params, headers, consumes, produces, custom.getCondition());24}
邏輯F:PatternsRequestCondition匹配
呼叫PatternsRequestCondition 的 getMatchingPattern 方法進行URL匹配;遍歷PatternsRequestCondition的 patterns屬性,逐個getMatchingPattern進行比較,匹配上將pattern存入集合,並且使用AntPatternComparator進行排序,排序之後集合加入到一個新的PatternsRequestCondition物件中;
//pattern就是patterns屬性當前迭代的元素,lookupPath就是servlet-mapping下請求URL 1 private String getMatchingPattern(String pattern, String lookupPath) {2if (pattern.equals(lookupPath)) { //兩者相等 無疑義直接返回 這種是沒有萬用字元 * ?這種都會很容易匹配到並且返回 3return pattern; 4} 5if (this.useSuffixPatternMatch) {// useSuffixPatternMatch預設為true 6if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {// fileExtensions預設為空 7for (String extension : this.fileExtensions) { 8if (this.pathMatcher.match(pattern + extension, lookupPath)) { 9return pattern + extension; 10} 11} 12} 13else { 14boolean hasSuffix = pattern.indexOf('.') != -1;//pattern字串是否有 . 15if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {//沒有 . 就用AntPathMatcher的match匹配 pattern.*lookupPath 16return pattern + ".*"; 17} 18} 19} 20if (this.pathMatcher.match(pattern, lookupPath)) { // 用AntPathMatcher的match匹配 pattern lookupPath,匹配上就返回pattern 21return pattern; 22} 23if (this.useTrailingSlashMatch) { 24if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) { 25return pattern +"/"; 26} 27} 28return null; 29}
邏輯G:先介紹下為什麼要看RequestMappingInfo的hashCode以及equals方法?RequestMappingInfo作為key儲存在Map中,肯定需要重寫HashCode以及equals方法;
RequestMappingInfo的hashCode以及equals方法: 比較的時候會先呼叫hashCode判斷值是否相等,相等再比較equals方法,如果相等則認為是同一個物件;
先來看hashCode方法,將RequestMappingInfo的所有RequestCondition屬性按公式求和,這些屬性都是AbstractRequestCondition,equals和hashCode方法都呼叫了getContent方法,而AbstractRequestCondition的各種實現類的getContent方法,比如PatternsRequestCondition實現方式就是返回patterns(URL)集合;比如RequestMethodsRequestCondition實現就是返回methods集合;
RequestMappingInfo 1public boolean equals(Object other) { 2if (this == other) { 3return true; 4} 5if (!(other instanceof RequestMappingInfo)) { 6return false; 7} 8RequestMappingInfo otherInfo = (RequestMappingInfo) other; 9return (this.patternsCondition.equals(otherInfo.patternsCondition) && 10this.methodsCondition.equals(otherInfo.methodsCondition) && 11this.paramsCondition.equals(otherInfo.paramsCondition) && 12this.headersCondition.equals(otherInfo.headersCondition) && 13this.consumesCondition.equals(otherInfo.consumesCondition) && 14this.producesCondition.equals(otherInfo.producesCondition) && 15this.customConditionHolder.equals(otherInfo.customConditionHolder)); 16} 17 18@Override 19public int hashCode() { 20return (this.patternsCondition.hashCode() * 31 +// primary differentiation 21this.methodsCondition.hashCode() + this.paramsCondition.hashCode() + 22this.headersCondition.hashCode() + this.consumesCondition.hashCode() + 23this.producesCondition.hashCode() + this.customConditionHolder.hashCode()); 24}
AbstractRequestCondition 1public boolean equals(Object obj) { 2if (this == obj) { 3return true; 4} 5if (obj != null && getClass() == obj.getClass()) { 6AbstractRequestCondition<?> other = (AbstractRequestCondition<?>) obj; 7return getContent().equals(other.getContent()); 8} 9return false; 10} 11 12@Override 13public int hashCode() { 14return getContent().hashCode(); 15}
分析到上面,getHandlerInternal已經找到了對應的HandlerMethod物件,呼叫getHandlerExecutionChain封裝成HandlerExecutionChain;
1 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { 2HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? 3(HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); // //構造一個HandlerExecutionChain物件,持有handlerMethod4 5String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); 6for (HandlerInterceptor interceptor : this.adaptedInterceptors) { //adaptedInterceptors在開啟<mvc:annotation-drvien/>之後不為空,多了一個MappedInterceptor攔截器 7if (interceptor instanceof MappedInterceptor) { 8MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; 9if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { 10chain.addInterceptor(mappedInterceptor.getInterceptor()); //將ConversionServiceExposingInterceptor新增到HandlerExecutionChain的interceptorList屬性中 11} 12} 13else { 14chain.addInterceptor(interceptor); 15} 16} 17return chain;//返回HandlerExecutionChain物件 18}
Tip1:這個RequestMappingHandlerMapping的MappedInterceptor是從哪裡注入的呢?
開啟了<mvc:annotation-driven />之後 Spring向容器中注入了這樣兩個bean的定義,MappedInterceptor,該物件持有ConversionServiceExposingInterceptor物件;
容器中有了MappedInterceptor物件,什麼時候給RequestMappingHandlerMapping設定的adaptedInterceptors呢?通過打斷點分析到,RequestMappingHandlerMapping實現了ApplicationContextAware介面,Spring向其注入ApplicationContext的時候,呼叫了initApplicationContext方法,不斷進入方法最後進入到父類AbstractHandlerMapping的initApplicationContext方法,
1 protected void initApplicationContext() throws BeansException { 2extendInterceptors(this.interceptors); 3detectMappedInterceptors(this.adaptedInterceptors);//此處添加了RequestMappingHandlerMapping的adaptedInterceptors 4initInterceptors(); 5 } 6 7 protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { //這裡將容器中的MappedInterceptor新增到了RequestMappingHandlerMapping的adaptedInterceptors 8mappedInterceptors.addAll( 9BeanFactoryUtils.beansOfTypeIncludingAncestors( 10getApplicationContext(), MappedInterceptor.class, true, false).values()); 11}
至此,如何找到HandlerMethod已經分析完畢;
總結
SpringMvc請求尋找規則 : 如果一個請求同時匹配上多個方法,按照如下順序選擇執行哪個方法:
先URL匹配的方法 >>>>> params滿足的方法 >>>>> headers 滿足的方法 >>>>>>consume滿足的方法 >>>> produce 滿足的方法 >>>> method滿足的方法
如果一個請求匹配上了多個RequestMappingInfo篩選:
之前介紹過排序是呼叫 RequestMappingInfo的compareTo進行排序
1 public int compareTo(RequestMappingInfo other, HttpServletRequest request) { 2int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);//優先URL進行匹配 3if (result != 0) { 4return result; 5} 6result = this.paramsCondition.compareTo(other.getParamsCondition(), request); 7if (result != 0) { 8return result; 9} 10result = this.headersCondition.compareTo(other.getHeadersCondition(), request); 11if (result != 0) { 12return result; 13} 14result = this.consumesCondition.compareTo(other.getConsumesCondition(), request); 15if (result != 0) { 16return result; 17} 18result = this.producesCondition.compareTo(other.getProducesCondition(), request); 19if (result != 0) { 20return result; 21} 22result = this.methodsCondition.compareTo(other.getMethodsCondition(), request); 23if (result != 0) { 24return result; 25} 26result = this.customConditionHolder.compareTo(other.customConditionHolder, request); 27if (result != 0) { 28return result; 29} 30return 0; 31}
介紹下URL如何排序吧,其他類似; 假設兩個URL /get1 可以被匹配 /get* 以及 /get?
1 public int compareTo(PatternsRequestCondition other, HttpServletRequest request) { 2String lookupPath = this.pathHelper.getLookupPathForRequest(request); 3Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);//獲取AntPatternComparator比較器 4Iterator<String> iterator = this.patterns.iterator(); 5Iterator<String> iteratorOther = other.patterns.iterator(); 6while (iterator.hasNext() && iteratorOther.hasNext()) { 7int result = patternComparator.compare(iterator.next(), iteratorOther.next());//URL比較規則在這裡 8if (result != 0) { 9return result; 10} 11} 12if (iterator.hasNext()) { 13return -1; 14} 15else if (iteratorOther.hasNext()) { 16return 1; 17} 18else { 19return 0; 20} 21}
URL比較規則:按照請求URL萬用字元按一定權重計算排序順序,{個數+*個數+ ** 個數 ;所以 get* 比get?排在前面;
1 public int compare(String pattern1, String pattern2) {//例子中pattern1為 /get*pattern2為/get? 2PatternInfo info1 = new PatternInfo(pattern1);//具體檢視下面構造方法 3PatternInfo info2 = new PatternInfo(pattern2); 4 5if (info1.isLeastSpecific() && info2.isLeastSpecific()) { 6return 0; 7} 8else if (info1.isLeastSpecific()) { 9return 1; 10} 11else if (info2.isLeastSpecific()) {//上面三種情況是 比較 /**的情況 12return -1; 13} 14 15boolean pattern1EqualsPath = pattern1.equals(path); 16boolean pattern2EqualsPath = pattern2.equals(path); 17if (pattern1EqualsPath && pattern2EqualsPath) { 18return 0; 19} 20else if (pattern1EqualsPath) { 21return -1; 22} 23else if (pattern2EqualsPath) {//這三種情況是比較pattern1 pattern2存在和請求URL完全匹配的情況 24return 1; 25} 26 27if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { 28return 1; 29} 30else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {//哪個pattern的 /**多 哪個排在前面 31return -1; 32} 33 34if (info1.getTotalCount() != info2.getTotalCount()) { 35return info1.getTotalCount() - info2.getTotalCount();//按照權重來排序了{算1*算1 **算2哪個大哪個排前面/get*權重為1排前面 36} 37 38if (info1.getLength() != info2.getLength()) { 39return info2.getLength() - info1.getLength(); 40} 41 42if (info1.getSingleWildcards() < info2.getSingleWildcards()) { 43return -1; 44} 45else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { 46return 1; 47} 48 49if (info1.getUriVars() < info2.getUriVars()) { 50return -1; 51} 52else if (info2.getUriVars() < info1.getUriVars()) { 53return 1; 54} 55 56return 0; 57} 58 59 public PatternInfo(String pattern) { 60this.pattern = pattern; 61if (this.pattern != null) { 62initCounters(); 63this.catchAllPattern = this.pattern.equals("/**");//代表匹配所有就是pattern為 /** 64this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); 65} 66if (this.uriVars == 0) { 67this.length = (this.pattern != null ? this.pattern.length() : 0); 68} 69} 70 71protected void initCounters() { 72int pos = 0; 73while (pos < this.pattern.length()) { 74if (this.pattern.charAt(pos) == '{') {//存在變數 則uriVars自增 75this.uriVars++; 76pos++; 77} 78else if (this.pattern.charAt(pos) == '*') {//解析到* 79if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { 80this.doubleWildcards++;// doubleWildcards代表有兩個*的 81pos += 2; 82} 83else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {//最後一位是* 且倒數第二位不是 * 84this.singleWildcards++;// singleWildcards代表有單個* 85pos++; 86} 87else { 88pos++; 89} 90} 91else { 92pos++; 93} 94} 95}