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

DispatcherServlet的doDispatch方法利用getHandler獲取與請(qǐng)求匹配的HandlerExecutionChain,getHandler方法代碼如下,可以看到該方法從已有的HandlerMapping中返回第一個(gè)匹配該請(qǐng)求的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接口定義了請(qǐng)求到處理器對(duì)象的映射,處理器會(huì)被包裝成處理器執(zhí)行鏈HandlerExecutionChain。HandlerMapping接口的類層次結(jié)構(gòu)如下圖所示:


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

HandlerMapping接口的代碼如下所示,除了常量定義外只有一個(gè)接口方法。

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方法返回與請(qǐng)求匹配的執(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為例分析請(qǐng)求與處理器的匹配過程。

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)處理器,可以是任意類型的對(duì)象;
  • urlPathHelper和pathMatcher用于路徑匹配;
  • interceptors和adaptedInterceptors都是攔截器列表,但是用途不同。目前支持的攔截器類型有三種,分別是HandlerInterceptor、WebRequestInterceptor和MappedInterceptor,其中MappedInterceptor實(shí)現(xiàn)了HandlerInterceptor接口而WebRequestInterceptor卻沒有。請(qǐng)注意,interceptors列表的元素是Object類型,該列表保存所有這三種攔截器的原始形式;而adaptedInterceptor列表的元素是HandlerInterceptor類型,該列表存HandlerInterceptor類型的攔截器,之所以叫adapted是因?yàn)槌跏蓟^程會(huì)將WebRequestInterceptor用適配器模式轉(zhuǎn)換成HandlerInterceptor存起來(見下文);
  • AbstractHandlerMapping類實(shí)現(xiàn)了Ordered接口以便排序,優(yōu)先級(jí)默認(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());
    }
}
  • 對(duì)HandlerInterceptor和MappedInterceptor(它實(shí)現(xiàn)了HandlerInterceptor接口),直接添加到adaptedInterceptors;
  • 對(duì)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時(shí)是否使用完整路徑,默認(rèn)false;
  • setUrlDecode:是否應(yīng)該URL解碼上下文路徑和請(qǐng)求URI,默認(rèn)是true,注意由Servlet API返回的二者都是未解碼的;
  • setRemoveSemicolonContent:是否應(yīng)該去掉請(qǐng)求URI中分號(hào)后面的部分,默認(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找到與該請(qǐng)求匹配的處理器,若沒有匹配則使用默認(rèn)處理器,若還沒有匹配則返回null。處理器可以是任意類型的對(duì)象,若是String則表示bean名稱,所在ApplicationContext中該名稱對(duì)應(yīng)的bean會(huì)被作為處理器;
  • 獲取處理器后,調(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. 利用處理器對(duì)象生成一個(gè)HandlerExecutionChain對(duì)象,HandlerExecutionChain的getHandler返回的即是該處理器對(duì)象,這會(huì)用于doDispatch的后續(xù)過程;
  2. 利用UrlPathHelper獲得該請(qǐng)求的查找路徑;
  3. 為HandlerExecutionChain添加攔截器,添加時(shí)需要區(qū)分?jǐn)r截器的類型,這是因?yàn)镸appedInterceptor只會(huì)應(yīng)用于其路徑模式與請(qǐng)求URL匹配的請(qǐng)求。對(duì)所有攔截器,若不是MappedInterceptor類型那么直接添加到HandlerExecutionChain中;否則判斷查找路徑是否與MappedInterceptor的路徑模式相匹配,匹配才添加到HandlerExecutionChain中。

AbstractHandlerMethodMapping類

