SpringMVC中HandlerMapping和HandlerAdapter詳解(適配器模式)

一、引言

本人在閱讀 SpringMVC 源碼過程中,一直對 HandlerMappingHandlerAdapter 有疑惑,一直不理解。心裏知道這裏用的是適配器模式,本人對適配器模式還是知曉的,但這兩個東西就是不理解。最近突然知道了一個知識點,瞬間豁然開朗,至於是哪個知識點,下面慢慢說。

下面這張圖是SpringMVC的工作流程圖,隨便一搜,應該一大把,這裏只做熟悉用,不會細說。(PS:之前跳槽面試,就有一道筆試題讓我畫SpringMVC的工作流程。。。。)
在這裏插入圖片描述

對上圖做一下簡單總結:

1、請求首先進入DispatcherServlet, 由DispatcherServletHandlerMappings中匹配對應的Handler,此時只是獲取到了對應的Handler,然後拿着這個Handler去尋找對應的適配器,即:HandlerAdapter

2、拿到對應HandlerAdapter時,這時候開始調用對應的Handler方法,即執行我們的Controller來處理業務邏輯了, 執行完成之後返回一個ModeAndView

3、HandlerAdapter執行完之後,返回一個ModeAndView,把它交給我們的視圖解析器ViewResolver,通過視圖名稱查找出對應的視圖然後返回;

4、最後,渲染視圖 返回渲染後的視圖。

二、SpringMVC中定義Controller的方式

在介紹HandlerMappingHandlerAdapter之前,先來說一下SpringMVC中定義Handler的方式,本人就是對這個知識點不熟悉,導致對這兩個對象一直不明白。

先說一下最最最最……常用定義Handler的方式,使用@RequestMapping註解,下面這段代碼不用介紹吧:

@Controller
public class IndexController {
    @RequestMapping("/index")
    @ResponseBody
    public String sayHello(){
        System.out.println("hello ...");
        return "hello";
    }
}

那大家有沒有用過下面的兩種方式來聲明一個Handler呢??

實現org.springframework.web.servlet.mvc.Controller控制器接口,此接口只有一個方法handleRequest(),用於請求的處理,返回ModelAndView。 這個接口從第一版SpringMVC就存在了,所以這個接口是非常古老的接口~~~也是Spring MVC最早期的實現Handler的方式

// 關注一下這個包
import org.springframework.web.servlet.mvc.Controller;

@Component("/home")
public class HomeController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, 
    				HttpServletResponse response) throws Exception {
        System.out.println("home ...");
        return null;
    }

	// 這地方考慮個問題:怎麼樣實現類似@ResponseBody的功能呢?
	// 就是想實現直接向body裏寫數據,而不是返回一個頁面。

	// 如果想直接在處理器/控制器裏使用response向客戶端寫回數據,
	// 可以通過返回null來告訴	DispatcherServlet我們已經寫出響應了,
	// 不需要它進行視圖解析。像下面這樣
	@Override
    public ModelAndView handleRequest(HttpServletRequest request,
    				 HttpServletResponse response) throws Exception {
        System.out.println("home ...");
        response.getWriter().write("home controller from body");
        return null; // 返回null告訴視圖渲染  直接把body裏面的內容輸出瀏覽器即可
    }

}

實現org.springframework.web.HttpRequestHandler接口,HttpRequestHandler用於處理Http requests,其類似於一個簡單的Servlet,只有一個handlerRequest()方法,其處理邏輯隨子類的實現不同而不同。

// 關注一下這個包
import org.springframework.web.HttpRequestHandler;

@Component("/login")
public class LoginController implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, 
    		HttpServletResponse response) 
    		throws ServletException, IOException {
        System.out.println("login...");
        response.getWriter().write("login ...");
    }
}

再來看一下servlet的使用,是不是很相似。

@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, 
    		HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }
}

其實上面這兩種方式第一種使用@RequestMapping註解一樣,都能定義爲一個Handler,攔截到對應的請求,並且做出響應。這地方就要牽扯出HandlerMapping了。

三、何爲HandlerMapping、HandlerAdapter?

從上面的分析,我們知道,Handler的定義有上面三種(也有可能還有其他方式,比如Servlet),這地方就要引出下面這兩個HandlerMapping:BeanNameUrlHandlerMappingRequestMappingHandlerMapping,當然還有其他HandlerMapping,下面的斷點圖也能說明這一點。

