Spring MVC請求處理(二) - HandlerMapping(一)

DispatcherServlet的doDispatch方法利用getHandler獲取與請求匹配的HandlerExecutionChain,getHandler方法代碼如下廊宪,可以看到該方法從已有的HandlerMapping中返回第一個匹配該請求的HandlerExecutionChain亮钦,若沒有匹配則返回null析藕。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

這里的HandlerMapping和HandlerExecutionChain是什么呢盛末?

HandlerMapping接口

HandlerMapping接口定義了請求到處理器對象的映射唁奢,處理器會被包裝成處理器執(zhí)行鏈HandlerExecutionChain甚负。HandlerMapping接口的類層次結(jié)構(gòu)如下圖所示:


HandlerMapping接口的類層次結(jié)構(gòu).png

HandlerMapping接口的代碼如下所示柬焕,除了常量定義外只有一個接口方法审残。

public interface HandlerMapping {

    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

getHandler方法返回與請求匹配的執(zhí)行鏈,若沒有匹配則返回null斑举,其Javadoc如下所述:

Return a handler and any interceptors for this request. The choice may be made on request URL, session state, or any factor the implementing class chooses.
The returned HandlerExecutionChain contains a handler Object, rather than even a tag interface, so that handlers are not constrained in any way. For example, a HandlerAdapter could be written to allow another framework's handler objects to be used.
Returns null if no match was found. This is not an error. The DispatcherServlet will query all registered HandlerMapping beans to find a match, and only decide there is an error if none can find a handler.

下面以常用的RequestMappingHandlerMapping為例分析請求與處理器的匹配過程搅轿。

AbstractHandlerMapping類

AbstractHandlerMapping類是實(shí)現(xiàn)了HandlerMapping接口的抽象類。

成員變量

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
    private Object defaultHandler;

    private UrlPathHelper urlPathHelper = new UrlPathHelper();

    private PathMatcher pathMatcher = new AntPathMatcher();

    private final List<Object> interceptors = new ArrayList<Object>();

    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();

    private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered

    // 省略一些代碼
    
    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return this.order;
    }
}
  • defaultHandler是默認(rèn)處理器富玷,可以是任意類型的對象璧坟;
  • urlPathHelper和pathMatcher用于路徑匹配;
  • interceptors和adaptedInterceptors都是攔截器列表赎懦,但是用途不同雀鹃。目前支持的攔截器類型有三種,分別是HandlerInterceptor励两、WebRequestInterceptor和MappedInterceptor黎茎,其中MappedInterceptor實(shí)現(xiàn)了HandlerInterceptor接口而WebRequestInterceptor卻沒有。請注意当悔,interceptors列表的元素是Object類型傅瞻,該列表保存所有這三種攔截器的原始形式;而adaptedInterceptor列表的元素是HandlerInterceptor類型盲憎,該列表存HandlerInterceptor類型的攔截器嗅骄,之所以叫adapted是因?yàn)槌跏蓟^程會將WebRequestInterceptor用適配器模式轉(zhuǎn)換成HandlerInterceptor存起來(見下文);
  • AbstractHandlerMapping類實(shí)現(xiàn)了Ordered接口以便排序饼疙,優(yōu)先級默認(rèn)是Ordered.LOWEST_PRECEDENCE即int的最大值溺森,表示不排序。

初始化

AbstractHandlerMapping類繼承了WebApplicationObjectSupport類宏多,重寫了initApplicationContext方法以自定義初始化過程儿惫。

@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

protected void extendInterceptors(List<Object> interceptors) {
}

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    getApplicationContext(), MappedInterceptor.class, true, false).values());
}

初始化的過程做了以下三件事:

  • extendInterceptors供子類添加新的攔截器使用;
  • detectMappedInterceptors將MappedInterceptor類型的攔截器bean添加到adaptedInterceptors列表中伸但;
  • initInterceptors初始化所有的攔截器肾请。
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
        return (HandlerInterceptor) interceptor;
    }
    else if (interceptor instanceof WebRequestInterceptor) {
        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else {
        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
}
  • 對HandlerInterceptor和MappedInterceptor(它實(shí)現(xiàn)了HandlerInterceptor接口),直接添加到adaptedInterceptors更胖;
  • 對WebRequestInterceptor铛铁,先利用適配器模式將其包裝成WebRequestHandlerInterceptorAdapter(它實(shí)現(xiàn)了HandlerInterceptor接口),然后添加到adaptedInterceptors却妨。

setter方法

AbstractHandlerMapping類的部分setter方法如下所示饵逐,除了setUrlPathHelper和setPathMatcher設(shè)置了成員變量外,其他都用來設(shè)置urlPathHelper成員變量的屬性彪标。

public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
    this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
    this.globalCorsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
}

