springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】

前言:

       這裏爲什麼要單獨介紹一下handler,handlermapping,adapter呢?我們知道springmvc主要是來接收請求,處理請求的。
那麼怎麼知道哪個請求應該使用哪個controller來處理,是通過什麼策略來進行匹配的的,最後又是怎麼知道調用controller裏面的那個方法的,這些其實就是handler,handlermapping,adapter這三者搞出來的。

在前面的【springmvc DispatcherServlet】文章中,我們有提到過,DispatcherServlet的init方法會從容器中去找我們聲明的handlerMapping和adapter相關的javabean,如果沒有,就是用默認的,如下:

三者的關係

  • 處理請求的類就成爲handler,例如springmvc中我們的controller就屬於handler。當然要成爲handler也不一定非要是controller。只要滿足handler的要求,他就可以成爲handler。
  • 有了handler後,怎麼又怎麼能知道我們spring-mvc.xml中配合的bean或者使用java配置的方式聲明的bean,那些屬於handler?,每個handler又能處理那個請求呢?這就是handlermapping的作用。每個handlermapping類,實際上是提供了一種策略或者是約定,只要在xml中或者其他方式配置的bean,滿足他的約定,他就可以自動把我們下你給要處理的url和handler聯繫起來。用BeanNameUrlHandlerMapping來舉個例子,當DispatcherServlet中初始化的時候,會從容器中handlermapping,如果找不到就會使用默認的BeanNameUrlHandlerMapping,BeanNameUrlHandlerMapping的策略就是,如果你在spring-mvc.xml中聲明的bean,id以/開頭,例如下面的樣子: 
         <bean id="/user" class="com.controller.BeanNameURLHandlerMappingController"/>
    那麼BeanNameUrlHandlerMapping就認爲你後面的這個class就屬於一個handler,並且id的值/user就是作爲映射的url,也就是說將來請求的如果是/user,那麼便會使用BeanNameURLHandlerMappingController來處理這個請求

                             
           對於其他的handlermapping,會有各自的不同的識別和綁定url和handler的策略和約定。這裏只要知道handlermapping的             作用即可。在使用中,你可以通過在xml中聲明不同的handlermapping的類,讓他們把我們handler和請求映射起來。也就             是說我們可以指定使用不同的handlermapping或者自定義handlermapping

  • 通過上面二者,url和handler就映射綁定成功了,但是要用handler裏的什麼方法來處理這個請求呢?也就是實際調用handler中處理請求的方法的豬腳就是adapter了,也就是說,你的handler僅僅滿足handlermapping的規則和策略進行了url映射還不行,因爲處理請求的最終用的是handler裏面的方法。那麼到底應該用什麼方法?是自己隨便寫方法?還是必須重寫一個規定的方法呢?這就取決於你的handler屬於那種adapter了。怎麼知道我們寫的handler屬於那種adapter呢。下面以SimpleControllerHandlerAdapter爲例來解釋一下,先看代碼:      


    紅框部分就決定了我們的handler屬於哪個adapter,如果你的handler實現了Controller接口,那麼你就屬於  SimpleControllerHandlerAdapter了,屬於這個adapter,那麼我們的handler就必須有handlerRequest這樣一個方法(handle方法中調用的),以後用來處理請求(其實這個方法就是Controller接口中的方法,因爲實現一個接口,肯定就必須重寫這個方法)。這也就保證SimpleControllerHandlerAdapter的handle方法中調用不會出錯。說白了就是面向接口編程,你的handler如果屬於我這個接口(supports爲true),然後就用這個方法來處理請求。因此只要我們寫的controller重寫了這個handleRequest方法,就可以在這個方法中處理請求了。具體實現,下面會有例子。

 

關係都挑明瞭,下面來看看springmvc包中默認都帶了那些handlemapping和adapter,然後我們再通過自定義一些handler,通過例子來詳細說明一下。

springmvc默認的提供的handlermapping和adatper,一般都放在spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet\handler包下面。下面通過uml來展示這個包下的handlermapping相關的類和接口。如下:

  • handlermapping:

                                      

       另外還有spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet\mvc\method\annotation 包下的   
       RequestMappingHandlerMapping等.

  • adapter
    SimpleServletHandlerAdapter 實現HandlerAdapter接口

最後我們就通過例子來說明handler,和默認的handlermapping,adapter三者的用法。

1.BeanNameUrlHandlerMapping
   首先,什麼時候會使用這個handlermapping,看下面源碼中的說明
 

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {}