這裏先說明一下,用註解@RequestMapping定義的Handler,用的是RequestMappingHandlerMapping,上面的其他兩種,用的是BeanNameUrlHandlerMapping,靜態資源的請求,用的是SimpleUrlHandlerMapping

這地方我們可以從 Spring 的角度考慮,Spring 容器在啓動的時候,會去掃描所有的組件,並把它們實例化。當 Spring 容器發現一個方法用@RequestMapping註解標註的時候,就用RequestMappingHandlerMapping這個類去實例化,當發現一個類實現了org.springframework.web.servlet.mvc.Controller這個接口的時候,就用BeanNameUrlHandlerMapping去實例化,然後將所有請求放在一個Map裏,用請求路徑(比如:/index)和對應的Handler做映射處理,這樣是不是更好理解。

HandlerMapping的作用:主要是根據request請求匹配/映射上能夠處理當前requestHandler.

下面來看一下如何根據request來獲取HandlerMapping

	protected HandlerExecutionChain getHandler(HttpServletRequest request) 
												throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

下面是對/index請求的斷點調試圖,我們從圖中可以看出,this.handlerMappings 裏面有4個類,有一個爲重複的。循環這個List,判斷這個/index請求是由哪個Handler來處理(即查找HandlerMapping的過程)。

通過循環HandlerMapping來獲取HandlerExecutionChain,再次強調,因爲spring當中存在的Handler有多種形式,我們處理request需要通過HandlerExecutionChain來反射執行Handler當中的方法,所以不同的Handler需要new不同的HandlerExecutionChain,那麼問題來了HandlerExecutionChain不知道你的Handler是什麼類型(因爲HandlerExecutionChain裏只定義了一個Object handler屬性,它不知道你的Handler是什麼類型的),但是HandlerMapping知道,所以HandlerExecutionChain的實例化必須依賴HandlerMapping

好,講到這終於明白HandlerMapping的幹嘛的了,至於如何根據/index去找對應的HandlerHandlerExecutionChain ,這裏就不做介紹啦。
在這裏插入圖片描述
那上面幾個HandlerMapping是怎麼來的呢?Spring容器在初始化的過程中,會調用到initStrategies中的 initHandlerMappings(context)、initHandlerAdapters(context);這兩個方法。我們在源碼包的DispatcherServlet.properties文件下會看見, 它定義了圖片裏的這些屬性。 第一個屬性,就是我們剛看見的HandlerMappings, 也就是說 HandlerMappingsSpringMVC事先定義好的,Spring容器會幫我們創建。至於第二個屬性,也就是HandlerAdapter
在這裏插入圖片描述
介紹完HandlerMapping之後,下面就要來介紹HandlerAdapter了。

HandlerAdapter的作用:因爲Spring MVC中的Handler可以有多種實現形式,但是Servlet需要的處理方法的結構卻是固定的,都是以requestresponse作爲方法入參,那麼如何讓固定參數的Servlet處理方法調用靈活的Handler來進行處理呢?這就需要HandlerAdapter來做適配。

爲什麼需要HandlerAdapter?
前面說過不同的請求會獲取到不同的Handler,那麼不同的Handler它是怎麼實現處理不同的請求的呢?我的第一反應是抽象出一個接口,定義一個公共接口,然後讓每個Handler實現這個接口,我想的沒問題吧,但 Spring 不是這麼做的,爲什麼呢?

再次強調:Spring MVCHandlerController接口,HttpRequestHandler,@RequestMapping、Servlet)有多種表現形式,不同的Handler,處理請求的方式是不一樣的,註解@RequestMapping方式使用的是用方法處理請求,而實現Controller接口和HttpRequestHandler接口方式使用的是一個類,而適配器模式就能模糊掉具體的實現,從而就能提供統一訪問接口,所以這地方就要使用適配器了。

這樣做的好處有兩個 (1)、處理器程序,也就是Handler,允許的是任意的Object,只要返回封裝好的HandlerExecutionChain,具體的Handler不用管;(2)、集成第三方請求處理器的時候,本處代碼也無需修改,加個適配器就行(PS:這地方可以參考文章最後的模擬程序)

