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”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM