Spring MVC 核心类: HandlerMapping(二)

上一篇
Spring MVC 核心类: HandlerMapping(一)

2. 详细说明

2.1 AbstractHandlerMapping

HandlerMapping 实现的基础类。这个基础类不支持暴露PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE 属性。这个属性的暴露取决于具体的子类实现,例如典型的通过URL映射来实现。

这个超类定义了默认handler、排序、拦截器的基本实现。

2.1.1 默认handler设置:

默认为null。主要用于当无法匹配到handler时,返回这个具体指定的handler。

    /**
     * Set the default handler for this handler mapping.
     * This handler will be returned if no specific mapping was found.
     * <p>Default is {@code null}, indicating no default handler.
     */
    public void setDefaultHandler(@Nullable Object defaultHandler) {
        this.defaultHandler = defaultHandler;
    }
2.1.2 顺序

默认为最低优先级,主要通过实现 Spring 框架中的 Ordered 接口来支撑。

    private int order = Ordered.LOWEST_PRECEDENCE;

    /**
     * Specify the order value for this HandlerMapping bean.
     * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
     * @see org.springframework.core.Ordered#getOrder()
     */
    public void setOrder(int order) {
        this.order = order;
    }
2.1.3 URL 处理

通过 UrlPathHelper ,把URL的处理能力转交出去,从而实现各种URL处理的能力,例如实现ANT模式的URL处理方式。同时也提供了便利的设置 UrlPathHelper 属性的方法,方便直接配置:

  1. 可以设置一直使用全路径匹配;
  2. 可以设置URL的decode操作;
  3. 可以设置是否移除URL中分号的内容

通过 UrlPathHelper, Spring 框架可以提供灵活的URL处理方式,后续 HandlerMapping 子类实现中会使用到具体的实例来完成对应的功能。

2.1.4 拦截器(interceptor)

1.首先可以设置拦截器

设置匹配这个Handler映射的所有拦截器。目前支持的拦截器类型有HandlerInterceptor、WebRequestInterceptor、MappedInterceptor 三种。通过路径匹配找到对应拦截器处理。

    /**
     * Set the interceptors to apply for all handlers mapped by this handler mapping.
     * <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
     * Mapped interceptors apply only to request URLs that match its path patterns.
     * Mapped interceptor beans are also detected by type during initialization.
     * @param interceptors array of handler interceptors
     * @see #adaptInterceptor
     * @see org.springframework.web.servlet.HandlerInterceptor
     * @see org.springframework.web.context.request.WebRequestInterceptor
     */
    public void setInterceptors(Object... interceptors) {
        this.interceptors.addAll(Arrays.asList(interceptors));
    }

2.初始化过程

首先 Spring 框架提供了在初始化或者是正式使用的扩展点。支持用户自定义增加或者其他想要的操作来处理将要被配置的拦截器列表。

再次是在Spring Bean 容器中查找 MappedInterceptor 所有实例,包含父 Spring Bean 容器中的所有 MappedInterceptor 实例。同时把查找到的MappedInterceptor添加到adaptedInterceptors列表中。当然这是默认实现,如果你想改变这种策略,Spring 框架是支持通过重写 detectMappedInterceptors 方法来扩展。

最后把把 interceptors 列表中的拦截器添加到 adaptedInterceptors中。放入代理的拦截器列表前会通过 MappedInterceptor 具体实现类型进行包装:如果直接实现HandlerInterceptor不包装;如果实现WebRequestInterceptor就使用 WebRequestHandlerInterceptorAdapter包装。在默认实现中仅仅支持这两种类型的处理,如果需要可以自己扩展。