HandlerMapping的源碼也說明了這一點。HandlerMapping接口裏面只有一個getHandler()方法,而且返回類型是HandlerExecutionChain,用HandlerExecutionChain裏面定義了一個Object類型的handler屬性,並對handler進行了封裝,在每個請求里加入了攔截器鏈。然後將這個HandlerExecutionChain裏面的handler傳給了HandlerAdapter

這地方我們可以換個角度,就是萬一處理請求的每個方法不一樣怎麼辦?支持擴展的話,是不是就需要適配器模式了

說了這麼多,是不是終於知道爲什麼需要HandlerAdapter了。

在得到Handler之後,就是下面的這行代碼,我們來看一下getHandlerAdapter()方法

	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) 
				throws ServletException {
	if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
}

從代碼中能看到,從一個this.handlerAdapters屬性裏面遍歷了我們的適配器。這個handlerAdapters哪來的呢? 跟上面的this.HandlerMappings一樣,在SpringMVC的配置文件裏面配置的,也就是上圖中的第二個屬性。
在這裏插入圖片描述
實現org.springframework.web.servlet.mvc.Controller接口形式的處理器,對應的HandlerMappingBeanNameUrlHandlerMapping,對應的HandlerAdapterHttpRequestHandlerAdapter
在這裏插入圖片描述

實現org.springframework.web.HttpRequestHandler接口形式的處理器,對應的HandlerMapping也是 BeanNameUrlHandlerMapping,對應的HandlerAdapter 也是 HttpRequestHandlerAdapter在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
最後看一下三個適配器中的supports()handle()方法

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter適配org.springframework.web.servlet.mvc.Controller這種Handler。源碼非常之簡單,它是一個非常古老的適配器,幾乎已棄用狀態。因爲它直接處理的就是源生的HttpServletRequestHttpServletResponse,所以它和Servlet容器是強綁定的。無數據自動封裝、校驗等一系列高級功能,所以實際應用中此種方式很少被使用。

// 適配`org.springframework.web.servlet.mvc.Controller`這種Handler
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}
	// 最終執行邏輯的還是Handler啊~~~~
	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return ((Controller) handler).handleRequest(request, response);
	}
	
}

HttpRequestHandlerAdapter

HttpRequestHandlerAdapter適配org.springframework.web.HttpRequestHandler這種Handler。它比Controller方式還源生。
它和上面的唯一不同是:return null。那是因爲HttpRequestHandler#handleRequest()它沒有返回值,這就需要全靠開發者自己寫response,而Controller最起碼來說還有Model和View自動渲染的能力。

public class HttpRequestHandlerAdapter implements HandlerAdapter {

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

	@Override
	public ModelAndView handle(HttpServletRequest request, 
				HttpServletResponse response, Object handler)
			throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter主要是支持到了org.springframework.web.method.HandlerMethod這種handler,顯然這種處理器也是我們最最最最爲常用的,它已經把HandlerMethod的實現精確到了使用@RequestMapping註解標註的方法。這個類,我們要查看它的父類AbstractHandlerMethodAdapter

public class AbstractHandlerMethodAdapter {

	// 只處理HandlerMethod 類型的處理器。抽象方法supportsInternal默認返回true
	// 是留出的鉤子可以給你自己擴展的
	@Override
	public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod 
			&& supportsInternal((HandlerMethod) handler));
	}

	@Override
	public final ModelAndView handle(HttpServletRequest request, 
					HttpServletResponse response, Object handler)
			throws Exception {
		// 抽象方法交給子類handleInternal去實現
		return handleInternal(request, response, (HandlerMethod) handler);
	}
}

看完之後,再來讀一下DispatcherServlet#doDispatch()方法的分發流程,看看DispatcherServlet是如何使用HandlerMapping和HandlerAdapter

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
							throws Exception {
		...
		//1、根據URL(當然不一定非得是URL)匹配到一個處理器
		mappedHandler = getHandler(processedRequest);
		if (mappedHandler == null) {
			// 若匹配不到Handler處理器,就404了
			noHandlerFound(processedRequest, response);
			return;
		}

		//2、從HandlerExecutionChain裏拿出Handler(注意是Object類型哦~ )然後找到屬於它的適配器
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
		...
		//3、執行作用在此Handler上的所有攔截器的Pre方法
		if (!mappedHandler.applyPreHandle(processedRequest, response)) {
			return;
		}
		//4、真正執行handle方法(也就是你自己書寫的邏輯方法),得到一個ModelAndView
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

		//5、視圖渲染
		applyDefaultViewName(processedRequest, mv);
		
		//6、執行攔截器的post方法(可見它是視圖渲染完成了纔會執行的哦~)
		mappedHandler.applyPostHandle(processedRequest, response, mv);
		...
		//7、執行攔截器的afterCompletion方法(不管拋出與否)
	}