AbstractHandlerMethodMapping類是抽象泛型類,它繼承AbstractHandlerMapping類并實(shí)現(xiàn)了InitializingBean接口,類名暗示它返回的處理器是HandlerMethod類型,泛型參數(shù)T表示一種映射,這個(gè)映射含有將處理器匹配到請(qǐng)求所需的條件。請(qǐng)注意,如不加說明,本文中的映射均指這種映射,而不是數(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會(huì)在正常bean的屬性被填充之后但在InitializingBean的afterPropertiesSet()方法或自定義初始化方法之前被調(diào)用,具體可參見ApplicationContextAware的Javadoc;
  • 所以initApplicationContext先于afterPropertiesSet執(zhí)行。

因此AbstractHandlerMethodMapping初始化時(shí)會(huì)先初始化所有的攔截器,然后調(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可以存在于任何類型的對(duì)象中,所以查找Bean的時(shí)候需要查找Object類型;
  • 按bean名稱獲得其真實(shí)類型,調(diào)用抽象方法isHandler判斷bean是否需要被掃描以發(fā)現(xiàn)其中的HandlerMethod,若是則調(diào)用detectHandlerMethods方法發(fā)現(xiàn)該bean里的HandlerMethod;
  • handlerMethodsInitialized是受保護(hù)方法,子類可以重寫,這相當(dāng)于一個(gè)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注冊(cè)該方法和映射,委托給MappingRegistry內(nèi)部類的register方法。

MappingRegistration內(nèi)部類

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

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是從映射到注冊(cè)信息的Map;
  • mappingLookup是從映射到HandlerMethod的Map;
  • urlLookup是從URL到映射的多值Map;
  • nameLookup是從映射名到HandlerMethod的Map。
2. 注冊(cè)映射

將處理器和Java原始方法通過register和其他輔助方法注冊(cè)映射,代碼如下所示。

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方法包裝成一個(gè)新的HandlerMethod對(duì)象,由此可見不是每個(gè)Java方法都是HandlerMethod,只有滿足getMappingForMethod抽象方法中條件的才是;
  • 調(diào)用assertUniqueMethodMapping驗(yàn)證新的HandlerMethod和映射在MappingRegistry中的唯一性,若不唯一則拋出異常,相信很多人使用@RequestMapping注解時(shí)都遇到過錯(cuò)誤“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路徑,對(duì)其中的所有直接URL路徑(即不需要模式匹配的路徑),將直接URL路徑與該映射的關(guān)聯(lián)添加到MappingRegistry的urlLookup。注意對(duì)同一直接URL路徑可能會(huì)有多個(gè)處理器方法映射與之對(duì)應(yīng),例如同一Controller的兩個(gè)方法分別處理對(duì)/users不同HTTP方法的請(qǐng)求,這時(shí)urlLookup中的/users鍵就會(huì)對(duì)應(yīng)兩個(gè)映射;
    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和映射生成映射名,同時(shí)調(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.");
            }
        }
    }
    
  • 利用上述注冊(cè)信息生成MappingRegistration對(duì)象,并將該對(duì)象添加到MappingRegistry;
  • 釋放寫鎖。

經(jīng)過上述分析,HandlerMethod的概念逐漸變得清晰。簡(jiǎn)單地說,它是對(duì)Java方法的包裝,但不是所有的Java方法都能被包裝成HandlerMethod去處理請(qǐng)求,只有符合某些條件的才是,這個(gè)條件的判斷就在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,這會(huì)用于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檢查映射是否與請(qǐng)求相匹配,若匹配則返回一個(gè)與當(dāng)前請(qǐng)求有關(guān)的映射,否則返回null。addMatchingMappings方法遍歷mappings表示的映射集合,將與請(qǐng)求相匹配的映射包裝成Match添加到匹配集matches。從上述代碼可以總結(jié)出查找匹配HandlerMethod的過程:

  1. MappingRegistry的getMappingsByUrl方法代碼如下,是從URL到映射的多值Map中取值,這個(gè)Map保存的是注冊(cè)映射時(shí)直接URL路徑(即不需要模式匹配)與映射的關(guān)聯(lián)。
    public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
    }
    
    從mappingRegistry獲取請(qǐng)求路徑對(duì)應(yīng)的映射(不需要模式匹配),這時(shí)只看URL路徑而不會(huì)去管HTTP請(qǐng)求方法等條件,接著將與請(qǐng)求相匹配的映射加入匹配集;
  2. 若匹配集為空則表示沒有能與請(qǐng)求路徑直接匹配的映射(不需要模式匹配),此時(shí)需要遍歷mappingRegistry中的所有映射進(jìn)行模式匹配;
  3. 若能匹配請(qǐng)求路徑,那么對(duì)這些匹配排序,排序方式由子類決定。若匹配數(shù)量有兩個(gè)以上且最佳匹配和次最佳匹配相同則報(bào)錯(cuò);
    protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
    
  4. handleMatch相當(dāng)于回調(diào)函數(shù),表示有匹配時(shí)的執(zhí)行動(dòng)作;
    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ù),表示無匹配時(shí)的執(zhí)行動(dòng)作;
    protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
            throws Exception {
        return null;
    }
    

RequestMappingInfoHandlerMapping類和RequestMappingHandlerMapping類的分析請(qǐng)看下文。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容