SpringMVC 4.3 源码分析之 HandlerMapping

1. HandlerMapping 概述

HandlerMapping 的存在其实就是解决 HttpServletRequest 与 Handler 之间映射关系!对应的子类主要分为基于 BeanName, ClassName, 注解信息来获取对应的映射的请求! 其子类如下:


HandlerMapping.png
1. HandlerMapping:                 这个接口中定义了通过HttpServletRequest 来获取对应的 HandlerExecutionChain(PS: HandlerExecutionChain 中定义了请求的拦截器+最终调用的 Handler), 其中还有一个非常重要的变量 URI_TEMPLATE_VARIABLES_ATTRIBUTE, 这个key对应的value其实就是 uri template 变量(@PathVariable 解析属性时获取数据的来源)
2. AbstractHandlerMapping:         这个类继承了 WebApplicationObjectSupport, 这就让其具有通过 ApplicationContextAware 接口来触发初始化的功能, 当然其也在初始化方法中获取了 MappedInterceptor, 并且定义了通过 HttpServletRequest 获取 HandlerExecutionChain 的主逻辑 <-- 其也留下了获取 Handler 的模版方法 getHandlerInternal
3. AbstractHandlerMethodMapping:   通过 HttpServletRequest 来获取 HandlerMethod; 其通过 InitializingBean.afterPropertiesSet 来搜索获取所有 HandlerMethod <-- 这部分就是在HandlerMapping初始化时获取所有HandlerMethod; 并且留下了 getMappingForMethod 等获取 RequestMappingInfo 的模版方法
4. AbstractUrlHandlerMapping:      与 AbstractHandlerMethodMapping 不同, AbstractUrlHandlerMapping.getHandlerInternal 返回的是个 Object(PS: 这里的Object有可能是 ApplicationContext 中的 BeanName, 或直接是个 Bean), 而且其中完成了 URI 与 handler 的注册流程 registerHandler(urlPath, beanName)
5. RequestMappingHandlerMapping:   基于 HandlerMethod 的HandlerMapping, 主要实现了getMappingForMethod(基于Method,handlerType获取RequestMappingInfo), 而 RequestMappingInfoHandlerMapping 中主要是在查找 HandlerMethod 时对 HttpServletRequest 中的一些操作, 比如设置 uri template variable
6. SimpleUrlHandlerMapping:        在配置 uri 与handler 之间映射关系的 HandlerMapping (PS: 这里的 handler 可以是任意类型, 方正可以有对应的 HandlerAdapter 来进行激活它)
7. AbstractDetectingUrlHandlerMapping:  从类名中我们就可以获知, 这是一个自动获取 Handler 已经 url 的类, 这个方法的触发操作是 ApplicationContextAware.setApplicationContext
8. BeanNameUrlHandlerMapping:           以 BeanName 为 uri 的 HandlerMapping <-- 其中 BeanName 必需以 "/" 开头
9. ControllerBeanNameHandlerMapping:    通过 BeanName 构成 uri 的 HandlerMapping (PS: 这个类已经过期)
10. ControllerClassNameHandlerMapping:  基于 className 生成 uri 的 HandlerMapping(PS: 这个类已经过期)
11. DefaultAnnotationHandlerMapping:   基于 @RequestMapping 的 HandlerMapping, 这里面会出现 多个 urls 对应一个 Handler (PS: 这个类已经过期)
2. HandlerMapping 抽象类 AbstractHandlerMapping

AbstractHandlerMapping 中定义了获取 Handler 的主逻辑; 其主要有一下属性

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

// 默认请求处理器, 在找不到 Handler时返回的默认值
private Object defaultHandler;

// 获取 url 的工具类
private UrlPathHelper urlPathHelper = new UrlPathHelper();

// 路径匹配器 <- 进行正则之类匹配的操作
private PathMatcher pathMatcher = new AntPathMatcher();

// Handler 处理拦截器 <- 最后还是会将拦截器放入 adaptedInterceptors 中
private final List<Object> interceptors = new ArrayList<Object>();
// Handler 处理拦截器, 这个与 interceptors不同, 在封装 Chain 时其实使用的是 adaptedInterceptors
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();

