SpringMVC源码系列——AbstractUrlHandlerMapping

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringMVC源码系列——AbstractUrlHandlerMapping

微信公众号:glmapper工作室 如有问题或建议,请公众号留言 座右铭:开放 开源 分享 共享

AbstractUrlHandlerMapping是通过url来进行匹配的,也就是说通过url与对应的Handler包存到一个Map中,然后在getHandlerInternal方法中使用url作为key从Map中获取我们的handler。

AbstractUrlHandlerMapping实现了从url获取handler的过程,具体的映射关系,也就是handlerMap则是交给具体子类来去完成的。 AbstractUrlHandlerMapping中定义了handlerMap用来维护映射关系,如下:


private final MapString, Object handlerMap = 
new LinkedHashMapString, Object();

除此之外,还有一个rootHandler,这个用于处理“/”请求。

在前面三篇文章中提到过,handler的获取是通过getHandlerInternal方法完成的,下面看下具体的源码,分析下handler的获取和handlerMap的构建。


//查找给定请求的URL路径的Handler。
protected Object getHandlerInternal(HttpServletRequest request) throws
Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    //使用lookupPath从Map中查找handler
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
        //临时变量,保存原始的handler
        Object rawHandler = null;
        //是否是‘/’根路径
        if ("/".equals(lookupPath)) {
            //获取rootHandler
            rawHandler = getRootHandler();
        }
        //如果rawHandler是null
        if (rawHandler == null) {
            //获取默认的handler
            rawHandler = getDefaultHandler();
        }
        //如果rawHandler不是null
        if (rawHandler != null) {
            // 如果是string类型,则到容器中查找具体的bean
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                //容器中获取
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            //校验handler和request是否匹配
            validateHandler(rawHandler, request);
            //注册拦截器
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    //日志debug
    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    //返回handler
    return handler;
}