通过上面的处理,adaptedInterceptors装载来所有的拦截器。

    /**
     * Initializes the interceptors.
     * @see #extendInterceptors(java.util.List)
     * @see #initInterceptors()
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }
2.1.5 获取Handler(getHandler)

根据Request,查找对应的Handler。当没有匹配的Handler时,使用指定的默认Handler。

    /**
     * Look up a handler for the given request, falling back to the default
     * handler if no specific one is found.
     * @param request current HTTP request
     * @return the corresponding handler instance, or the default handler
     * @see #getHandlerInternal
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // 如果是使用的BeanName,从spring容器中获取
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        ...

        return executionChain;
    }

实现中有两次扩展,可以供子类使用:

  1. getHandlerInternal :
    • 真正的执行通过指定的Request查找对应的Handler,当无法匹配到对应的Handler 时,返回null。通过上面的 getHandler 方法的调用,我们可以看出,当此方法返回 null 时,会触发默认 Handler 赋值。
    • 这个方法你可以返回一个已经包装好的HandlerExecutionChain。因为 HandlerExecutionChain 中已经存在了相关拦截器的配置,所以这些拦截器会动态和HandlerMapping中配置的拦截器进行合并。也就是说静态的拦截器会合并到已经提供的 HandlerExecutionChain 中。
  2. getHandlerExecutionChain
    • getHandler 方法在调用getHandlerInternal后获取到具体的handler后调用该方法。主要用于构建一个 Handler 处理器的处理链路。在构建这个处理链时,会选择合适的拦截器。
    • 构建一个默认标准的HandlerExecutionChain包含指定的Handler、通用映射对应的拦截器、以及和当前请求URL匹配的MappedInterceptor。拦截器的顺序依赖注册顺序的先后。如果需求,子类可以重写这个顺序来扩展/重新构建这个拦截器列表。
    • 这个方法支持用原始的Handler创建新的HandlerExecutionChain和传入的HandlerExecutionChain两种方式。
      如果仅仅是简单的在子类中增加一个拦截器,可以考虑调用super.getHandlerExecutionChain(handler, request),同时调用 HandlerExecutionChain 中的 addInterceptor方法。
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;
}

2.2 AbstractUrlHandlerMapping

用URL映射方式的基础HandlerMapping实现类。本类提供了通过URL或者配置的具体的URL查找方式的HandlerMapping实现的基础设施,对于后者,可以通过“alwaysUseFullPath”属性来了解更多。

支持直接匹配,例如配置 “/test” 来映射 “/test”;或者使用ANT形式来匹配,例如配置“/t”来映射“/test”和“/team”,使用“/test/”来匹配所有“/test”的直接目录下的URL,使用“/test/**”来匹配“/test”下面所有的URL包含子目录下的链接。更多配置详情,可以参考 AntPathMatcher 。

匹配过程中会查询所有匹配路径,同时找到最长匹配作为最精确的匹配。

同时此基础类定义了handlerMap,这个Map定义了URL映射关系。本基础类提供了Map注册的模版方法(registerHandler),供其子类调用。

2.2.1 设置根目录对应的Handler,即为“/”的handler。默认情况为null
/**
 * Set the root handler for this handler mapping, that is,
 * the handler to be registered for the root path ("/").
 * <p>Default is {@code null}, indicating no root handler.
 */
public void setRootHandler(Object rootHandler) {
    this.rootHandler = rootHandler;
}

2.2.2 覆盖getHandlerInternal 方法
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    Object handler = lookupHandler(lookupPath, request);
    // 当通过URL路径无法匹配时,优先处理根Handler,再使用默认Handler。
    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)) {
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    …
    return handler;
}

2.2.3 查到Handler (lookupHandler)
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    // 1. 如果可以通过URL直接映射,使用这个映射对应的Handler
    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);
    }

    // 2. 通过匹配进行映射
    List<String> matchingPatterns = new ArrayList<String>();
    // 2.1 找到所有的可能的映射对应的Handler
    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 + "/");
            }
        }
    }

    // 2.2 决策最优映射
    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,如果没有找到对应的Handler,报错
        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;
}

2.2.4 模版方法:registerHandler