public void setUrlDecode(boolean urlDecode) {
    this.urlPathHelper.setUrlDecode(urlDecode);
    this.globalCorsConfigSource.setUrlDecode(urlDecode);
}

public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
    this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
    this.globalCorsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
}

public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
    this.urlPathHelper = urlPathHelper;
    this.globalCorsConfigSource.setUrlPathHelper(urlPathHelper);
}

public void setPathMatcher(PathMatcher pathMatcher) {
    Assert.notNull(pathMatcher, "PathMatcher must not be null");
    this.pathMatcher = pathMatcher;
    this.globalCorsConfigSource.setPathMatcher(pathMatcher);
}
  • setAlwaysUseFullPath:在當(dāng)前ServletContext查找URL時是否使用完整路徑倍权,默認(rèn)false;
  • setUrlDecode:是否應(yīng)該URL解碼上下文路徑和請求URI捞烟,默認(rèn)是true薄声,注意由Servlet API返回的二者都是未解碼的当船;
  • setRemoveSemicolonContent:是否應(yīng)該去掉請求URI中分號后面的部分,默認(rèn)是true默辨。

getHandler接口方法

AbstractHandlerMapping類利用模板方法模式實(shí)現(xiàn)了HandlerMapping的getHandler接口方法德频,子類需要重寫getHandlerInternal方法以實(shí)現(xiàn)功能,代碼如下所示缩幸。

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
  • getHandler方法首先調(diào)用getHandlerInternal找到與該請求匹配的處理器壹置,若沒有匹配則使用默認(rèn)處理器,若還沒有匹配則返回null表谊。處理器可以是任意類型的對象钞护,若是String則表示bean名稱,所在ApplicationContext中該名稱對應(yīng)的bean會被作為處理器铃肯;
  • 獲取處理器后患亿,調(diào)用getHandlerExecutionChain函數(shù)將處理器包裝成HandlerExecutionChain并返回传蹈。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

getHandlerExecutionChain函數(shù)的包裝過程如下:

  1. 利用處理器對象生成一個HandlerExecutionChain對象押逼,HandlerExecutionChain的getHandler返回的即是該處理器對象,這會用于doDispatch的后續(xù)過程惦界;
  2. 利用UrlPathHelper獲得該請求的查找路徑挑格;
  3. 為HandlerExecutionChain添加攔截器,添加時需要區(qū)分?jǐn)r截器的類型沾歪,這是因?yàn)镸appedInterceptor只會應(yīng)用于其路徑模式與請求URL匹配的請求漂彤。對所有攔截器,若不是MappedInterceptor類型那么直接添加到HandlerExecutionChain中灾搏;否則判斷查找路徑是否與MappedInterceptor的路徑模式相匹配挫望,匹配才添加到HandlerExecutionChain中。

AbstractHandlerMethodMapping類

AbstractHandlerMethodMapping類是抽象泛型類狂窑,它繼承AbstractHandlerMapping類并實(shí)現(xiàn)了InitializingBean接口媳板,類名暗示它返回的處理器是HandlerMethod類型,泛型參數(shù)T表示一種映射泉哈,這個映射含有將處理器匹配到請求所需的條件蛉幸。請注意,如不加說明丛晦,本文中的映射均指這種映射奕纫,而不是數(shù)據(jù)結(jié)構(gòu)的Map。

成員變量

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    private boolean detectHandlerMethodsInAncestorContexts = false;

    private HandlerMethodMappingNamingStrategy<T> namingStrategy;

    private final MappingRegistry mappingRegistry = new MappingRegistry();

    public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) {
        this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
    }

    public void setHandlerMethodMappingNamingStrategy(HandlerMethodMappingNamingStrategy<T> namingStrategy) {
        this.namingStrategy = namingStrategy;
    }

    public HandlerMethodMappingNamingStrategy<T> getNamingStrategy() {
        return this.namingStrategy;
    }

    // 省略一些代碼
}
  • detectHandlerMethodsInAncestorContexts表示是否要在祖先上下文中發(fā)現(xiàn)HandlerMethod烫沙,默認(rèn)為false匹层;
  • namingStrategy是為映射命名的策略接口;
  • mappingRegistry是比較重要的變量锌蓄,保存了與映射有關(guān)的全部信息升筏。