也就是說如果在spring容器中找不到HandlerMapping,就會默認使用BeanNameUrlHandlerMapping。

其次,這個類有什麼作用?這個類會從spring容器中(就是xml中或者java配置類聲明的bean)查找beanname以/開頭的bean什麼是beanname?<bean id="/hello"  name="/hello" class="..."/>name就是beanname,如果沒有name屬性,id就是beanname),如果找到了,就會以這個beanname(/hello)爲映射的key,如果以後瀏覽器發送的請求(/hello)正好與這個key相同,那麼就用這個key對應的handler來處理這個請求(至於用哪個方法來處理,就要看adapter了)。

也就是說,使用了BeanNameUrlHandlerMapping後,springmvc會默認認爲,用戶想告訴它,xml中只要按照這個格式寫的bean,那麼這個bean就是用來處理請求的,處理那個請求?就是處理這個bean的beanname中那個字符串對應的請求。

例子一:

要使用BeanNameUrlHandlerMapping,那麼我們在xml中不要聲明其他的HandlerMapping,否則不會生效。

spring-mvc.xml

<!--沒有name屬性,那麼beanname就是id的值。/hello符合BeanNameURLHandlerMapping的規則
    以後便會用這個類來處理/hello的請求
-->
<bean id="/helllo" class="com.lhb.controller.BeanNameURLHandlerMappingController"/>

BeanNameURLHandlerMappingController.java

/**
* 自定義的用來處理請求的handler
*/
public class BeanNameURLHandlerMappingController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 接受請求後,跳轉到hello.jsp頁面,然後在頁面打印出Hello World!
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
}

只需這多麼兩步,我們在自己寫的handler中接收到/hello的請求進行處理了。

也許你可能會問,前面不是說了麼,通過handlermapping只能綁定某個請求由那個handler來處理,但是具體用handler中的哪個方法來處理,必須要有adapter才行麼,爲什麼這裏沒看到,你就說只需要上面的兩步就可以處理請求了,你還沒指定處理請求的方法呢,怎麼就保證請求一定會進入到handleRequest這個方法進行處理。那麼下面來解開真像,有請第三者SimpleControllerHandlerAdapter,同上問題,它是什麼時候被加載的呢?我們並沒有生命過這個類呀?其實這個Adapter還有其他的幾個Adapter都是在DispatcherServlet.properties中指定的,然後在DispatchServlet的static靜態的塊中就實例化放到容器中了,在initHandlerAdapters方法中直接拿了出來,代碼如下:

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;
    // 如果要加載所有的adapter
	if (this.detectAllHandlerAdapters) {
        // 這裏便把dispatchservlet.properites中的adapter都加載了,包括了我們說的
        // SimpleControllerHandlerAdapter
		Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, 
                                                       HandlerAdapter.class, true, false);
	}
    // 如果不想加載默認properites中的adapter,而是想加載自己定義的adapter,那麼就在
    // xml中定義名字爲handlerAdapter的bean
	else {
        //從容器中取出名爲handlerAdapter的adapter,也即是說我們可以自定義adapter來使用
		HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, 
                                             HandlerAdapter.class);
		this.handlerAdapters = Collections.singletonList(ha);	
	}
    有部分代碼此處省略了。。。
}

從代碼中可以看出這裏會有三個adapter:

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • AnnotationMethodHandlerAdapter

爲什麼會默認使用了SimpleControllerHandlerAdapter呢,最簡單的方式就是看名字,名字中有個Controller,就是說你的handler如果實現了Controller這個接口,那麼就會用這個Adapter ^_^,再從看下面DispatcherServlet中獲取adapter的代碼和SimpleControllerHandlerAdapter的代碼來看看爲什麼我們的例子中最後用的是SimpleControllerHandlerAdapter:

