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接口定义了请求到处理器对象的映射,处理器会被包装成处理器执行链HandlerExecutionChain。HandlerMapping接口的类层次结构如下图所示:


HandlerMapping接口的类层次结构.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方法返回与请求匹配的执行链,若没有匹配则返回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类是实现了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是默认处理器,可以是任意类型的对象;
  • urlPathHelper和pathMatcher用于路径匹配;
  • interceptors和adaptedInterceptors都是拦截器列表,但是用途不同。目前支持的拦截器类型有三种,分别是HandlerInterceptor、WebRequestInterceptor和MappedInterceptor,其中MappedInterceptor实现了HandlerInterceptor接口而WebRequestInterceptor却没有。请注意,interceptors列表的元素是Object类型,该列表保存所有这三种拦截器的原始形式;而adaptedInterceptor列表的元素是HandlerInterceptor类型,该列表存HandlerInterceptor类型的拦截器,之所以叫adapted是因为初始化过程会将WebRequestInterceptor用适配器模式转换成HandlerInterceptor存起来(见下文);
  • AbstractHandlerMapping类实现了Ordered接口以便排序,优先级默认是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(它实现了HandlerInterceptor接口),直接添加到adaptedInterceptors;
  • 对WebRequestInterceptor,先利用适配器模式将其包装成WebRequestHandlerInterceptorAdapter(它实现了HandlerInterceptor接口),然后添加到adaptedInterceptors。

setter方法

AbstractHandlerMapping类的部分setter方法如下所示,除了setUrlPathHelper和setPathMatcher设置了成员变量外,其他都用来设置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:在当前ServletContext查找URL时是否使用完整路径,默认false;
  • setUrlDecode:是否应该URL解码上下文路径和请求URI,默认是true,注意由Servlet API返回的二者都是未解码的;
  • setRemoveSemicolonContent:是否应该去掉请求URI中分号后面的部分,默认是true。

getHandler接口方法

AbstractHandlerMapping类利用模板方法模式实现了HandlerMapping的getHandler接口方法,子类需要重写getHandlerInternal方法以实现功能,代码如下所示。

@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方法首先调用getHandlerInternal找到与该请求匹配的处理器,若没有匹配则使用默认处理器,若还没有匹配则返回null。处理器可以是任意类型的对象,若是String则表示bean名称,所在ApplicationContext中该名称对应的bean会被作为处理器;
  • 获取处理器后,调用getHandlerExecutionChain函数将处理器包装成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函数的包装过程如下:

  1. 利用处理器对象生成一个HandlerExecutionChain对象,HandlerExecutionChain的getHandler返回的即是该处理器对象,这会用于doDispatch的后续过程;
  2. 利用UrlPathHelper获得该请求的查找路径;
  3. 为HandlerExecutionChain添加拦截器,添加时需要区分拦截器的类型,这是因为MappedInterceptor只会应用于其路径模式与请求URL匹配的请求。对所有拦截器,若不是MappedInterceptor类型那么直接添加到HandlerExecutionChain中;否则判断查找路径是否与MappedInterceptor的路径模式相匹配,匹配才添加到HandlerExecutionChain中。

AbstractHandlerMethodMapping类

AbstractHandlerMethodMapping类是抽象泛型类,它继承AbstractHandlerMapping类并实现了InitializingBean接口,类名暗示它返回的处理器是HandlerMethod类型,泛型参数T表示一种映射,这个映射含有将处理器匹配到请求所需的条件。请注意,如不加说明,本文中的映射均指这种映射,而不是数据结构的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表示是否要在祖先上下文中发现HandlerMethod,默认为false;
  • namingStrategy是为映射命名的策略接口;
  • mappingRegistry是比较重要的变量,保存了与映射有关的全部信息。

初始化

AbstractHandlerMethodMapping类既继承了AbstractHandlerMapping类又实现了InitializingBean接口,因此重写了afterPropertiesSet方法。

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

上文提到AbstractHandlerMapping类的initApplicationContext方法也用于初始化过程,这里需要注意这些回调方法的执行顺序:

  • AbstractHandlerMapping类继承了WebApplicationObjectSupport类,该类实现了ApplicationContextAware接口,在它重写的setApplicationContext方法中调用了initApplicationContext方法去实现自定义初始化的功能。setApplicationContext会在正常bean的属性被填充之后但在InitializingBean的afterPropertiesSet()方法或自定义初始化方法之前被调用,具体可参见ApplicationContextAware的Javadoc
  • 所以initApplicationContext先于afterPropertiesSet执行。