初始化

AbstractHandlerMethodMapping類既繼承了AbstractHandlerMapping類又實(shí)現(xiàn)了InitializingBean接口仲器,因此重寫了afterPropertiesSet方法。

@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

上文提到AbstractHandlerMapping類的initApplicationContext方法也用于初始化過程仰冠,這里需要注意這些回調(diào)方法的執(zhí)行順序:

  • AbstractHandlerMapping類繼承了WebApplicationObjectSupport類乏冀,該類實(shí)現(xiàn)了ApplicationContextAware接口,在它重寫的setApplicationContext方法中調(diào)用了initApplicationContext方法去實(shí)現(xiàn)自定義初始化的功能洋只。setApplicationContext會在正常bean的屬性被填充之后但在InitializingBean的afterPropertiesSet()方法或自定義初始化方法之前被調(diào)用辆沦,具體可參見ApplicationContextAware的Javadoc
  • 所以initApplicationContext先于afterPropertiesSet執(zhí)行识虚。

因此AbstractHandlerMethodMapping初始化時會先初始化所有的攔截器肢扯,然后調(diào)用initHandlerMethods發(fā)現(xiàn)所有HandlerMethod,代碼如下:

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

protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
}

protected abstract boolean isHandler(Class<?> beanType);
  • detectHandlerMethodsInAncestorContexts表示是否在祖先上下文中發(fā)現(xiàn)HandlerMethod担锤,默認(rèn)為false蔚晨。因?yàn)镠andlerMethod可以存在于任何類型的對象中,所以查找Bean的時候需要查找Object類型肛循;
  • 按bean名稱獲得其真實(shí)類型铭腕,調(diào)用抽象方法isHandler判斷bean是否需要被掃描以發(fā)現(xiàn)其中的HandlerMethod,若是則調(diào)用detectHandlerMethods方法發(fā)現(xiàn)該bean里的HandlerMethod多糠;
  • handlerMethodsInitialized是受保護(hù)方法累舷,子類可以重寫,這相當(dāng)于一個HandlerMethod初始化完成的回調(diào)函數(shù)夹孔。

下面重點(diǎn)看一下detectHandlerMethods方法:

protected void detectHandlerMethods(final Object 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);
    }
}

protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}
  • 用ClassUtils.getUserClass可以正確獲得CGLIB生成子類的情況下原始類的類型被盈,因此userType是真實(shí)、原始的bean類型搭伤,可想象成Controller類型只怎;
  • getMappingForMethod抽象方法由子類重寫,若Java方法是所需的處理器那么為該Java方法生成映射并返回怜俐,否則返回null身堡;
  • 在userType表示的類中遍歷方法,若該方法是所需的處理器則調(diào)用registerHandlerMethod注冊該方法和映射佑菩,委托給MappingRegistry內(nèi)部類的register方法盾沫。

MappingRegistration內(nèi)部類

MappingRegistration類是AbstractHandlerMethodMapping的私有靜態(tài)內(nèi)部類,有構(gòu)造函數(shù)和getter方法殿漠,沒有setter方法赴精,表示映射的注冊(見下文分析)信息。

private static class MappingRegistration<T> {
    private final T mapping;

    private final HandlerMethod handlerMethod;

    private final List<String> directUrls;

    private final String mappingName;

    public MappingRegistration(T mapping, HandlerMethod handlerMethod, List<String> directUrls, String mappingName) {
        Assert.notNull(mapping, "Mapping must not be null");
        Assert.notNull(handlerMethod, "HandlerMethod must not be null");
        this.mapping = mapping;
        this.handlerMethod = handlerMethod;
        this.directUrls = (directUrls != null ? directUrls : Collections.<String>emptyList());
        this.mappingName = mappingName;
    }

    public T getMapping() {
        return this.mapping;
    }

    public HandlerMethod getHandlerMethod() {
        return this.handlerMethod;
    }

    public List<String> getDirectUrls() {
        return this.directUrls;
    }

    public String getMappingName() {
        return this.mappingName;
    }
}

MappingRegistry內(nèi)部類

MappingRegistry類是AbstractHandlerMethodMapping的內(nèi)部類绞幌,保存了與映射有關(guān)的全部信息蕾哟。

1. 成員變量
class MappingRegistry {
    private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();

    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();

    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();

    private final Map<String, List<HandlerMethod>> nameLookup =
            new ConcurrentHashMap<String, List<HandlerMethod>>();