通过给定的URL路径,注册指定的Handler。

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        ...
        Object resolvedHandler = handler;
        ...

        // 从HandlerMap中获取URL路径对应的Handler
        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            // 如果出现一个URL路径两个匹配的Handler的情况报错
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(...);
            }
        }
        else {
            // 如果是根目录的情况下,设置根目录处理器
            if (urlPath.equals("/")) {
                ...
                setRootHandler(resolvedHandler);
            }
            // 如果是根目录匹配的情况下,设置根目录处理器
            else if (urlPath.equals("/*")) {
                ...
                setDefaultHandler(resolvedHandler);
            }
            // 其他情况下,设置URL路径和处理器的映射
            else {
                this.handlerMap.put(urlPath, resolvedHandler);
                ...
            }
        }
    }

2.3 AbstractUrlHandlerMapping 子类

2.3.1 BeanNameUrlHandlerMapping

该类是 AbstractDetectingUrlHandlerMapping 的子类。在 AbstractDetectingUrlHandlerMapping 中定义了检测所有Bean的基础方法。其子类通过扩展 determineUrlsForHandler 方法进行判断是否需要进行URL注册。

    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                applicationContext.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);
            }
        }
        ...
    }

BeanNameUrlHandlerMapping 扩展了 determineUrlsForHandler,定义通过Bean名称来定义URL,通过配置Bean名称的别名(以“/”开头)来确定对应的URL:

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

这个类是通过配置URL到对应的处理器的Bean名称的方式的HandlerMapping。支持URL到bean实例和URL到bean名称两种方式。使用Bean名称映射时,仅仅支持该Bean为单例模式,否则无法通过Bean名称初始化。

配置URL到处理器Handler有两种方式:

1.通过属性文件配置,属性文件的的样子如下:

 /welcome.html=ticketController
 /show.html=ticketController

2.直接通过XML配置 urlMap

    /**
     * Set a Map with URL paths as keys and handler beans (or handler bean names)
     * as values. Convenient for population with bean references.
     * <p>Supports direct URL matches and Ant-style pattern matches. For syntax
     * details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
     * @param urlMap map with URLs as keys and beans as values
     * @see #setMappings
     */
    public void setUrlMap(Map<String, ?> urlMap) {
        this.urlMap.putAll(urlMap);
    }

同时本类定义了处理URL集合的方法

    /**
     * 触发解析URL配置
     */
    @Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }
    
    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.trace("No patterns in " + formatMappingName());
        }
        else {
            urlMap.forEach((url, handler) -> {
                // 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到Controller的映射关系。当然URL可以遵循 ANT 模式。

2.3.3 WelcomePageHandlerMapping

首先在构建时定义默认的Controller和根路径处理Handler:

    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
            ApplicationContext applicationContext, Optional<Resource> welcomePage,
            String staticPathPattern) {
        if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            logger.info("Adding welcome page: " + welcomePage.get());
            setRootViewName("forward:index.html");
        }
        else if (welcomeTemplateExists(templateAvailabilityProviders,
                applicationContext)) {
            logger.info("Adding welcome page template: index");
            setRootViewName("index");
        }
    }
    
    private void setRootViewName(String viewName) {
        ParameterizableViewController controller = new ParameterizableViewController();
        controller.setViewName(viewName);
        setRootHandler(controller);
        setOrder(2);
    }

再次覆盖 getHandlerInternal 方法,使用超类中通过URL匹配不到就判断是否为根路径同时使用根路径对应的处理Handler的逻辑,达到使用通用的欢迎页的Controller的目的。

    @Override
    public Object getHandlerInternal(HttpServletRequest request) throws Exception {
        for (MediaType mediaType : getAcceptedMediaTypes(request)) {
            if (mediaType.includes(MediaType.TEXT_HTML)) {
                return super.getHandlerInternal(request);
            }
        }
        return null;
    }

综上,这个类处理的核心是配置Index对应的Controller为默认的根处理Handler

下一篇:方法级别的映射分析
Spring MVC 核心类: HandlerMapping(三)

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

推荐阅读更多精彩内容