getHandlerInternal方法中有几个方法调用,像getLookupPathForRequest、getRootHandler、getDefaultHandler、lookupHandler、buildPathExposingHandler等。其中getLookupPathForRequest、getRootHandler、getDefaultHandler这几个没啥好说的;比较核心的就是lookupHandler、buildPathExposingHandler这两个方法。

  • lookupHandler lookupHandler使用getUrlPathHelper().getLookupPathForRequest(request)获取到的lookupPath从Map中查找需要的Handler,通常情况下是直接get不到的。为什么呢?原因在于很多的handler都是使用了Pattern的匹配模式,比如说“/user/*”,星号表示匹配任意内容,并非是指定url串中的字符。如果Pattern中包含了PathVariable,也不能直接从Map中获取到。 除此之外,一个url还可能和多个Pattern相匹配,那么这个时候咱们肯定就需要选择最优的,所以说查找过程其实并不是直接从map中获取那么简单。那么就来看下在lookupHandler中都干了哪些事情:
  • lookupHandler使用getUrlPathHelper().getLookupPathForRequest(request)获取到的lookupPath从Map中查找需要的Handler,通常情况下是直接get不到的。为什么呢?原因在于很多的handler都是使用了Pattern的匹配模式,比如说“/user/*”,星号表示匹配任意内容,并非是指定url串中的字符。如果Pattern中包含了PathVariable,也不能直接从Map中获取到。

    
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // 直接匹配,直接从Map中获取
        Object handler = this.handlerMap.get(urlPath);
        //取到了
        if (handler != null) {
            // 如果是string类型,则从容器中获取Bean
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            //验证是否匹配
            validateHandler(handler, request);
            //注册拦截器
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }
        // Pattern 匹配,带*号的模式与url进行匹配
        ListString matchingPatterns = new ArrayListString();
        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 bestPatternMatch = null;
        ComparatorString patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            Collections.sort(matchingPatterns, patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestPatternMatch = matchingPatterns.get(0);
        }
        //最佳匹配不为null
        if (bestPatternMatch != null) {
            //从Map中看看是否有对应的Handler  
            handler = this.handlerMap.get(bestPatternMatch);
            //如果Map中没有
            if (handler == null) {
            //是否以/结尾
                Assert.isTrue(bestPatternMatch.endsWith("/"));
                //去除/之后再获取一次
                handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
            }
            // 如果是String类型,则从容器中获取Bean?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            //验证是否匹配
            validateHandler(handler, request);
            String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
    
            // 可能有多种最佳模式,让我们确保我们有正确的URI模板变量(译)
            MapString, String uriTemplateVariables = new LinkedHashMapString, String();
            for (String matchingPattern : matchingPatterns) {
                if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
                    MapString, String vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                    MapString, 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, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
        }
        // No handler found...
        return null;
    }
    

    上面代码中,关于译注的部分需要说一下;代码如下:

    
    MapString, String uriTemplateVariables = new LinkedHashMapString, String();
    for (String matchingPattern : matchingPatterns) {
        if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
            MapString, String vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
            MapString, String decodedVars =
            getUrlPathHelper().decodePathVariables(request, vars);
            uriTemplateVariables.putAll(decodedVars);
        }
    }
    

    之前是通过sort方法进行排序的,然后将第一个作为bestPatternMatch,但是如果多个pattern的顺序相同,也就是说sort返回的是0,存在多种最佳匹配,那就需要确保我们有正确的URI模板变量。上面代码就是处理这种情况的。

  • buildPathExposingHandler 这个方法在上面的两段代码中都频繁出现,那么这个方法到底有什么作用呢?代码中我注释的是注册拦截器,那么注册的又是什么拦截器?带着这两个问题,我们来看下代码。
  • 这个方法在上面的两段代码中都频繁出现,那么这个方法到底有什么作用呢?代码中我注释的是注册拦截器,那么注册的又是什么拦截器?带着这两个问题,我们来看下代码。

    
    //buildPathExposingHandler为给定的rawHandler构建一个Handler对象,并在执
    //行处理程序之前暴露实际的处理程序PATH_WITHIN_HANDLER_MAPPING_ATTRIBUT
    //E以及URI_TEMPLATE_VARIABLES_ATTRIBUTE。
    
    //默认实现用一个特殊的拦截器构建一个HandlerExecutionChain,该拦截器暴露
    //path属性和uri模板变量。
    protected Object buildPathExposingHandler(Object rawHandler, 
        String bestMatchingPattern,
        String pathWithinMapping, 
        MapString, String uriTemplateVariables) {
    
        HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
        chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
        if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
            chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
        }
        return chain;
    }
    

    四个参数:

  • rawHandler 原始处理程序
  • bestMatchingPattern 最佳匹配模式
  • pathWithinMapping 在执行Handler之前公开的路径
  • uriTemplateVariables 如果没有找到变量,URI模板变量可以是{null}
  • 从代码注释翻译及代码内容可以了解到,buildPathExposingHandler的作用就是给已经查找到的handler注册两个拦截器

  • ExposingHandlerInterceptor
  • UriTemplateVariablesHandlerInterceptor
  • 这两个类均是 AbstractUrlHandlerMapping的内部类,也就是两个内部拦截器。这两个拦截器的主要作用就是将与当前url实际匹配的pattern、匹配条件以及url模板参数等设置到request的属性里面去,这样在后面的处理过程中就可以直接从request属性中获取。看下两个内部类的定义:

    
    private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
    
        private final String bestMatchingPattern;
        private final String pathWithinMapping;
        public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
            this.bestMatchingPattern = bestMatchingPattern;
            this.pathWithinMapping = pathWithinMapping;
        }
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            exposePathWithinMapping(this.bestMatchingPattern,
            this.pathWithinMapping, request);
            //设置request属性
            request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
            return true;
        }
    }
    
    
    private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
    
        private final MapString, String uriTemplateVariables;
        public UriTemplateVariablesHandlerInterceptor(MapString, String uriTemplateVariables) {
            this.uriTemplateVariables = uriTemplateVariables;
        }
        @Override
        public boolean preHandle(HttpServletRequest request,
        HttpServletResponse response, Object handler) {
        //这exposeUriTemplateVariables种设置request属性
            exposeUriTemplateVariables(this.uriTemplateVariables, request);
            return true;
        }
    }
    

    从内部类的代码可以看出,这两个内部类是通过在preHandle方法中调用exposePathWithinMapping和exposeUriTemplateVariables完成属性设置到request中的。

    对于查找handler的关键其实就是维护url和handler的映射关系,也就是handlerMap的构建。在 AbstractUrlHandlerMapping中是通过registerHandler这个方法来构建handlerMap的。 AbstractUrlHandlerMapping提供了两个registerHandler方法,下面就通过代码来看下具体的实现。

    
    protected void registerHandler(String[] urlPaths, String beanName)
        throws BeansException, IllegalStateException {
        Assert.notNull(urlPaths, "URL path array must not be null");
        for (String urlPath : urlPaths) {
            registerHandler(urlPath, beanName);
        }
    }
    

    第一个registerHandler是将多个url注册到一个处理器。beanName其实就是咱们处理器的名称,可以通过beanName到容器中去找到真正的处理器Bean。具体处理就是通过遍历所有的url,然后再通过调用第二个registerHandler将handler注册到handlerMap中。来看第二个:

    
    protected void registerHandler(String urlPath, Object handler) throws
    BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;
    
        // 如果的handler是string类型,并且不是lazyInitHandlers,则从SpringMV
        //C容器中获取handler
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            if (getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = getApplicationContext().getBean(handlerName);
            }
        }
    
        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                //异常处理
            }
        }
        else {
            //是否是跟路径
            if (urlPath.equals("/")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Root mapping to " +
                    getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            //是否是*模式
            else if (urlPath.equals("/*")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Default mapping to " +
                    getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            //加入到handlerMap中
            else {
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isInfoEnabled()) {
                    logger.info("Mapped URL path [" + urlPath + "] onto " +
                    getHandlerDescription(handler));
                }
            }
        }
    }
    

    这个里面首先是看Map中是否有原来传入的url,如果没有就加入,如果有就看下原来保存的和当前注册的handler是否是同一个,如果不是同一个就抛出异常。(同一个url不可能存在两个不同的handler)。

    在put之前,也做了一些“/”和“/*”的验证处理,如果是这两种路径的话就不保存到handlerMap中了。

  • “/”:setRootHandler(resolvedHandler);
  • “/*”:setDefaultHandler(resolvedHandler);
  • OK,到这 AbstractUrlHandlerMapping这个类就分析完了,其实 AbstractUrlHandlerMapping做的事情就是定义了一个框子,子类只要完成对Map的初始化就可以了。关于 AbstractUrlHandlerMapping的子类后续再谈。

    原文始发于微信公众号(glmapper工作室):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> SpringMVC源码系列——AbstractUrlHandlerMapping


     上一篇
    Spring源码系列——注解详解 Spring源码系列——注解详解
    因为要看Spring中注解的具体定义,所以在说之前,先来简单说下JAVA中注解的一些基本知识。 元注解什么是元注解呢,就是注解的注解。java中提供了以下几种: @Target 注解的作用域描述 public enum ElementTy
    2021-04-05
    下一篇 
    Spring Context 你真的懂了吗 Spring Context 你真的懂了吗
    今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么我们经常在编程中见到 context 这个单词,当然每个人有每个人的理解,它
    2021-04-05