// 跨域资源共享(Cross-origin resource sharing)
private CorsProcessor corsProcessor = new DefaultCorsProcessor();

从上面属性我们发现了 HandlerInterceptor, 这是一个作用于 handler 之前与之后的拦截器, 对应的获取操作是在 ApplicationContextAware.setApplicationContext 中, 方法也很简单, 直接通过ApplicationContext获取其中的所有 HandlerInterceptor 类型的 Bean

// 通过 initApplicationContext 方法进行初始化, 其一般是由父类执行 ApplicationContextAware#setApplicationContext() 方法间接调用
// 主要的目的是获取 springMVC 上下文中的拦截器集合, 特指 MappedInterceptor
@Override
protected void initApplicationContext() throws BeansException {
    // 供子类扩展添加拦截器, 目前 spring 没有自行实现
    extendInterceptors(this.interceptors);
    // 搜索 springMVC 中的 MappedInterceptors 保存至 adaptorInterceptors 集合
    detectMappedInterceptors(this.adaptedInterceptors);
    // 将 interceptors 集合添加至 adaptedInterceptors 集合
    initInterceptors();
}

而获取 HandlerExecutionChain 的主逻辑也在这里进行类实现, 当然留下了另外一个模版方法 getHandlerInternal(request)

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 获取到的 handler 对象一般为 bean/HandlerMathod, 它是方法为抽象方法, 供子类实现
    Object handler = getHandlerInternal(request);           // 正真调用的是 AbstractHandlerMethodMapping#getHandlerInternal()
    // 上述找不到则使用默认的处理类, 没有设定则返回 null, 则会返回前台 404 错误
    if (handler == null)  handler = getDefaultHandler();
    // 如果也没有提供默认的 handler, 则无法继续处理返回 null
    if (handler == null)  return null;
    // 若 handler 是 beanName, 则 通过名称取出对应的 Handler bean
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    // 创建处理链对象 <-- 其中就是封装 HandlerInterceptor 与 Handler
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    return executionChain;
}
3. AbstractHandlerMapping 的子类 AbstractHandlerMethodMapping

相对于AbstractHandlerMapping, AbstractHandlerMethodMapping 主要完成了Handler的获取操作, 对应代码在getHandlerInternal中, 通过 uri 与 HttpServletRequest 获取 HandlerMethod

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 获取访问的路径, 一般类似于 request.getServletPath() 返回不含 contextPath 的访问路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 获取读锁
    this.mappingRegistry.acquireReadLock();    // 获取读写所的读锁
    try {
        // 获取 HandlerMethod 作为 handler 对象, 这里涉及到路径匹配的优先级
        // 优先级: 精确匹配 > 最长路径匹配 > 扩展名匹配
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        // handlerMethod 内部含有 bean 对象, 其实指的是对应的 Controller
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        // 释放 度锁
        this.mappingRegistry.releaseReadLock();
    }
}

上面是对应的获取逻辑, 与之对应的还有HandlerMethod 的注册逻辑, 注册的入口在 InitializingBean.afterPropertiesSet, 大体流程如下:

1. 获取 Spring 中所有注册的 Bean 的Name, 并且一个一个获取对应 BeanType
2. 判断BeanType 是否被注解 @Controller 或 @RequestMapping 注解, 若是的话, 则进行解析这个 BeanType 
3. 筛选 BeanType 中被 @RequestMapping 的方法 

对应的代码如下:

protected void initHandlerMethods() {
    // 获取 springMVC 上下文的所有注册的 Bean
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) { // 循环遍历 ApplicationContext 中的 所有 beanName
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {}
            if (beanType != null && isHandler(beanType)) { // isHandler() 是抽象方法, 主要供子类 需要扫描什么类型的 bean <-- 判断是否是 Handler
                // 解析其中的 handlerMethod 进行注册 <-- 其中注册中会注册到好几个 Map 中 RequestMappingInfo <-> HandlerMethod, URI <-> HandlerMethod, name <-> HandlerMethod, RequestMappingInfo <-> MappingRegistration
                detectHandlerMethods(beanName);
            }
        }
    }
}

