最近和同事討論一個(gè)spring mvc的問題,問到HandlerMapping與HandlerAdapter有什么關(guān)系?雖然使用spring mvc時(shí)間也不短,但是瞬間能起來的只有兩個(gè)關(guān)鍵詞:

  • @RequestMapping,這個(gè)經(jīng)常用的,每個(gè) Controller下面的action方法上一般都會(huì)定義一個(gè)特有的url路徑。當(dāng)HTTP請(qǐng)求請(qǐng)求發(fā)送到服務(wù)端后會(huì)根據(jù)url來查找應(yīng)該執(zhí)行哪個(gè)Controller下面的哪個(gè)action,我理解為url與java代碼的一個(gè)路由關(guān)系。

    @RequestMapping(value = "/bss/{priceId}", method = RequestMethod.GET)    public ValueResult<ProductPrice> getProductPrice(HttpServletRequest request,                                                                 @Min(value = 1,message = "priceId不合法")                                                                 @PathVariable final long priceId)  {        //省略
    }
  • HandlerInterceptor,這個(gè)也是經(jīng)常用的,做請(qǐng)求攔截時(shí)比較常用。

上面兩個(gè)關(guān)鍵詞盡管與問題有所關(guān)聯(lián),但很明顯不是主要的,核心還是這兩個(gè)接口都是做什么的,兩者之間有什么互動(dòng)。于是我們可以從一個(gè)請(qǐng)求開始調(diào)試下spring mvc的調(diào)用過程,以此來分析它們的作用以及關(guān)系。

Spring MVC配置

兩個(gè)配置文件:

  • 應(yīng)用程序級(jí)別的applicationContext.xml,一般加載非web的配置,比如數(shù)據(jù)庫(kù)配置,redis配置等等。

  • web級(jí)別的mvc-dispatcher-servlet.xml,這里專注mvc的配置。

XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocations(new String[]{"classpath*:applicationContext.xml","classpath*:spring/mvc-dispatcher-servlet.xml"});ServletContextHandler spingMvcHandler = new ServletContextHandler();
spingMvcHandler.setContextPath(appConfig.getContext());
spingMvcHandler.addEventListener(new ContextLoaderListener(context));
spingMvcHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), "/*");

 這里引用《張開濤》同學(xué)的圖來說明上面兩個(gè)配置的作用以及關(guān)系:

  大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

兩個(gè)核心類

  • ContextLoaderListener
    這的作用主要是在啟動(dòng)web容器時(shí)加載ApplicationContext的信息,用來創(chuàng)建ROOT ApplicationContext的,可以接收XML類型的,比如XmlWebApplicationContext,它將從XML配置文件中加載配置信息。

這篇它不是重點(diǎn)至此主止。

  • DispatcherServlet
    也叫前端控制器,它是Spring MVC的統(tǒng)一訪問入口,負(fù)責(zé)職責(zé)的分配以及工作調(diào)試,由于它的功能復(fù)雜這里只關(guān)心與HandlerMapping與HandlerAdaper的內(nèi)容。下面是初始化的功能,其中有初始化HandlerMapping與HandlerAdaper。

@Overrideprotected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */protected void initStrategies(ApplicationContext context) {	//其它初始化
	initHandlerMappings(context);
	initHandlerAdapters(context);	//其它初始化}

initHandlerMappings,主要是調(diào)用BeanFactoryUtils.beansOfTypeIncludingAncestors,其中一種非常重要的HandlerMapping是RequestMappingHandlerMapping,我們通過在Controller方面上加@RequestMapping注釋來配合使用,系統(tǒng)會(huì)將我們配置的RequestMapping信息注冊(cè)到其中,詳細(xì)數(shù)據(jù)參數(shù)此圖:mappingRegistry中包含了所有的請(qǐng)求路由信息。

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

代碼如下:

private void initHandlerMappings(ApplicationContext context) {		this.handlerMappings = null;		if (this.detectAllHandlerMappings) {			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);			if (!matchingBeans.isEmpty()) {				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}		//不加載全部的先省略

		//加載默認(rèn)的邏輯先省略
	}

DispatcherServlet核心方法:doDispatch,三個(gè)重要步驟:

  • getHandler,獲取頁面處理器,通俗點(diǎn)就是獲取由哪個(gè)Controller來執(zhí)行,包含方法信息以及方法參數(shù)等信息。

  • getHandlerAdapter,獲取HandlerAdapter,它包含一個(gè)handle方法,負(fù)責(zé)調(diào)用真實(shí)的頁面處理器進(jìn)行請(qǐng)求處理并返回一個(gè)ModelAndView。HandlerAdpter里面有一些常見的處理,比如消息轉(zhuǎn)移,參數(shù)處理等,詳見此圖:里面的argumentResolvers可以用來處理請(qǐng)求的參數(shù),messageConverts是作消息轉(zhuǎn)換等等。

     大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

  • HandlerAdapter.handle,執(zhí)行真實(shí)頁面處理器的處理請(qǐng)求。

請(qǐng)求時(shí)序圖(只關(guān)注HandlerMapping與HandlerAdapter)

doDispath獲取頁面處理器,然后根據(jù)頁面處理器獲取對(duì)應(yīng)的HanlerAdapter,最后由HanlerAdaper來調(diào)用頁面處理器的方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {		//初始化省略

		try {			ModelAndView mv = null;			Exception dispatchException = null;			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);					return;
				}				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());				//其它邏輯省略

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());				//其它邏輯省略
			}			catch (Exception ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}		//異常邏輯省略
	}

具體的調(diào)用邏輯比較復(fù)雜,只選取與HandlerMapping與HandlerAdaper的部分,時(shí)序圖圖如下:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)