    private final Map<HandlerMethod, CorsConfiguration> corsLookup =
            new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public Map<T, HandlerMethod> getMappings() {
        return this.mappingLookup;
    }

    public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
    }

    public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
        return this.nameLookup.get(mappingName);
    }

    public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
        HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
        return this.corsLookup.get(original != null ? original : handlerMethod);
    }

    public void acquireReadLock() {
        this.readWriteLock.readLock().lock();
    }

    public void releaseReadLock() {
        this.readWriteLock.readLock().unlock();
    }

    // 省略一些代碼
}

成員變量的多數(shù)雖然都是Map,但用途不同:

  • registry是從映射到注冊信息的Map;
  • mappingLookup是從映射到HandlerMethod的Map谭确;
  • urlLookup是從URL到映射的多值Map帘营;
  • nameLookup是從映射名到HandlerMethod的Map。
2. 注冊映射

將處理器和Java原始方法通過register和其他輔助方法注冊映射逐哈,代碼如下所示芬迄。

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);

        if (logger.isInfoEnabled()) {
            logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
        }
        this.mappingLookup.put(mapping, handlerMethod);

        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }

        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }

        this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}
  • 獲取讀寫鎖的寫鎖;
  • 調(diào)用createHandlerMethod將Java方法包裝成一個新的HandlerMethod對象昂秃,由此可見不是每個Java方法都是HandlerMethod禀梳,只有滿足getMappingForMethod抽象方法中條件的才是;
  • 調(diào)用assertUniqueMethodMapping驗(yàn)證新的HandlerMethod和映射在MappingRegistry中的唯一性肠骆,若不唯一則拋出異常算途,相信很多人使用@RequestMapping注解時都遇到過錯誤“Ambiguous mapping ...”;
    private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
        HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
        if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
            throw new IllegalStateException(
                    "Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
                    newHandlerMethod + "\nto " + mapping + ": There is already '" +
                    handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
        }
    }
    
  • 通過唯一性驗(yàn)證后蚀腿,新的映射和HandlerMethod被添加到MappingRegistry嘴瓤,可以在日志看到如Mapped xxx onto xxx 形式的輸出;
  • 調(diào)用AbstractHandlerMethodMapping類的getMappingPathPatterns抽象方法提取該映射包含的URL路徑莉钙,對其中的所有直接URL路徑(即不需要模式匹配的路徑)廓脆,將直接URL路徑與該映射的關(guān)聯(lián)添加到MappingRegistry的urlLookup。注意對同一直接URL路徑可能會有多個處理器方法映射與之對應(yīng)胆胰,例如同一Controller的兩個方法分別處理對/users不同HTTP方法的請求狞贱,這時urlLookup中的/users鍵就會對應(yīng)兩個映射刻获;
    private List<String> getDirectUrls(T mapping) {
        List<String> urls = new ArrayList<String>(1);
        for (String path : getMappingPathPatterns(mapping)) {
            if (!getPathMatcher().isPattern(path)) {
                urls.add(path);
            }
        }
        return urls;
    }
    
  • 使用AbstractHandlerMethodMapping的策略接口為HandlerMethod和映射生成映射名蜀涨,同時調(diào)用addMappingName方法將映射名與該HandlerMethod的關(guān)聯(lián)添加到MappingRegistry;
    private void addMappingName(String name, HandlerMethod handlerMethod) {
        List<HandlerMethod> oldList = this.nameLookup.get(name);
        if (oldList == null) {
            oldList = Collections.<HandlerMethod>emptyList();
        }
    
        for (HandlerMethod current : oldList) {
            if (handlerMethod.equals(current)) {
                return;
            }
        }
    
        if (logger.isTraceEnabled()) {
            logger.trace("Mapping name '" + name + "'");
        }
    
        List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() + 1);
        newList.addAll(oldList);
        newList.add(handlerMethod);
        this.nameLookup.put(name, newList);
    
        if (newList.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace("Mapping name clash for handlerMethods " + newList +
                        ". Consider assigning explicit names.");
            }
        }
    }
    
  • 利用上述注冊信息生成MappingRegistration對象蝎毡,并將該對象添加到MappingRegistry厚柳;
  • 釋放寫鎖。

經(jīng)過上述分析沐兵,HandlerMethod的概念逐漸變得清晰别垮。簡單地說,它是對Java方法的包裝扎谎,但不是所有的Java方法都能被包裝成HandlerMethod去處理請求碳想,只有符合某些條件的才是,這個條件的判斷就在getMappingForMethod抽象方法中毁靶。