protected void detectHandlerMethods(final Object handler) {
    // 获取 handler 的类型 <- 若 handler 是 String, 则通过 ApplicationContext 直接获取 真实的 Bean
    Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
    // 因为有些是 CGLIB 代理生成的, 获取真实类
    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); // 模板方法获取 handlerMethod 的 mapping 属性
                    }
                    catch (Throwable ex) {}
                }
            });
    for (Map.Entry<Method, T> entry : methods.entrySet()) { // 对查找到的 HandlerMethod 进行注册, 默认就是 被@RequestMapping 注解修饰的,保存到内部类mappingRegistry 对象中
        // 做下判断, method 是否从属于 userType
        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
        T mapping = entry.getValue(); // 获取 RequestMappingInfo 的信息
        registerHandlerMethod(handler, invocableMethod, mapping); // 将 RequestMappingInfo <-> HandlerMethod, URI <-> HandlerMethod, name <-> HandlerMethod, RequestMappingInfo <-> MappingRegistration
    }
}

其上只是获取 Method 的操作, 而对应正真注册的工作交给 MappingRegistry 来完成, 而 MappingRegistry 中有如下属性:

1. registry: RequestMappingInfo <--> MappingRegistration 的映射关系(MappingRegistration 是包含 HandlerMethod 与 RequestMappingInfo 的包装类)
2. mappingLookup: RequestMappingInfo <--> HandlerMethod 的映射关系
3. urlLookup: uri <--> HandlerMethod 的映射关系
4. nameLookup: name <--> HandlerMethod 的映射关系
5. readWriteLock: 读写锁, 在多线程环境中对共享资源(映射关系), 读写权限的控制

对应的注册逻辑如下:

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();                 // 获取写锁
    try {                                                  // 封装 HandlerMethod <-- 其中有 Method, Bean, 以及对应的 MethodParameter
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping); // 断言 mapping 没有注册过

        if (logger.isInfoEnabled()) {
            logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
        }
        this.mappingLookup.put(mapping, handlerMethod);    // 将 RequestMethodInfo 与 HandlerMethod 放入 mappingLookup 中

        List<String> directUrls = getDirectUrls(mapping);  // 获取 @RequestMapping 中 value | path 中的信息 <-- 并且不能含有 *|? (PS: 一个 RequestMapping 具有多个 path|value)
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);              // 将 @RequestMapping 中的 value|path 注册到 urlLookup 中
        }

        String name = null;                                // 获取 @RequestMapping 中的 name 值
        if (getNamingStrategy() != null) {                 // nameStrategy 其实就是 RequestMappingInfoHandlerMethodMappingNamingStrategy
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);           // 将 name <--> handlerMethod 加入 nameLookup
        }
        // Cors 跨域访问的处理
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // 将 RequestMappingInfo 与 MappingRegistration 注册到 registry 中
        this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}
4. AbstractHandlerMethodMapping 的子类 RequestMappingHandlerMapping

RequestMappingHandlerMapping 是一个收集被@RequestMapping修饰的方法的 HandlerMapping, 相对其父类其主要完成了一下功能:

1. 根据 Method, handlerType, 收集@RequestMapping,  并以此创建创建 RequestMappingInfo, 其中涉及到 RequestMappingInfo中的建造者模式(建造RequestMappingInfo), 策略模式(针对 @RequestMapping 中数据的不同条件判断器 RequestCondition)
2. 根据 beanType 上是否被 @Controller | @RequestMapping注解修饰来判断 beanType 是否是符合的 Handler

主要代码如下:

protected boolean isHandler(Class<?> beanType) { // class 是否是 Handler
    // 通过 @Controller @RequestMapping 来进行匹配
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

在这里还提到了将 @RequestMapping 解析后得到的对象 RequestMappingInfo, 这个类中包含了 @RequestMapping 注解中各个属性的条件匹配器(匹配器主要有 combine: 组合两个匹配器, getMatchingCondition: 得到两个RequestCondition都匹配的 RequestCondition), 下面是其主要属性:

// @RequestMapping 中的信息
private final String name;
// @RequestMapping 中 value|path 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final PatternsRequestCondition patternsCondition;
// @RequestMapping 中 RequestMethod 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final RequestMethodsRequestCondition methodsCondition;
// @RequestMapping 中 param 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final ParamsRequestCondition paramsCondition;
// @RequestMapping 中 headers 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final HeadersRequestCondition headersCondition;
// @RequestMapping 中 consumes 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final ConsumesRequestCondition consumesCondition;
// @RequestMapping 中 produces 的解析器, 主要用于解析 HttpServletRequest 中的信息, 并进行匹配
private final ProducesRequestCondition producesCondition;
// RequestCondition 的 holder
private final RequestConditionHolder customConditionHolder;

PS: 在进行查找HttpServletRequest对应的HandlerMethod 时, 会通过 RequestMappingInfo.getMatchingCondition 来获取与 HttpServletRequest & @RequestMapping 都匹配的 RequestMappingInfo, 有的话表示找到了需要的Handler

5. AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping

相较于 AbstractHandlerMapping, AbstractUrlHandlerMapping中完成了

1. getHandlerInternal方法, Handler 的获取(PS: 这里handler 可以是任意类型);
2. lookupHandler方法, 通过 urlPath, HttpServletRequest 获取对应的 Handler, 这里也包含了 "URI 模版变量" 的获取(PS: 通过 AntPathMatcher.extractUriTemplateVariables) 
3. registerHandler方法, 将 urlPath, handler 注册到 handlerMap 中

getHandlerInternal中定义了获取 Handler 的主逻辑, 若获取不到且uri是"/"则使用 rootHandler, 否则使用 defaultHandler; 代码如下:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    // 从 Request 中得到 请求的 URL 路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

    // 将得到的 URL 路径与 Handler 进行匹配, 得到对应的 Handler, 如果没有对应的 Handler, 返回 null, 这样默认的 Handler 会被使用
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
        // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if ("/".equals(lookupPath)) {
            // 如果请求的路径仅仅是 "/", 那么使用 RootHandler 进行处理
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            // 无法找到 Handler, 则使用默认的 Handler
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // 根据 beanName 找到对应的 bean
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            // 模板方法 校验 hanlder 是否合法, 比如 DefaultAnnotationHandlerMapping 中校验类上面是否有 @RequestMapping 注解
            validateHandler(rawHandler, request);
            // 将 rawHandler HandlerInterceptor 包装到 chain 中 <- 其中涉及到 暴露 URI 模版变量 <-- 就是 www.baidu.com/{gropuId}/{userId}/{pageNo} <-- 中 groupId, userId, pageNo 的值, 其实就是 @PathVariable 这个注解解析时用到的值
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    return handler;
}

但真正的通过 urlPath 来进行直接匹配或通配符匹配是在 lookupHandler 的方法中, 并且方法中还有提取 uri 模版变量的步骤, 提取好后就直接存储在 HttpServletRequest 中

