SpringMVC handleMapping映射過程記錄

SpringMVC Mapping映射 記錄

  1. 初始化IOC容器
    Spring初始化的時候會優先初始化自定義的類,下面這個就是
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0

類的結構圖

image.png

根據這個結構圖可以發現,RequestMappingHandlerMapping這個功能還是非常強大的.畢竟註冊路由這個功能還是需要依賴IOC容器的,所以它已經實現了ApplicationContextAware持有了上下文的對象.擁有了這個對象,就可以很方便的去容器中查找controller中的所有對象和方法了.

  1. 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);
            }
        }
    }

  1. 這時候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中了,不過有幾點需要注意

  1. 它的匹配規則
  • 如果註冊的時候是//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是如何做的呢?

  1. addMatchingMappings這個裏面會進行一個循環匹配所有URL
  2. 遍歷的時候會得到這個key的RequestMappingInfo對象.RequestMappingInfo對象持有(PatternsRequestCondition對象)擁有匹配url的規則
  3. PatternsRequestCondition對象又會交給AntPathMatcher , 這裏的持有對象都是在初始化中如果沒有指定Spring給你默認的,相當於實際匹配的規則都是在AntPathMatcher裏面去操作的

AntPathMatcher操作思路:

  1. 他定義了一個ConcurrentHashMap對象stringMatcherCache,key是url中的每一個/後面的對象

/a/b/{c} 它承裝的就是三個對象 a、b、{c}都是它的key,value就是一個AntPathStringMatcher對象,這個對象會處理{c}這種情況,轉化成對應的類似*這種正則匹配

  1. 上層經過解析到達AntPathMatcher 對象時是url中的一段一段path,然後從stringMatcherCache去找有沒有對應的AntPathStringMatcher,如果沒有則實例化一個,然後根據這個進行match,匹配則返回true

整體的匹配思路:
前端傳遞一個: /a/b/c

  1. 程序會將這個url解析成3段去匹配 【a、b、c】
    2.先拿a去AntPathMatcher的matchStrings方法去進行匹配,而AntPathMatcher會轉交給AntPathStringMatcher,通過則返回true

這裏會涉及到一個優先級的問題:
比如後端定義了3個url :

  1. /a/b/{c}
  2. /a/b/*
  3. /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進行處理

總結

初始化邏輯:

  1. 在IOC容器已經初始化容器的時候,會加載Spring的一個內置對象RequestMappingHandlerMapping,而這個對象又實現了InitializingBean方法.這時候就會觸發afterPropertiesSet方法的調用
  2. 而這個方法裏面則是會對ioc容器中的所有對象進行遍歷,找到類中包含@Controller或者@RequestMapping等註解的類
  3. 將上面符合的類的方法進行遍歷並且註冊到Map中,這個map又分爲一個url爲key的map和方法對象爲key的map
  4. 這時候HandlerMethod已經初始化完成

調用邏輯:

  1. 當前端發送一個url請求的時候,會被springMvc攔截到
  2. 會根據當前的url進行handler匹配,第一個匹配是根據url進行全路徑匹配,這個匹配容器封裝在了一個叫UrlMap中,如果匹配到直接返回map中的handlerMethod對象
  3. 如果上面沒有匹配到,這時候會從一個封裝方法的Map中去進行正則匹配,這裏匹配是將所有的Controller中的路由方法進行匹配,這裏會先將url按照/進行拆分成多個String進行全路徑匹配,匹配到直接返回。
  4. 如果匹配不到,則開始進行一系列的正則匹配..具體的匹配規則可以參考上面的AntPatternComparator類,這裏會將正則匹配到的url進行一個排序,最前面的優先級最高.
  5. 這裏就會將優先級最高的進行反射調用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章