• 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”

  • 相关阅读:
    CF 149D Coloring Brackets(区间DP,好题,给配对的括号上色,求上色方案数,限制条件多,dp四维)
    hdu 4607 树形dp 树的直径
    poj 2955 区间dp入门题
    poj 2139 flord水题
    poj 2377 最大生成树
    lightoj 1422 区间dp
    模拟类似括号匹配
    nyoj 33 蛇形填数
    nyoj 2 括号配对问题水
    Hackonacci Matrix Rotations 观察题 ,更新了我的模板
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/SpingMvc-BeanNameUrlHandlerMapping.html
Copyright © 2020-2023  润新知