// lookupHandler  根据 URL 路径启动在 handlerMap 中对 handler 的检索, 并最终返回 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) {                         // 若 handler 是 string 类型, 则将 handler 当作类名, 直接从 BeanFactory 中获取 Bean
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        // 模板方法 校验 hanlder 是否合法, 比如 DefaultAnnotationHandlerMapping 中校验类上面是否有 @RequestMapping 注解
        validateHandler(handler, request);
        // 将 rawHandler HandlerInterceptor 包装到 chain 中 <- 其中涉及到 暴露 URI 模版变量 <-- 就是 www.baidu.com/{gropuId}/{userId}/{pageNo} <-- 中 groupId, userId, pageNo 的值, 其实就是 @PathVariable 这个注解解析时用到的值
        return buildPathExposingHandler(handler, urlPath, urlPath, null);  // <-- 最后一个参数是 null, 则 URI 模版参数就没有了
    }

    // 通配符匹配的处理
    // Pattern match?
    List<String> matchingPatterns = new ArrayList<String>();
    for (String registeredPattern : this.handlerMap.keySet()) {
        if (getPathMatcher().match(registeredPattern, urlPath)) { // 通过 AntPathMatcher 来进行匹配 <-- 正则匹配
            matchingPatterns.add(registeredPattern);              // 匹配成功, 加入 matchingPatterns <-- matchingPatterns 里面可能有多个值
        }
        else if (useTrailingSlashMatch()) {                       // 是否使用尾部反斜杆进行匹配, 若是的话, 则直接在尾部加上 "/" 再进行匹配
            if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                matchingPatterns.add(registeredPattern +"/");     // 匹配成功, 加入 matchingPatterns <-- matchingPatterns 里面可能有多个值
            }
        }
    }

    String bestMatch = null;                                      // 获取 AntPatternComparator, 主要是处理多个 urlPath 的排序
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if (!matchingPatterns.isEmpty()) {                            // 通过 AntPatternComparator 进行排序, 获取排序的第一个值
        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);                 // 从 handlerMap 从获取 bestMatch 匹配的 handler
        if (handler == null) {
            if (bestMatch.endsWith("/")) {                        // 若获取不到, 但 bestMatch 又是以 "/" 结尾的, 则去除 "/", 再从 handlerMap 里面获取一次
                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) {                          // 若从 handlerMap 里面获取出的 handler 是 String, 则再从 ApplicationContext 里面获取对应的 Bean
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);                        // 模板方法 校验 hanlder 是否合法, 比如 DefaultAnnotationHandlerMapping 中校验类上面是否有 @RequestMapping 注解
        // 获取 正则表达式中 *, ? 所代表的真实字符串
        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                                        // 获取 URI 模版变量的值
        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);
            }
        }
        // 将 rawHandler HandlerInterceptor 包装到 chain 中 <- 其中涉及到 暴露 URI 模版变量 <-- 就是 www.baidu.com/{gropuId}/{userId}/{pageNo} <-- 中 groupId, userId, pageNo 的值, 其实就是 @PathVariable 这个注解解析时用到的值
        return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
    }
    // No handler found...
    return null;
}

接着就是registerHandler, 将 url <--> handler 注册到映射容器 handlerMap 中, 整个流程比较简单, 若 urlPath 是 "/", 则设置为 rootHandler, 若 urlPath 是 "/*", 则设置为 defaultHandler, 其他的则直接设置到 handlerMap(PS: 从中我们也可以看出这个 HandlerMapping 其实不是发安全的, 从 handlerMap 是个 HashMap 可以看出)

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    Object resolvedHandler = handler;
    // 若 handler 是 String (BeanName), 是否需要将其初始化成 Bean
    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (getApplicationContext().isSingleton(handlerName)) { // 从 ApplicationContext 拿出 Handler
            resolvedHandler = getApplicationContext().getBean(handlerName);
        }
    }
    // 是否已存在对应的 handler, 若存在, 且里面存储的 handler 与现在将注入进去的不同, 则抛出异常
    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
        if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
        }
    }
    else { // 不存在 handler
        // 处理 URL 是 "/" 地映射, 把这个 "/" 映射地 controller 设置到 rootHandler 中
        if (urlPath.equals("/")) {
            // "/" --> 设置为 rootHandler
            setRootHandler(resolvedHandler);
        }
        // 处理 URL 是 "/*" 地映射, 把这个 "/*" 映射地 controller 设置到 defaultHandler 中
        else if (urlPath.equals("/*")) {
            // 对 "/*" 的匹配 设置默认的 handler
            setDefaultHandler(resolvedHandler);
        }
        // 处理正常地 URL 映射, 设置 handlerMap 的 key 和 value, 分别对应 URL 和 映射的 controller
        else {
            // 其余 的路径绑定关系则存入 handlerMap
            this.handlerMap.put(urlPath, resolvedHandler);
        }
    }
}
6. AbstractUrlHandlerMapping 的子类 SimpleUrlHandlerMapping

SimpleUrlHandlerMapping 就像其名字一样, 一个简单的 HandlerMapping, 一般都是在xml文件中配置一个, 配置的内容主要是 mappings <-- 这里包含了 urlPath 与 handler 的映射关系, 下面是一个简单的配置文件内容

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    // 处理 /* 的handler
    <property name="defaultHandler"><ref local="starController"/></property>  
    // 处理 / 的 handler
    <property name="rootHandler"><ref local="mainController"/></property> 
    // 是否使用尾部斜杆匹配url
    <property name="useTrailingSlashMatch" value="true"/>   
    <property name="urlMap">
        <map>
            <!-- 处理 /welcome 的是 welcomeController -->
            <entry key="/welcome"><ref local="welcomeController"/></entry>
        </map>
    </property>
