SpringMVC Mapping映射 記錄
- 初始化IOC容器
Spring初始化的時候會優先初始化自定義的類,下面這個就是
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0
類的結構圖
根據這個結構圖可以發現,RequestMappingHandlerMapping這個功能還是非常強大的.畢竟註冊路由這個功能還是需要依賴IOC容器的,所以它已經實現了ApplicationContextAware持有了上下文的對象.擁有了這個對象,就可以很方便的去容器中查找controller中的所有對象和方法了.
- ioc 實例話這個類的時候 會經過一個 AbstractAutowireCapableBeanFactory - invokeInitMethods 的方法, 這個方法會判斷這個類是否實現InitializingBean這個類的方法,如果是則調用它的afterPropertiesSet方法。
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
((InitializingBean) bean).afterPropertiesSet();
return null;
}
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 調用這個初始化中接口的方法
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
- 這時候RequestMappingHandlerMapping類會委託給父類去處理這個afterPropertiesSet方法。
- 獲取MVC中的ioc容器裏面註冊的對象,並且進行循環遍歷
- 判斷isHandler註冊的對象中的類是否包含@Controller或者@RequestMapping等註解,滿足則會對類的方法進行遍歷
- 進行註冊到AbstractHandlerMethodMapping中的handlerMethods、urlMap中,一個是以方法做爲key[RequestMappingInfo],一個是以url作爲key進行存儲
- 註冊完畢之後,前端發送過來的請求就會從urlMap這裏面進行查找
AbstractHandlerMethodMapping
// 父類又會交給一個initHandlerMethods處理
public void afterPropertiesSet() {
initHandlerMethods();
}
// 初始化方法處理
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
// 查詢父類是否有處理HandlerMethods方法的上下文,如果沒有則獲取MVC容器中的所有對象
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// 遍歷對象
for (String beanName : beanNames) {
// 類名前綴是否包含scopedTarget.
// isHandler 這個方法很關鍵
// 判斷該類是否包含Controller或者RequestMapping註解
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
// 處理包含handle中的方法
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
// 注意這個類是在子類RequestMappingHandlerMapping中
@Override
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
/**
* 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) {
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 查找該類中的所有和handler相關的方法
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;
}
}
});
// 把上面遍歷出來的方法進行相應的註冊
for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
// 這裏是實際註冊handler的方法,在AbstractHandlerMethodMapping中
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
// 創建一個HandlerMethod對象,並且HandlerMethod這個對象持有整個工廠的引用
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
// 判斷是否存在重複的handler,如果存在則報錯
HandlerMethod oldHandlerMethod = this.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.");
}
// 不存在則將這個handle註冊到handlerMethods中, 這裏是根據方法的標識作爲key
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
// 這裏就是根據RequestMapping的value作爲key進行存儲 .. 也就url進行存儲
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
if (this.namingStrategy != null) {
String name = this.namingStrategy.getName(newHandlerMethod, mapping);
updateNameMap(name, newHandlerMethod);
}
}
調用過程
其實最終也會交給AbstractHandlerMethodMapping方法進行處理,因爲上面註冊的時候就已經把url註冊到urlMap中了,不過有幾點需要注意
- 它的匹配規則
- 如果註冊的時候是//urlPath , 但是你前端傳遞過來的時候是/urlPath, 這時候urlMap是匹配不到的,這時候它會從方法匹配中去查找,需要注意的是方法匹配需要遍歷所有註冊過的方法,相當於全局查找
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// 從urlMap註冊中查找對應匹配的handler
List<T> directPathMatches = this.urlMap.get(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// 如果查找不到,就從方法註冊的map中進行遍歷匹配
if (matches.isEmpty()) {
// No choice but to go through all mappings...
// 從方法爲key的map中查找就涉及到一個優先級匹配的規則了
addMatchingMappings(this.handlerMethods.keySet(), matches, request);
}
// 如果取出來的匹配的url不爲空,可能是1個或者多個的時候
if (!matches.isEmpty()) {
// 獲取一個比較器 , 注意這裏的比較器最終的實現規則是在AntPatternComparator 類中, 這是一個內部類 -> AntPathMatcher
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);
// 返回優先級最好的handler
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
這裏會涉及到一個匹配規則,spring是如何做的呢?
- addMatchingMappings這個裏面會進行一個循環匹配所有URL
- 遍歷的時候會得到這個key的RequestMappingInfo對象.RequestMappingInfo對象持有(PatternsRequestCondition對象)擁有匹配url的規則
- PatternsRequestCondition對象又會交給AntPathMatcher , 這裏的持有對象都是在初始化中如果沒有指定Spring給你默認的,相當於實際匹配的規則都是在AntPathMatcher裏面去操作的
AntPathMatcher操作思路:
- 他定義了一個ConcurrentHashMap對象stringMatcherCache,key是url中的每一個/後面的對象
/a/b/{c} 它承裝的就是三個對象 a、b、{c}都是它的key,value就是一個AntPathStringMatcher對象,這個對象會處理{c}這種情況,轉化成對應的類似*這種正則匹配
- 上層經過解析到達AntPathMatcher 對象時是url中的一段一段path,然後從stringMatcherCache去找有沒有對應的AntPathStringMatcher,如果沒有則實例化一個,然後根據這個進行match,匹配則返回true
整體的匹配思路:
前端傳遞一個: /a/b/c
- 程序會將這個url解析成3段去匹配 【a、b、c】
2.先拿a去AntPathMatcher的matchStrings方法去進行匹配,而AntPathMatcher會轉交給AntPathStringMatcher,通過則返回true
這裏會涉及到一個優先級的問題:
比如後端定義了3個url :
- /a/b/{c}
- /a/b/*
- /a/b/**
這三個都滿足上面的匹配條件,這時候Spring會要優先選取一個最好的handle去處理.
這裏最終處理的是
AntPathMatcher的內部類AntPatternComparator實現了一個compare方法
/**
優先級排序規則
1. 需要注意的是返回 1 的 表示正序 -1 表示倒序
*/
protected static class AntPatternComparator implements Comparator<String> {
private final String path;
public AntPatternComparator(String path) {
this.path = path;
}
/**
* Compare two patterns to determine which should match first, i.e. which
* is the most specific regarding the current path.
* @return a negative integer, zero, or a positive integer as pattern1 is
* more specific, equally specific, or less specific than pattern2.
*/
@Override
public int compare(String pattern1, String pattern2) {
PatternInfo info1 = new PatternInfo(pattern1);
PatternInfo info2 = new PatternInfo(pattern2);
// 如果pattern1 > pattern2 則會將 pattern2 放在前面 (優先級較高), 反則不動
// 再通俗一點講 1表示Info2的優先級上調 , -1表示 info1的優先級上調
// 如果是參數(pattern)裏面是null 或者 是 /**
if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
return 0;
}
// 優先級降低
else if (info1.isLeastSpecific()) {
return 1;
}
// 優先級上升
else if (info2.isLeastSpecific()) {
return -1;
}
// 具體匹配
boolean pattern1EqualsPath = pattern1.equals(path);
boolean pattern2EqualsPath = pattern2.equals(path);
if (pattern1EqualsPath && pattern2EqualsPath) {
return 0;
}
else if (pattern1EqualsPath) {
return -1;
}
else if (pattern2EqualsPath) {
return 1;
}
// 如果第一個前綴不是/**和後綴不是/**結尾
if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
return 1;
}
else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
return -1;
}
// TotalCount=(包含"{"的次數) +("*出現的次數") + "("**出現的次數")"
if (info1.getTotalCount() != info2.getTotalCount()) {
return info1.getTotalCount() - info2.getTotalCount();
}
//"\\{[^/]+?\\}"替換後的長度
if (info1.getLength() != info2.getLength()) {
return info2.getLength() - info1.getLength();
}
//*次數比較
if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
return -1;
}
else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
return 1;
}
// { 出現的次數
if (info1.getUriVars() < info2.getUriVars()) {
return -1;
}
else if (info2.getUriVars() < info1.getUriVars()) {
return 1;
}
return 0;
}
}
根據上面定義的優先級進行排序.之後會將優先級也就是list中的下標爲0的作爲最好的handle進行處理
總結
初始化邏輯:
- 在IOC容器已經初始化容器的時候,會加載Spring的一個內置對象RequestMappingHandlerMapping,而這個對象又實現了InitializingBean方法.這時候就會觸發afterPropertiesSet方法的調用
- 而這個方法裏面則是會對ioc容器中的所有對象進行遍歷,找到類中包含@Controller或者@RequestMapping等註解的類
- 將上面符合的類的方法進行遍歷並且註冊到Map中,這個map又分爲一個url爲key的map和方法對象爲key的map
- 這時候HandlerMethod已經初始化完成
調用邏輯:
- 當前端發送一個url請求的時候,會被springMvc攔截到
- 會根據當前的url進行handler匹配,第一個匹配是根據url進行全路徑匹配,這個匹配容器封裝在了一個叫UrlMap中,如果匹配到直接返回map中的handlerMethod對象
- 如果上面沒有匹配到,這時候會從一個封裝方法的Map中去進行正則匹配,這裏匹配是將所有的Controller中的路由方法進行匹配,這裏會先將url按照/進行拆分成多個String進行全路徑匹配,匹配到直接返回。
- 如果匹配不到,則開始進行一系列的正則匹配..具體的匹配規則可以參考上面的AntPatternComparator類,這裏會將正則匹配到的url進行一個排序,最前面的優先級最高.
- 這裏就會將優先級最高的進行反射調用