SpringMVC源码分析--HandlerMappings

之前分析过SpringMVC中的DispatcherServlet,分析了SpringMVC处理请求的过程。但忽略了一些DispatcherServlet协助请求处理的组件,例如SpringMVC中的HandlerMappingHandlerAdapterViewResolvers等等。

HandlerMappings

HandlerMappingsDispathServlet中主要作用是为请求的urlpath匹配对应的Controller,建立一个映射关系,根据请求查找HandlerInterceptorHandlerMappings将请求传递到HandlerExecutionChain上,HandlerExecutionChain包含了一个能够处理该请求的处理器,还可以包含拦截改请求的拦截器。

在没有处理器映射相关配置情况下,DispatcherServlet会为你创建一个BeanNameUrlHandlerMapping作为默认映射的配置。在DispatchServlet.properties文件中对于HandlerMapping的默认配置是:

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

HandlerMapping的配置策略一般分为配置式BeanNameUrlHandlerMapping和注解式DefaultAnnotationHandlerMapping。不过DefaultAnnotationHandlerMapping已经被放弃了,取代它的是RequestMappingHandlerMapping,不知道为啥SpringMVC这个默认配置尚未做修改。

AbstractHandlerMapping

AbstractHandlerMappingHandlerMapping的抽象实现,是所有HandlerMapping实现类的父类。

AbstractHandlerMapping的作用是是为了初始化InterceptorsAbstractHandlerMapping重写了WebApplicationObjectSupportinitApplicationContext方法。

    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }
  • extendInterceptors方法,Springmvc并没有做出具体实现,这里留下一个拓展,子类可以重写这个模板方法,为子类添加或者修改Interceptors

  • detectMappedInterceptors方法将SpringMVC容器中所有MappedInterceptor类的bean添加到adaptedInterceptors中。

  • 最后调用initInterceptors初始化拦截器。遍历interceptorsWebRequestInterceptorHandlerInterceptor类型的拦截器添加到adaptedInterceptors中。

HandlerMapping通过getHandler方法来获取请求的处理器Handler和拦截器Interceptor。在getHandlerExecutionChain方法中将遍历之前初始化的adaptedInterceptors,为当前的请求选择对应的MappedInterceptorsadaptedInterceptors

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping继承于AbstractHandlerMapping,它是通过URL来匹配具体的HandlerAbstractUrlHandlerMapping维护一个handlerMap来存储UrlHandler的映射关系。

AbstractUrlHandlerMapping重写了AbstractHandlerMapping类中的getHandlerInternal方法。HandlerMapping通过getHandler方法,就会调用这里的getHandlerInternal方法来获取HandlergetHandlerInternal方法中关键调用lookupHandler方法去获取handler

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

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

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

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

        // No handler found...
        return null;
    }
  • 首先调用lookupHandler方法来获取handler。在lookupHandler方法中,先通过URLhandlerMap查找是否有合适的handler
  • 如果没有获取到handler,遍历handlerMap利用正则匹配的方法,找到符合要求的handlers(有可能是多个)。
  • 正则匹配是采用Ant风格,将会通过排序筛选出一个匹配程度最高的Handler
  • 最后调用buildPathExposingHandler方法构建一个handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor两个拦截器并返回。

上面介绍获取handler的过程中,会先从handlerMap查找。下面看一下handlerMap是如何初始化的。AbstractUrlHandlerMapping是通过registerHandler初始化handlerMap的。AbstractUrlHandlerMapping共有两个registerHandler方法。分别是注册多个url到一个handler和注册一个url到一个handler。首先判断handlerMap是否有此handler。如果存在的话,判断是否一致,不一致则抛出异常,如果不存在的话,如果url//*,则,返回root handlerdefault handler,如果不是将添加到handlerMap中。

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping继承于AbstractUrlHandlerMappingSimpleUrlHandlerMapping重写了父类AbstractHandlerMapping中的初始化方法initApplicationContext。在initApplicationContext方法中调用registerHandlers方法。

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

判断是url是否以/开头,如果不是,默认补齐/,确保所有的url都是以/开头,然后依次调用父类的registerHandler方法注册到AbstractUrlHandlerMapping中的handlerMap

在使用SimpleUrlHandlerMapping时,需要在注册的时候配置其urlmap否则会抛异常。

AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping类继承于AbstractUrlHandlerMapping类,重写了initApplicationContext方法,在initApplicationContext方法中调用了detectHandlers方法。

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

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

获取所有容器的beanNames,遍历所有的beanName,调用determineUrlsForHandler方法解析url,这里的determineUrlsForHandler也是运用了模板方法设计模式,具体的实现在其子类中,如果解析到子类,将注册到父类的handlerMap中。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping类的类图大致如下:

BeanNameUrlHandlerMapping类继承于AbstractDetectingUrlHandlerMapping类。重写了父类中的determineUrlsForHandler方法。

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