</bean>
7. AbstractUrlHandlerMapping 的子类AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping 从其名字中我们也可以看出, 这是一个自动获取 URL 的HandlerMapping, 同样对应的触发还是通过 ApplicationContextAware.setApplicationContext 方法!

// 注册所有在 ApplicationContext 中的 Handler
protected void detectHandlers() throws BeansException {
    // 获取 ApplicationContext 中所有的 beanNames
    String[] beanNames = (this.detectHandlersInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));
    // 根据 beanName 获取所有的 urls, 并注册到 AbstractUrlHandlerMapping.handlerMap 中
    // Take any bean name that we can determine URLs for.
    for (String beanName : beanNames) {
        String[] urls = determineUrlsForHandler(beanName);      // 通过  beanName 获取 url, 比如 BeanNameUrlHandlerMapping 是以 beanName 为 url <-- beanName 必需以 "/" 开头
        if (!ObjectUtils.isEmpty(urls)) {
            // URL paths found: Let's consider it a handler.
            registerHandler(urls, beanName);
        }
    }
}
8. AbstractDetectingUrlHandlerMapping 的子类
1. BeanNameUrlHandlerMapping: 一个把beanName当作urlPath的HandlerMapping(PS: BeanName必需以 "/" 开头)
2. ControllerClassNameHandlerMapping:  基于 beanName 生成 uri 的 HandlerMapping(PS: 此类已经废弃)
3. ControllerBeanNameHandlerMapping: 基于 className 生成 uri 的 HandlerMapping(PS: 此类已经废弃)
4. DefaultAnnotationHandlerMapping: 扫描在 Bean 上注解 @RequestMapping|@Controller 的 类, 收集这个类中所有被 @RequetsMapping 注解修饰的方法, 并将 urlPaths <---> handlerType 注册到AbstractUrlHandlerMapping.handlerMap中(PS: 不过此类也已经废弃)
9. HandlerMapping 中的优秀设计
1. 模版模式: 在Spring中这是一个非常常见的设计模式, 比如 AbstractDetectingUrlHandlerMapping.detectHandlers, 其中定义了获取handler的主逻辑, 但留下了获取 url 的模版方法, 在其子类中进行类相应的实现, 子类中有根据 beanClass|beanName, 也有根据子类方法上的 @RequestMapping 注解
2. 建造者模式: HandlerMapping中主要体现在创建RequestMappingInfo的过程, 通过建造者 RequestMappingInfo.DefaultBuilder 来创建, 当创建一个对象时需要很多属性设置时就可以用 建造者模式
3. 策略模式: 这里的策略模式体现在 RequestCondition上(AbstractRequestCondition: 主要是将@RequestMapping上属性与 HttpServletRequest进行条件匹配, 最终找出两者都符合的 RequestMappingInfo)
4. 组合模式: CompositeRequestCondition 将所有 RequestCondition组合起来, 构成一个 RequestCondition来进行匹配操作
5. 责任链模式: 这个主要体现在 HandlerExecutionChain上, 链条的里面都是 interceptor, 最后执行的才是 Handler 
10. 总结

整个 HandlerMapping 体系的设计融合了常见的设计模式以及Spring IOC中的扩展接口 ApplicationContextAware, 完成了 urlPath 与 handler 之间映射的注册, 搜索工作!

11. 参考资料

SpringMVC源码分析系列
Spring MVC源码剖析
Spring源码情操陶冶
Spring 揭秘
Spring 技术内幕
Spring 源码深度分析
看透 Spring MVC 源代码分析与实践

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,441评论 1 133
  • 半水微风半水澜,半江芦苇倚风谙。闲人半坐执竿钓,半似望鱼半似眠。
    月夕华阅读 149评论 0 0
  • 上一篇只是主要说了springboot启动流程。刚刚想起来里面的tomcat啥时候启动的,还有设置applicat...
    lijiaccy阅读 3,080评论 1 3