重寫getHandlerInternal方法

AbstractHandlerMethodMapping類繼承了AbstractHandlerMapping類胧奔,重寫的getHandlerInternal方法如下,由lookupHandlerMethod方法完成查找匹配HandlerMethod的任務(wù)预吆。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
        logger.debug("Looking up handler method for path " + lookupPath);
    }
    this.mappingRegistry.acquireReadLock();
    try {
        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();
    }
}
  • 注意該方法的返回值是HandlerMethod龙填,因此AbstractHandlerMapping類的getHandlerExecutionChain方法將其包裝成HandlerExecutionChain后HandlerExecutionChain的getHandler返回的是該HandlerMethod,這會用于doDispatch的后續(xù)過程。

查找匹配HandlerMethod的代碼如下岩遗。

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        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 + "}");
            }
        }
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
        }
    }
}

/**
 * Check if a mapping matches the current request and return a (potentially
 * new) mapping with conditions relevant to the current request.
 * @param mapping the mapping to get a match for
 * @param request the current HTTP servlet request
 * @return the match, or {@code null} if the mapping doesn't match
 */
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);

抽象方法getMatchingMapping檢查映射是否與請求相匹配扇商,若匹配則返回一個與當(dāng)前請求有關(guān)的映射,否則返回null宿礁。addMatchingMappings方法遍歷mappings表示的映射集合案铺,將與請求相匹配的映射包裝成Match添加到匹配集matches。從上述代碼可以總結(jié)出查找匹配HandlerMethod的過程:

  1. MappingRegistry的getMappingsByUrl方法代碼如下梆靖,是從URL到映射的多值Map中取值红且,這個Map保存的是注冊映射時直接URL路徑(即不需要模式匹配)與映射的關(guān)聯(lián)。
    public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
    }
    
    從mappingRegistry獲取請求路徑對應(yīng)的映射(不需要模式匹配)涤姊,這時只看URL路徑而不會去管HTTP請求方法等條件暇番,接著將與請求相匹配的映射加入匹配集;
  2. 若匹配集為空則表示沒有能與請求路徑直接匹配的映射(不需要模式匹配)思喊,此時需要遍歷mappingRegistry中的所有映射進(jìn)行模式匹配壁酬;
  3. 若能匹配請求路徑,那么對這些匹配排序恨课,排序方式由子類決定舆乔。若匹配數(shù)量有兩個以上且最佳匹配和次最佳匹配相同則報錯;
    protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
    
  4. handleMatch相當(dāng)于回調(diào)函數(shù)剂公,表示有匹配時的執(zhí)行動作希俩;
    protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
        request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    }
    
  5. handleNoMatch也相當(dāng)于回調(diào)函數(shù),表示無匹配時的執(zhí)行動作纲辽;
    protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
            throws Exception {
        return null;
    }
    

RequestMappingInfoHandlerMapping類和RequestMappingHandlerMapping類的分析請看下文颜武。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拖吼,隨后出現(xiàn)的幾起案子鳞上,更是在濱河造成了極大的恐慌,老刑警劉巖吊档,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙议,死亡現(xiàn)場離奇詭異,居然都是意外死亡怠硼,警方通過查閱死者的電腦和手機(jī)鬼贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來香璃,“玉大人这难,你說我怎么就攤上這事≡鱿裕” “怎么了雁佳?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵脐帝,是天一觀的道長。 經(jīng)常有香客問我糖权,道長堵腹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任星澳,我火速辦了婚禮疚顷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘禁偎。我一直安慰自己腿堤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布如暖。 她就那樣靜靜地躺著笆檀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盒至。 梳的紋絲不亂的頭發(fā)上酗洒,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音枷遂,去河邊找鬼樱衷。 笑死,一個胖子當(dāng)著我的面吹牛酒唉,可吹牛的內(nèi)容都是我干的矩桂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痪伦,長吁一口氣:“原來是場噩夢啊……” “哼侄榴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起流妻,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牲蜀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绅这,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡在辆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年证薇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匆篓。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浑度,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸦概,到底是詐尸還是另有隱情箩张,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站先慷,受9級特大地震影響饮笛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜论熙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一福青、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脓诡,春花似錦无午、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至交惯,卻和暖如春踩验,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背商玫。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工箕憾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拳昌。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓袭异,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炬藤。 傳聞我的和親對象是個殘疾皇子御铃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容