因此AbstractHandlerMethodMapping初始化时会先初始化所有的拦截器,然后调用initHandlerMethods发现所有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表示是否在祖先上下文中发现HandlerMethod,默认为false。因为HandlerMethod可以存在于任何类型的对象中,所以查找Bean的时候需要查找Object类型;
  • 按bean名称获得其真实类型,调用抽象方法isHandler判断bean是否需要被扫描以发现其中的HandlerMethod,若是则调用detectHandlerMethods方法发现该bean里的HandlerMethod;
  • handlerMethodsInitialized是受保护方法,子类可以重写,这相当于一个HandlerMethod初始化完成的回调函数。

下面重点看一下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是真实、原始的bean类型,可想象成Controller类型;
  • getMappingForMethod抽象方法由子类重写,若Java方法是所需的处理器那么为该Java方法生成映射并返回,否则返回null;
  • 在userType表示的类中遍历方法,若该方法是所需的处理器则调用registerHandlerMethod注册该方法和映射,委托给MappingRegistry内部类的register方法。

MappingRegistration内部类

MappingRegistration类是AbstractHandlerMethodMapping的私有静态内部类,有构造函数和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内部类

MappingRegistry类是AbstractHandlerMethodMapping的内部类,保存了与映射有关的全部信息。

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

    // 省略一些代码
}

成员变量的多数虽然都是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();
    }
}
  • 获取读写锁的写锁;
  • 调用createHandlerMethod将Java方法包装成一个新的HandlerMethod对象,由此可见不是每个Java方法都是HandlerMethod,只有满足getMappingForMethod抽象方法中条件的才是;
  • 调用assertUniqueMethodMapping验证新的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.");
        }
    }
    
  • 通过唯一性验证后,新的映射和HandlerMethod被添加到MappingRegistry,可以在日志看到如Mapped xxx onto xxx 形式的输出;
  • 调用AbstractHandlerMethodMapping类的getMappingPathPatterns抽象方法提取该映射包含的URL路径,对其中的所有直接URL路径(即不需要模式匹配的路径),将直接URL路径与该映射的关联添加到MappingRegistry的urlLookup。注意对同一直接URL路径可能会有多个处理器方法映射与之对应,例如同一Controller的两个方法分别处理对/users不同HTTP方法的请求,这时urlLookup中的/users键就会对应两个映射;
    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和映射生成映射名,同时调用addMappingName方法将映射名与该HandlerMethod的关联添加到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;
  • 释放写锁。

经过上述分析,HandlerMethod的概念逐渐变得清晰。简单地说,它是对Java方法的包装,但不是所有的Java方法都能被包装成HandlerMethod去处理请求,只有符合某些条件的才是,这个条件的判断就在getMappingForMethod抽象方法中。

重写getHandlerInternal方法

AbstractHandlerMethodMapping类继承了AbstractHandlerMapping类,重写的getHandlerInternal方法如下,由lookupHandlerMethod方法完成查找匹配HandlerMethod的任务。

@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的后续过程。

查找匹配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检查映射是否与请求相匹配,若匹配则返回一个与当前请求有关的映射,否则返回null。addMatchingMappings方法遍历mappings表示的映射集合,将与请求相匹配的映射包装成Match添加到匹配集matches。从上述代码可以总结出查找匹配HandlerMethod的过程:

  1. MappingRegistry的getMappingsByUrl方法代码如下,是从URL到映射的多值Map中取值,这个Map保存的是注册映射时直接URL路径(即不需要模式匹配)与映射的关联。
    public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
    }
    
    从mappingRegistry获取请求路径对应的映射(不需要模式匹配),这时只看URL路径而不会去管HTTP请求方法等条件,接着将与请求相匹配的映射加入匹配集;
  2. 若匹配集为空则表示没有能与请求路径直接匹配的映射(不需要模式匹配),此时需要遍历mappingRegistry中的所有映射进行模式匹配;
  3. 若能匹配请求路径,那么对这些匹配排序,排序方式由子类决定。若匹配数量有两个以上且最佳匹配和次最佳匹配相同则报错;
    protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
    
  4. handleMatch相当于回调函数,表示有匹配时的执行动作;
    protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
        request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    }
    
  5. handleNoMatch也相当于回调函数,表示无匹配时的执行动作;
    protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
            throws Exception {
        return null;
    }
    

RequestMappingInfoHandlerMapping类和RequestMappingHandlerMapping类的分析请看下文。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容