/**
* DispatcherServlet接收到請求,獲取到handler後,便會通過handler來獲取對應的adapter,
* 因爲最後要通過adapter才能真正實現調用handler中的那個方法處理請求
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	// 遍歷所有adapter,決定使用哪個apdapter,至於怎麼決定,就在support方法
    for (HandlerAdapter ha : this.handlerAdapters) {
        // 如果這個handler符合當前adapter的規則,那麼就用這個adapter來調用handler中的方法來處理 
        // 請求
		if (ha.supports(handler)) {
			return ha;
		}
	}
	throw new ServletException("No adapter for handler ");
}

                     

從上面代碼看出, 因爲我們的handler(BeanNameURLHandlerMappingController)實現了Controller接口,所以handler instanceof Controller變會是true。所以就會選擇SimpleControllerHandlerAdapter來指定調用handler中的那個方法來處理請求。
調用handler中的哪個方法呢?在這個apdater的hand方法中指定的是handlRequest方法來處理(正好是實現Controller接口重寫的方法)。這裏知道了本質後,讓我們再來舉一反三,我們可不可以自定義個一個ControllerHandlerAdapter呢?然後讓這個adapter調用handler中的其他方法來處理請求,而不是用重寫的Controller接口來處理請求。

第一步:修改web.xml,將detectAllHandlerAdapters屬性設置爲false,這樣才能使用我們自己定義的adapter

web.xml
<servlet>
    <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        .... 其他的配置省略
        // 設置DispatcherServlet類中的detectAllHandlerAdapters屬性爲false
        // 只有設置到false才能使用自己定義的adapter
        <init-param>
          <param-name>detectAllHandlerAdapters</param-name>
          <param-value>false</param-value>
        </init-param>
		<load-on-startup>1</load-on-startup>
		.... 其他的配置省略
</servlet>

第二步:自定義一個adapter,一定要實現HandlerAdapter接口

/**
 * 除了handle方法,其他兩個方法從SimpleControllerHandlerAdapter複製過來就行
 *
 */
public class MyHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 這裏指定調用我們自己handler中的一個普通的方法來處理請求,而不是用接口中的方法了。
	    // 但是不提倡這樣寫,耦合太深,這裏只是爲了覺例子而且,實際不建議這樣寫
		return ((BeanNameURLHandlerMappingController) handler).myHandleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

第三步:改造上面的BeanNameURLHandlerMappingController類,添加一個讓我們自定義的adapter調用的方法

public class BeanNameURLHandlerMappingController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
	
	/**
	 * 自定義的apdater將調用這個方法來處理請求
	 */
	public ModelAndView myHandleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}

	
}

第四步:在spring-mvc.xml中聲明這個adapter

<bean id="/hello" class="com.lhb.controller.BeanNameURLHandlerMappingController"/>
<!--其他相關配置聲明等就不寫了,上面都有
    id必須寫handlerAdapter
-->
<bean id = "handlerAdapter" class="com.lhb.MyHandlerAdapter"/>

通過上面四步後,我們會看到,當發起/hello請求後,DispatcherServlet中便會使用我們自己定義adapter來調用handler(BeanNameURLHandlerMappingController)中的myHandleRequest方法來處理請求了。

如果你明白了舉一反三的這個例子中,那麼相信handler、handlermapping和adpter你就應該懂得差不多了。

 

下面我將繼續寫其他的例子
例子二 SimpleUrlHandlerMapping的使用:
看過上面BeanNameURLHandlerMapping的介紹後,大家有沒有這麼一個感受,就是spring-mvc.xml中聲明handler的時候,如下:
 <bean id="/hello" class="com.lhb.controller.BeanNameURLHandlerMappingController"/>
如果使用BeanNameURLHandlerMapping生成url和handler的映射,如果想多個請求都映射到同一個handler中處理,那麼就需要聲明多次,這樣就有點太囉嗦了。所以這就引入了SimpleUrlHandlerMapping,使用它來替代默認的BeanNameURLHandlerMapping,所以需要在xml中聲明一個,有了它,我們只要定義一個handler,然後通過做在聲明的SimpleUrlHandlerMapping的bean
中將多個不同的url指向這個handler即可,相關代碼如下:

public class SimpleUrlHandlerMappingHandler  implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
}

spring-mvc.xml

    <!--要代替默認的BeanNameURLHandlerMapping必須在聲明一下,
        因爲DispatcherServlet在初始化的時候,只有在容器中找不到任何HandlerMapping時,
       便會生成一個BeanNameURLHandlerMapping
    -->
	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<props>
				<prop key="/welcome">myHandler</prop>
				<prop key="/*/welcome1">myHandler</prop>
				<prop key="/welcome2">myHandler</prop>
			</props>
		</property>
	</bean>

	<bean id="myHandler" class="com.lhb.controller.SimpleUrlHandlerMappingHandler" />

通過上面的配置後,我們請求/welcome,/welcome2還有/*/welcome都會調用SimpleUrlHandlerMappingHandler中的myHandleRequest方法來處理請求了。那麼用的是哪個adpater呢,用的還是SimpleControllerHandlerAdapter,因爲我們的handler還是實現了Controller接口。只要實現了Controller接口,一定就會是SimpleControllerHandlerAdapter。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章