從執行步驟中可以看到:HandlerAdapter對於執行流程的通用性起到了非常重要的作用,它能把任何一個Handler(注意是Object類型)都適配成一個HandlerAdapter,從而可以做統一的流程處理,這也是爲何DispatcherServlet它能作爲其它web處理框架的分發器的原因,因爲它沒有耦合具體的處理器,你完全可以自己去實現。

四、模擬HandlerMapping/HandlerAdapter的適配器模式

如果上面的講法,你還是不懂,下面就用適配器模式模擬一下,這兩個類的具體調用情況,應該會一目瞭然。下面是依賴關係的類圖。其中Controller 代表的就是 HandlerMapping。具體代碼,可以在文章最後下載。

在這裏插入圖片描述

//多種Controller實現  
public interface Controller {

}

// 注意這裏每個實現,都用了不同的方法名, 如果都用一樣的話,就可以放到接口中了
class HttpController implements Controller {
	public void doHttpHandler() {
		System.out.println("http...");
	}
}

class SimpleController implements Controller {
	public void doSimplerHandler() {
		System.out.println("simple...");
	}
}

class AnnotationController implements Controller {
	public void doAnnotationHandler() {
		System.out.println("annotation...");
	}
}
// 定義一個Adapter接口 
public interface HandlerAdapter {

	public boolean supports(Object handler);

	public void handle(Object handler);
}

// 多種適配器類
class SimpleHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((SimpleController) handler).doSimplerHandler();
	}

	public boolean supports(Object handler) {
		return (handler instanceof SimpleController);
	}

}

class HttpHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((HttpController) handler).doHttpHandler();
	}

	public boolean supports(Object handler) {
		return (handler instanceof HttpController);
	}

}

class AnnotationHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((AnnotationController) handler).doAnnotationHandler();
	}

	public boolean supports(Object handler) {

		return (handler instanceof AnnotationController);
	}

}
public class DispatchServlet {

	public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();

	public DispatchServlet() {
		handlerAdapters.add(new AnnotationHandlerAdapter());
		handlerAdapters.add(new HttpHandlerAdapter());
		handlerAdapters.add(new SimpleHandlerAdapter());
	}

	public void doDispatch() {

		// 此處模擬SpringMVC從request取handler的對象,
		// 適配器可以獲取到希望的Controller
		 HttpController controller = new HttpController();
		// AnnotationController controller = new AnnotationController();
		//SimpleController controller = new SimpleController();
		// 得到對應適配器
		HandlerAdapter adapter = getHandler(controller);
		// 通過適配器執行對應的controller對應方法
		adapter.handle(controller);

	}

	public HandlerAdapter getHandler(Controller controller) {
		//遍歷:根據得到的controller(handler), 返回對應適配器
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(controller)) {
				return adapter;
			}
		}
		return null;
	}

	public static void main(String[] args) {
		new DispatchServlet().doDispatch(); // http...
	}
}

注意:Controller接口的每個實現類,都用了不同的方法名, 這樣的話就需要用到適配器模式了,如果都用一樣的話,就可以放到接口中了,這樣是不是可以理解SpringMVC中此處的HandlerAdapter

五、小結

還是做個小結吧。
SpringMVCHandler有多種實現方式(Controller,HttpRequestHandler,Servlet等),例如繼承Controller接口的形式,基於註解@Controller控制器方式的,HttpRequestHandler方式的。由於實現方式不一樣,調用方式就不確定。

,上面的其他兩種,用的是``,這地方要說明一下,靜態資源的請求,用的是SimpleUrlHandlerMapping`==。

繼承 Controller 方式所使用的HandlerMappingBeanNameUrlHandlerMapping
繼承 Controller 方式所使用的適配器:HttpRequestHandlerAdapter
註解方式@ControllerHandlerMapping器:RequestMappingHandlerMapping
註解方式@Controller適配器:RequestMappingHandlerAdapter

這是一一對應的。

如果說的不對的,反饋一下給我啊,謝謝……

代碼下載:

https://github.com/Hofanking/spring-boot-demo

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