SpringMVC源碼分析--HandlerMappings

以前分析過SpringMVC中的DispatcherServlet,分析了SpringMVC處理請求的過程。但忽略了一些DispatcherServlet協助請求處理的組件,例如SpringMVC中的HandlerMappingHandlerAdapterViewResolvers等等。java

HandlerMappings

HandlerMappingsDispathServlet中主要做用是爲請求的urlpath匹配對應的Controller,創建一個映射關係,根據請求查找HandlerInterceptorHandlerMappings將請求傳遞到HandlerExecutionChain上,HandlerExecutionChain包含了一個可以處理該請求的處理器,還能夠包含攔截改請求的攔截器。web

在沒有處理器映射相關配置狀況下,DispatcherServlet會爲你建立一個BeanNameUrlHandlerMapping做爲默認映射的配置。在DispatchServlet.properties文件中對於HandlerMapping的默認配置是:spring

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

HandlerMapping的配置策略通常分爲配置式BeanNameUrlHandlerMapping和註解式DefaultAnnotationHandlerMapping。不過DefaultAnnotationHandlerMapping已經被放棄了,取代它的是RequestMappingHandlerMapping,不知道爲啥SpringMVC這個默認配置還沒有作修改。設計模式

AbstractHandlerMapping

AbstractHandlerMappingHandlerMapping的抽象實現,是全部HandlerMapping實現類的父類。併發

AbstractHandlerMapping的做用是是爲了初始化InterceptorsAbstractHandlerMapping重寫了WebApplicationObjectSupportinitApplicationContext方法。mvc

protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }
  • extendInterceptors方法,Springmvc並無作出具體實現,這裏留下一個拓展,子類能夠重寫這個模板方法,爲子類添加或者修改Interceptorsapp

  • detectMappedInterceptors方法將SpringMVC容器中全部MappedInterceptor類的bean添加到adaptedInterceptors中。ide

  • 最後調用initInterceptors初始化攔截器。遍歷interceptorsWebRequestInterceptorHandlerInterceptor類型的攔截器添加到adaptedInterceptors中。ui

HandlerMapping經過getHandler方法來獲取請求的處理器Handler和攔截器Interceptor。在getHandlerExecutionChain方法中將遍歷以前初始化的adaptedInterceptors,爲當前的請求選擇對應的MappedInterceptorsadaptedInterceptorsthis

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping繼承於AbstractHandlerMapping,它是經過URL來匹配具體的HandlerAbstractUrlHandlerMapping維護一個handlerMap來存儲UrlHandler的映射關係。

AbstractUrlHandlerMapping重寫了AbstractHandlerMapping類中的getHandlerInternal方法。HandlerMapping經過getHandler方法,就會調用這裏的getHandlerInternal方法來獲取HandlergetHandlerInternal方法中關鍵調用lookupHandler方法去獲取handler

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // Direct match?
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }

        // Pattern match?
        List<String> matchingPatterns = new ArrayList<String>();
        for (String registeredPattern : this.handlerMap.keySet()) {
            if (getPathMatcher().match(registeredPattern, urlPath)) {
                matchingPatterns.add(registeredPattern);
            }
            else if (useTrailingSlashMatch()) {
                if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                    matchingPatterns.add(registeredPattern +"/");
                }
            }
        }

        String bestMatch = null;
        Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            Collections.sort(matchingPatterns, patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestMatch = matchingPatterns.get(0);
        }
        if (bestMatch != null) {
            handler = this.handlerMap.get(bestMatch);
            if (handler == null) {
                if (bestMatch.endsWith("/")) {
                    handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
                }
                if (handler == null) {
                    throw new IllegalStateException(
                            "Could not find handler for best pattern match [" + bestMatch + "]");
                }
            }
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);

            // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
            // for all of them
            Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
            for (String matchingPattern : matchingPatterns) {
                if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
                    Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                    Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                    uriTemplateVariables.putAll(decodedVars);
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
            }
            return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
        }

        // No handler found...
        return null;
    }
  • 首先調用lookupHandler方法來獲取handler。在lookupHandler方法中,先經過URLhandlerMap查找是否有合適的handler
  • 若是沒有獲取到handler,遍歷handlerMap利用正則匹配的方法,找到符合要求的handlers(有多是多個)。
  • 正則匹配是採用Ant風格,將會經過排序篩選出一個匹配程度最高的Handler
  • 最後調用buildPathExposingHandler方法構建一個handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor兩個攔截器並返回。

上面介紹獲取handler的過程當中,會先從handlerMap查找。下面看一下handlerMap是如何初始化的。AbstractUrlHandlerMapping是經過registerHandler初始化handlerMap的。AbstractUrlHandlerMapping共有兩個registerHandler方法。分別是註冊多個url到一個handler和註冊一個url到一個handler。首先判斷handlerMap是否有此handler。若是存在的話,判斷是否一致,不一致則拋出異常,若是不存在的話,若是url//*,則,返回root handlerdefault handler,若是不是將添加到handlerMap中。

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping繼承於AbstractUrlHandlerMappingSimpleUrlHandlerMapping重寫了父類AbstractHandlerMapping中的初始化方法initApplicationContext。在initApplicationContext方法中調用registerHandlers方法。

protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }

判斷是url是否以/開頭,若是不是,默認補齊/,確保全部的url都是以/開頭,而後依次調用父類的registerHandler方法註冊到AbstractUrlHandlerMapping中的handlerMap

在使用SimpleUrlHandlerMapping時,須要在註冊的時候配置其urlmap不然會拋異常。

AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping類繼承於AbstractUrlHandlerMapping類,重寫了initApplicationContext方法,在initApplicationContext方法中調用了detectHandlers方法。

protected void detectHandlers() throws BeansException {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
                }
            }
        }
    }

獲取全部容器的beanNames,遍歷全部的beanName,調用determineUrlsForHandler方法解析url,這裏的determineUrlsForHandler也是運用了模板方法設計模式,具體的實如今其子類中,若是解析到子類,將註冊到父類的handlerMap中。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping類的類圖大體以下:

BeanNameUrlHandlerMapping類繼承於AbstractDetectingUrlHandlerMapping類。重寫了父類中的determineUrlsForHandler方法。

protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<String>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = getApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

其經過beanName解析Url規則也很簡單,判斷beanName是否以/開頭。

BeanNameUrlHandlerMappingSpringMVC的默認映射配置。

AbstractHandlerMethodMapping

一般咱們也習慣於用@Controller@Re questMapping來定義HandlerAbstractHandlerMethodMapping能夠將method做爲Handler來使用。

AbstractHandlerMethodMapping實現了InitializingBean接口,實現了afterPropertiesSet方法。當容器啓動的時候會調用initHandlerMethods註冊委託handler中的方法。

public void afterPropertiesSet() {
        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));

        for (String beanName : beanNames) {
            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)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

initHandlerMethods方法中,作了如下工做:

  • 首先經過BeanFactoryUtils掃描應用上下文,獲取全部的bean
  • 遍歷全部的beanName,調用isHandler方法判斷是目標bean是否包含@Controller@RequestMapping註解。
  • 對於帶有@Controller@RequestMapping註解的類,調用detectHandlerMethods委託處理,獲取全部的method,並調用registerHandlerMethod註冊全部的方法。

detectHandlerMethods方法負責將Handler保存到Map中。

protected void detectHandlerMethods(final Object handler) {
       //  獲取handler的類型
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });

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

selectMethods方法中重寫了MetadataLookup中的inspect方法,inspect方法中調用了子類RequestMappingHandlerMapping實現了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)) {
            handlerTypes.add(targetType);
            specificHandlerType = targetType;
        }
        handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));

        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

            ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) {
                    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                    T result = metadataLookup.inspect(specificMethod);
                    if (result != null) {
                        Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                        if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                            methodMap.put(specificMethod, result);
                        }
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }

        return methodMap;
    }

selectMethods經過反射獲取全部的方法,重寫了doWith方法,將handler中的method和請求對應的RequestMappingInfo保存到methodMap中。

最終detectHandlerMethods將遍歷這個methodMap,調用registerHandlerMethod註冊HandlerMethodMappingRegistry

AbstractHandlerMethodMapping類中,有個內部類MappingRegistry,用來存儲mapping和 handler methods註冊關係,並提供了併發訪問方法。

AbstractHandlerMethodMapping經過getHandlerInternal來爲一個請求選擇對應的handler

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
       // 根據request獲取對應的urlpath
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up handler method for path " + lookupPath);
        }
        // 獲取讀鎖
        this.mappingRegistry.acquireReadLock();
        try {
          // 調用lookupHandlerMethod方法獲取請求對應的HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            if (logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    logger.debug("Returning handler method [" + handlerMethod + "]");
                }
                else {
                    logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

lookupHandlerMethod的具體實現以下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        // 經過lookupPath獲取全部匹配到的path
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
           // 將匹配條件添加到matches
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // 若是沒有匹配條件,將全部的匹配條件都加入matches
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        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) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                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 + "}");
                }
            }
            // 將lookupPath設爲請求request的PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE屬性
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

整個過程以Match做爲載體,Match是個內部類,封裝了匹配條件和handlerMethod兩個屬性,默認的實現是將lookupPath設置爲請求的屬性。

總結

本文從源碼角度上分析了HandlerMapping的各類實現。主要功能是爲請求找到合適的handlerinterceptors,並組合成HandlerExecutionChain。查找handler的過程經過getHandlerInternal方法實現,每一個子類都其不一樣的實現。

全部的HandlerMapping的實現都繼承於AbstarctHandlerMappingAbstarctHandlerMapping主要做用是完成攔截器的初始化工做。而經過AbstarctHandlerMapping又衍生出兩個系列,AbstractUrlHandlerMappingAbstractHandlerMethodMapping

AbstractUrlHandlerMapping也有不少子類的實現,如SimpleUrlHandlerMappingAbstractDetectingUrlHandlerMapping。整體來講,AbstractUrlHandlerMapping須要用到一個保存urlhandler的對應關係的mapmap的初始化工做由子類實現。不一樣的子類會有本身的策略,能夠在配置文件中註冊,也能夠在spring容器中找。

AbstractHandlerMethodMapping系列則一般用於註解的方法,解析包含@Controller或者@RequestMapping註解的類,創建urlmethod的直接對應關係,這也是目前使用最多的一種方式。

相關文章
相關標籤/搜索