其通过beanName解析Url规则也很简单,判断beanName是否以/开头。

BeanNameUrlHandlerMappingSpringMVC的默认映射配置。

AbstractHandlerMethodMapping

通常我们也习惯于用@Controller@Re questMapping来定义HandlerAbstractHandlerMethodMapping可以将method作为Handler来使用。

AbstractHandlerMethodMapping实现了InitializingBean接口,实现了afterPropertiesSet方法。当容器启动的时候会调用initHandlerMethods注册委托handler中的方法。


    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

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

  • 首先通过BeanFactoryUtils扫描应用上下文,获取所有的bean
  • 遍历所有的beanName,调用isHandler方法判断是目标bean是否包含@Controller@RequestMapping注解。
  • 对于带有@Controller@RequestMapping注解的类,调用detectHandlerMethods委托处理,获取所有的method,并调用registerHandlerMethod注册所有的方法。

detectHandlerMethods方法负责将Handler保存到Map中。

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

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }
    
    

selectMethods方法中重写了MetadataLookup中的inspect方法,inspect方法中调用了子类RequestMappingHandlerMapping实现了getMappingForMethod模板方法,用于构建RequestMappingInfo

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
        final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
        Class<?> specificHandlerType = null;

        if (!Proxy.isProxyClass(targetType)) {
            handlerTypes.add(targetType);
            specificHandlerType = targetType;
        }
        handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));

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

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

        return methodMap;
    }

selectMethods通过反射获取所有的方法,重写了doWith方法,将handler中的method和请求对应的RequestMappingInfo保存到methodMap中。

最终detectHandlerMethods将遍历这个methodMap,调用registerHandlerMethod注册HandlerMethodMappingRegistry

AbstractHandlerMethodMapping类中,有个内部类MappingRegistry,用来存储mappinghandler methods注册关系,并提供了并发访问方法。

AbstractHandlerMethodMapping通过getHandlerInternal来为一个请求选择对应的handler

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
       // 根据request获取对应的urlpath
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up handler method for path " + lookupPath);
        }
        // 获取读锁
        this.mappingRegistry.acquireReadLock();
        try {
          // 调用lookupHandlerMethod方法获取请求对应的HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            if (logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    logger.debug("Returning handler method [" + handlerMethod + "]");
                }
                else {
                    logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

lookupHandlerMethod的具体实现如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        // 通过lookupPath获取所有匹配到的path
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
           // 将匹配条件添加到matches
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // 如果没有匹配条件,将所有的匹配条件都加入matches
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
            if (logger.isTraceEnabled()) {
                logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                        lookupPath + "] : " + matches);
            }
            // 选取排序后的第一个作为最近排序条件
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                // 前两个匹配条件排序一样抛出异常
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                            request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                }
            }
            // 将lookupPath设为请求request的PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE属性
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

整个过程以Match作为载体,Match是个内部类,封装了匹配条件和handlerMethod两个属性,默认的实现是将lookupPath设置为请求的属性。

总结

本文从源码角度上分析了HandlerMapping的各种实现。主要功能是为请求找到合适的handlerinterceptors,并组合成HandlerExecutionChain。查找handler的过程通过getHandlerInternal方法实现,每个子类都其不同的实现。

所有的HandlerMapping的实现都继承于AbstarctHandlerMappingAbstarctHandlerMapping主要作用是完成拦截器的初始化工作。而通过AbstarctHandlerMapping又衍生出两个系列,AbstractUrlHandlerMappingAbstractHandlerMethodMapping

AbstractUrlHandlerMapping也有很多子类的实现,如SimpleUrlHandlerMappingAbstractDetectingUrlHandlerMapping。总体来说,AbstractUrlHandlerMapping需要用到一个保存urlhandler的对应关系的mapmap的初始化工作由子类实现。不同的子类会有自己的策略,可以在配置文件中注册,也可以在spring容器中找。

AbstractHandlerMethodMapping系列则通常用于注解的方法,解析包含@Controller或者@RequestMapping注解的类,建立urlmethod的直接对应关系,这也是目前使用最多的一种方式。

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

推荐阅读更多精彩内容

  • 你要知道的SpringMVC DispatcherServlet执行流程及源码分析都在这里 转载请注明出处 htt...
    WWWWDotPNG阅读 10,130评论 2 25
  • HandlerMapping的类继承图如下: 其中有一个DefaultAnnotationHanlerMappin...
    宙斯是只猫阅读 3,292评论 0 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 引言 一直以来都在使用Spring mvc,能够熟练使用它的各种组件。但是,它一直像个黑盒一样,我并不知道它内部是...
    yoqu阅读 883评论 0 24
  • 不见 有很多的话想说,心中的草稿已经满了 见 却发现我根本没有草稿箱。我慢慢的喜欢上她了,没有理由,当第一天看见...
    南乔枝010121阅读 131评论 0 0