SpringMVC的URL映射器注册篇之BeanNameUrlHandlerMapping

写在前面

上一篇,我们讲解了 SpringMVC的URL映射器之SimpleUrlHandlerMapping,列举了常见的配置方法,并且分析了主要源码。这一篇我们来分析另一个 URL 映射器。

概述 BeanNameUrlHandlerMapping


上图是 BeanNameUrlHandlerMappping 的继承层次图。

  • HandlerMapping 是通用接口,包含一个 getHandler(req:HttpServletRequest):HandlerExecutionChain 方法,该方法用来寻找请求对应的处理器链

  • AbstractHandlerMapping,用 interceptors 保存拦截器,并负责选取拦截器并加入到 HandlerExecutionChain 当中

  • AbstractUrlHandlerMapping,用 handlerMap 保存 url 和 “Handler” 之间的映射

  • AbstractDetectingUrlHandlerMapping,从 Spring 容器中检出 URL 映射

  • BeanNameUrlHandlerMapping,筛选出名称以“/”开头的 Bean,并把这些 Bean 的名称组成一个列表。

使用 BeanNameUrlHandlerMapping 的方式也十分简单,延续了上一篇文章的代码,只是替换了 spring-mvc.xml 中的内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
      <!-- Fixed problem : javax.servlet.ServletException: No adapter for handler [coderead.springframework.mvc.LoginHttpServlet@2f2f30df]-->
      <bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/>
      <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
      <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

      <!--注入 BeanNameUrlHandlerMapping Bean-->
      <bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

      <!--使用 name 可以创建一个或者多个在 id 标签中“非法”的别名-->
      <bean name="/welcome" class="coderead.springframework.mvc.WelcomeController" />
      <bean name="/hello" class="coderead.springframework.mvc.HelloGuestController" />
      <bean name="/hi" class="coderead.springframework.mvc.HelloLuBanHttpRequestHandler" />
      <bean name="/login" class="coderead.springframework.mvc.LoginHttpServlet" />
</beans>

源码解析

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping # determineUrlsForHandler 点击展开看源码

protected String[] determineUrlsForHandler(String beanName) {
      List urls = new ArrayList<>();
      // bean 的名称以 “/” 开头
      if (beanName.startsWith("/")) {
            urls.add(beanName);
      }
      String[] aliases = obtainApplicationContext().getAliases(beanName);
      for (String alias : aliases) {
            // bean 的别名以 “/” 开头
            if (alias.startsWith("/")) {
                  urls.add(alias);
            }
      }
      return StringUtils.toStringArray(urls);
}

看完这段源码,我就在想:什么情况下,是通过 beanName 判断的,什么时候是根据 beanName 的别名判断的?

第一种:单独指定 id 或者 name

<bean id="/welcome" class="coderead.springframework.mvc.WelcomeController" />
<bean name="/hello" class="coderead.springframework.mvc.HelloGuestController" />

此时,容器把 <bean> 唯一的 name 或者 id 作为 beanName。

第二种:即指定 id 又指定 name

<bean id="welcomeController" name="/welcome" class="coderead.springframework.mvc.WelcomeController" />

此时,容器把 "welcomeController" 作为 beanName,把 "/welcome" 作为别名,就会走到下面的循环中。

综上所述,生产中我更推荐第二种配置方式,这样更符合一般习惯。

AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping # detectHandlers 点击展开看源码

protected void detectHandlers() throws BeansException {
        // 得到应用程序上下文,该上下文是由 Spring 容器通过 {@link ApplicationContextAware} 调用以注入当前应用程序的
	ApplicationContext applicationContext = obtainApplicationContext();
        // detectHandlersInAncestorContexts 默认是 false
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
			applicationContext.getBeanNamesForType(Object.class));
	// 获取所有的 beanName 用来决定 URLs
	for (String beanName : beanNames) {
                // 由子类来决定 Handler 的 url 集合
		String[] urls = determineUrlsForHandler(beanName);
		if (!ObjectUtils.isEmpty(urls)) {
			// URL paths found: Let's consider it a handler.
			registerHandler(urls, beanName);
		}
	}
	if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
		logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
	}
}

detectHandlersInAncestorContexts 默认是 false,如果你设置为 true,你就可以从 Root WebApplicationContext 中获取映射了。接下来就教大家让 detectHandlersInAncestorContexts = true 生效的配置方法

首先,你的 web.xml 需要包含以下内容

<listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

为了顺利启动,你可能还需要一个创建文件 WEB-INF/applicationContext.xml 和 web.xml 放在同一目录下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="welcomeController" name="/welcome" class="coderead.springframework.mvc.WelcomeController" />
</beans>

最后,你还需要改一下放在 resources 下的 spring-mvc.xml,为 BeanNameUrlHandlerMapping 修改 detectHandlersInAncestorContexts 属性。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--注入 BeanNameUrlHandlerMapping Bean-->
    <bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="detectHandlersInAncestorContexts" value="true" />
    </bean>
</beans>

现在我们就可以把 “根应用程序上下文” 中注册的 Bean 作为 value,URL 作为 key,通过调用 AbstractUrlHandlerMapping # registerHandler 放入到 handlerMap 中去了。

结语

拦截器不是本文的重点,所以 AbstractHandlerMapping 就不放在本文讲解了。

BeanNameUrlHandlerMappingSimpleUrlHandlerMapping 同属于 AbstractUrlHandlerMapping 的子类,他们都有 URL 映射处理器的能力。

BeanNameUrlHandlerMapping 筛选出 Name 或者 别名以 "/" 开头的 Bean ,将这些 Bean 注册为 “Handler”,实现 URL 映射。这种方式在配置上会比 SimpleUrlHandlerMapping 要便利一些。

我这里给处理器 “Handler” 打上双引号,是因为这些处理器并没有统一的 Handler 接口,而是通过适配器进行转换的。所以概念上认为是统一的 “Handler”,但是从语法和类继承结构上,又算不上统一的 “Handler”

原文地址:https://www.cnblogs.com/kendoziyu/p/SpingMvc-BeanNameUrlHandlerMapping.html