SpringMVC中的HandlerMapping和HandlerAdapter詳解
一、引言
本人在閱讀 SpringMVC 源碼過程中,一直對 HandlerMapping 、 HandlerAdapter 有疑惑,一直不理解。心里知道這里用的是適配器模式,本人對適配器模式還是知曉的,但這兩個(gè)東西就是不理解。最近突然知道了一個(gè)知識點(diǎn),瞬間豁然開朗,至于是哪個(gè)知識點(diǎn),下面慢慢說。
下面這張圖是 SpringMVC 的工作流程圖,隨便一搜,應(yīng)該一大把,這里只做熟悉用,不會細(xì)說。(PS:之前跳槽面試,就有一道筆試題讓我畫SpringMVC的工作流程。。。。)
對上圖做一下簡單總結(jié):
1、請求首先進(jìn)入 DispatcherServlet , 由 DispatcherServlet 從 HandlerMappings 中匹配對應(yīng)的 Handler ,此時(shí)只是獲取到了對應(yīng)的 Handler ,然后拿著這個(gè) Handler 去尋找對應(yīng)的適配器,即: HandlerAdapter ;
2、拿到對應(yīng) HandlerAdapter 時(shí),這時(shí)候開始調(diào)用對應(yīng)的 Handler 方法,即執(zhí)行我們的 Controller 來處理業(yè)務(wù)邏輯了, 執(zhí)行完成之后返回一個(gè) ModeAndView ;
3、 HandlerAdapter 執(zhí)行完之后,返回一個(gè) ModeAndView ,把它交給我們的視圖解析器 ViewResolver ,通過視圖名稱查找出對應(yīng)的視圖然后返回;
4、最后,渲染視圖 返回渲染后的視圖。
二、SpringMVC中定義Controller的方式
在介紹 HandlerMapping 、 HandlerAdapter 之前,先來說一下 SpringMVC 中定義 Handler 的方式,本人就是對這個(gè)知識點(diǎn)不熟悉,導(dǎo)致對這兩個(gè)對象一直不明白。
先說一下最最最最……常用定義 Handler 的方式,使用 @RequestMapping 注解,下面這段代碼不用介紹吧:
@Controller public class IndexController { @RequestMapping("/index") @ResponseBody public String sayHello(){ System.out.println("hello ..."); return "hello"; } }
那大家有沒有用過下面的兩種方式來聲明一個(gè) Handler 呢??
實(shí)現(xiàn) org.springframework.web.servlet.mvc.Controller 控制器接口,此接口只有一個(gè)方法 handleRequest() ,用于請求的處理,返回 ModelAndView 。 這個(gè)接口從第一版 SpringMVC 就存在了,所以這個(gè)接口是非常古老的接口~~~也是 Spring MVC 最早期的實(shí)現(xiàn) Handler 的方式
// 關(guān)注一下這個(gè)包 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; } // 這地方考慮個(gè)問題:怎么樣實(shí)現(xiàn)類似@ResponseBody的功能呢? // 就是想實(shí)現(xiàn)直接向body里寫數(shù)據(jù),而不是返回一個(gè)頁面。 // 如果想直接在處理器/控制器里使用response向客戶端寫回?cái)?shù)據(jù), // 可以通過返回null來告訴 DispatcherServlet我們已經(jīng)寫出響應(yīng)了, // 不需要它進(jìn)行視圖解析。像下面這樣 @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里面的內(nèi)容輸出瀏覽器即可 } }
實(shí)現(xiàn) org.springframework.web.HttpRequestHandler 接口, HttpRequestHandler 用于處理 Http requests ,其類似于一個(gè)簡單的 Servlet ,只有一個(gè) handlerRequest() 方法,其處理邏輯隨子類的實(shí)現(xiàn)不同而不同。
// 關(guān)注一下這個(gè)包 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); } }
其實(shí)上面這兩種方式第一種使用 @RequestMapping 注解一樣,都能定義為一個(gè) Handler ,攔截到對應(yīng)的請求,并且做出響應(yīng)。這地方就要牽扯出 HandlerMapping 了。
三、何為HandlerMapping、HandlerAdapter?
從上面的分析,我們知道, Handler 的定義有上面三種(也有可能還有其他方式,比如Servlet),這地方就要引出下面這兩個(gè) HandlerMapping:BeanNameUrlHandlerMapping 、 RequestMappingHandlerMapping ,當(dāng)然還有其他 HandlerMapping ,下面的斷點(diǎn)圖也能說明這一點(diǎn)。
這里先說明一下,用注解 @RequestMapping 定義的 Handler ,用的是 RequestMappingHandlerMapping ,上面的其他兩種,用的是 BeanNameUrlHandlerMapping ,靜態(tài)資源的請求,用的是 SimpleUrlHandlerMapping 。
這地方我們可以從 Spring 的角度考慮,Spring 容器在啟動(dòng)的時(shí)候,會去掃描所有的組件,并把它們實(shí)例化。當(dāng) Spring 容器發(fā)現(xiàn)一個(gè)方法用 @RequestMapping 注解標(biāo)注的時(shí)候,就用 RequestMappingHandlerMapping 這個(gè)類去實(shí)例化,當(dāng)發(fā)現(xiàn)一個(gè)類實(shí)現(xiàn)了 org.springframework.web.servlet.mvc.Controller 這個(gè)接口的時(shí)候,就用 BeanNameUrlHandlerMapping 去實(shí)例化,然后將所有請求放在一個(gè)Map里,用請求路徑(比如:/index)和對應(yīng)的 Handler 做映射處理,這樣是不是更好理解。
HandlerMapping的作用:主要是根據(jù) request 請求匹配/映射上能夠處理當(dāng)前 request 的 Handler .
下面來看一下如何根據(jù) 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 請求的斷點(diǎn)調(diào)試圖,我們從圖中可以看出, this.handlerMappings 里面有4個(gè)類,有一個(gè)為重復(fù)的。
循環(huán)這個(gè)List,判斷這個(gè) /index 請求是由哪個(gè) Handler 來處理(即查找 HandlerMapping 的過程)。
通過循環(huán) HandlerMapping 來獲取 HandlerExecutionChain ,再次強(qiáng)調(diào),因?yàn)?spring 當(dāng)中存在的 Handler 有多種形式,我們處理 request 需要通過 HandlerExecutionChain 來反射執(zhí)行 Handler 當(dāng)中的方法,所以不同的 Handler 需要 new 不同的 HandlerExecutionChain ,那么問題來了 HandlerExecutionChain 不知道你的 Handler 是什么類型(因?yàn)?HandlerExecutionChain 里只定義了一個(gè) Object handler 屬性,它不知道你的 Handler 是什么類型的),但是 HandlerMapping 知道,所以 HandlerExecutionChain 的實(shí)例化必須依賴 HandlerMapping 。
好,講到這終于明白 HandlerMapping 的干嘛的了,至于如何根據(jù) /index 去找對應(yīng)的 Handler 和 HandlerExecutionChain ,這里就不做介紹啦。
那上面幾個(gè) HandlerMapping 是怎么來的呢? Spring 容器在初始化的過程中,會調(diào)用到 initStrategies 中的 initHandlerMappings(context)、initHandlerAdapters(context); 這兩個(gè)方法。我們在源碼包的 DispatcherServlet.properties 文件下會看見, 它定義了圖片里的這些屬性。 第一個(gè)屬性,就是我們剛看見的 HandlerMappings , 也就是說 HandlerMappings 是 SpringMVC 事先定義好的, Spring容器 會幫我們創(chuàng)建。至于第二個(gè)屬性,也就是 HandlerAdapter 。
介紹完 HandlerMapping 之后,下面就要來介紹 HandlerAdapter 了。
HandlerAdapter的作用:
因?yàn)?Spring MVC 中的 Handler 可以有多種實(shí)現(xiàn)形式,但是 Servlet 需要的處理方法的結(jié)構(gòu)卻是固定的,都是以 request 和 response 作為方法入?yún)?,那么如何讓固定參?shù)的 Servlet 處理方法調(diào)用靈活的 Handler 來進(jìn)行處理呢?這就需要 HandlerAdapter 來做適配。
為什么需要HandlerAdapter?
前面說過不同的請求會獲取到不同的 Handler ,那么不同的 Handler 它是怎么實(shí)現(xiàn)處理不同的請求的呢?我的第一反應(yīng)是抽象出一個(gè)接口,定義一個(gè)公共接口,然后讓每個(gè) Handler 實(shí)現(xiàn)這個(gè)接口,我想的沒問題吧,但 Spring 不是這么做的,為什么呢?
再次強(qiáng)調(diào): Spring MVC 的 Handler ( Controller接口,HttpRequestHandler,@RequestMapping、Servlet )有多種表現(xiàn)形式,不同的 Handler ,處理請求的方式是不一樣的,注解 @RequestMapping 方式使用的是用方法處理請求,而實(shí)現(xiàn) Controller 接口和 HttpRequestHandler 接口方式使用的是一個(gè)類,而適配器模式就能模糊掉具體的實(shí)現(xiàn),從而就能提供統(tǒng)一訪問接口,所以這地方就要使用適配器了。
這樣做的好處有兩個(gè) (1)、處理器程序,也就是 Handler ,允許的是任意的Object,只要返回封裝好的 HandlerExecutionChain ,具體的 Handler 不用管;(2)、集成第三方請求處理器的時(shí)候,本處代碼也無需修改,加個(gè)適配器就行(PS:這地方可以參考文章最后的模擬程序)
HandlerMapping 的源碼也說明了這一點(diǎn)。 HandlerMapping 接口里面只有一個(gè) getHandler() 方法,而且返回類型是 HandlerExecutionChain ,用 HandlerExecutionChain 里面定義了一個(gè) Object 類型的 handler 屬性,并對 handler 進(jìn)行了封裝,在每個(gè)請求里加入了攔截器鏈。然后將這個(gè) HandlerExecutionChain 里面的 handler 傳給了 HandlerAdapter 。
這地方我們可以換個(gè)角度,就是萬一處理請求的每個(gè)方法不一樣怎么辦?支持?jǐn)U展的話,是不是就需要適配器模式了
說了這么多,是不是終于知道為什么需要 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; } } } }
從代碼中能看到,從一個(gè) this.handlerAdapters 屬性里面遍歷了我們的適配器。這個(gè) handlerAdapters 哪來的呢? 跟上面的 this.HandlerMappings 一樣,在 SpringMVC 的配置文件里面配置的,也就是上圖中的第二個(gè)屬性。
實(shí)現(xiàn) org.springframework.web.servlet.mvc.Controller 接口形式的處理器,對應(yīng)的 HandlerMapping 是 BeanNameUrlHandlerMapping ,對應(yīng)的 HandlerAdapter 是 HttpRequestHandlerAdapter
實(shí)現(xiàn) org.springframework.web.HttpRequestHandler 接口形式的處理器,對應(yīng)的 HandlerMapping 也是 BeanNameUrlHandlerMapping ,對應(yīng)的 HandlerAdapter 也是 HttpRequestHandlerAdapter 。
最后看一下三個(gè)適配器中的 supports() 和 handle() 方法
SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter 適配 org.springframework.web.servlet.mvc.Controller 這種 Handler 。
源碼非常之簡單,它是一個(gè)非常古老的適配器,幾乎已棄用狀態(tài)。
因?yàn)樗苯犹幚淼木褪窃瓷?HttpServletRequest 和 HttpServletResponse ,所以它和 Servlet容器 是強(qiáng)綁定的。
無數(shù)據(jù)自動(dòng)封裝、校驗(yàn)等一系列高級功能,所以實(shí)際應(yīng)用中此種方式很少被使用。
// 適配`org.springframework.web.servlet.mvc.Controller`這種Handler public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } // 最終執(zhí)行邏輯的還是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 。
那是因?yàn)?HttpRequestHandler#handleRequest() 它沒有返回值,這就需要全靠開發(fā)者自己寫 response ,而 Controller 最起碼來說還有 Model和View 自動(dòng)渲染的能力。
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 ,顯然這種處理器也是我們最最最最為常用的,它已經(jīng)把 HandlerMethod 的實(shí)現(xiàn)精確到了使用 @RequestMapping 注解標(biāo)注的方法。
這個(gè)類,我們要查看它的父類 AbstractHandlerMethodAdapter 。
public class AbstractHandlerMethodAdapter { // 只處理HandlerMethod 類型的處理器。抽象方法supportsInternal默認(rèn)返回true // 是留出的鉤子可以給你自己擴(kuò)展的 @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去實(shí)現(xiàn) return handleInternal(request, response, (HandlerMethod) handler); } }
看完之后,再來讀一下 DispatcherServlet#doDispatch() 方法的分發(fā)流程,看看 DispatcherServlet 是如何使用 HandlerMapping和HandlerAdapter 。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... //1、根據(jù)URL(當(dāng)然不一定非得是URL)匹配到一個(gè)處理器 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 若匹配不到Handler處理器,就404了 noHandlerFound(processedRequest, response); return; } //2、從HandlerExecutionChain里拿出Handler(注意是Object類型哦~ )然后找到屬于它的適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ... //3、執(zhí)行作用在此Handler上的所有攔截器的Pre方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //4、真正執(zhí)行handle方法(也就是你自己書寫的邏輯方法),得到一個(gè)ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //5、視圖渲染 applyDefaultViewName(processedRequest, mv); //6、執(zhí)行攔截器的post方法(可見它是視圖渲染完成了才會執(zhí)行的哦~) mappedHandler.applyPostHandle(processedRequest, response, mv); ... //7、執(zhí)行攔截器的afterCompletion方法(不管拋出與否) }
從執(zhí)行步驟中可以看到: HandlerAdapter 對于執(zhí)行流程的通用性起到了非常重要的作用,它能把任何一個(gè) Handler(注意是Object類型) 都適配成一個(gè) HandlerAdapter ,從而可以做統(tǒng)一的流程處理,這也是為何 DispatcherServlet 它能作為其它 web處理框架 的分發(fā)器的原因,因?yàn)樗鼪]有耦合具體的處理器,你完全可以自己去實(shí)現(xiàn)。
四、模擬HandlerMapping/HandlerAdapter的適配器模式
如果上面的講法,你還是不懂,下面就用適配器模式模擬一下,這兩個(gè)類的具體調(diào)用情況,應(yīng)該會一目了然。下面是依賴關(guān)系的類圖。其中 Controller 代表的就是 HandlerMapping 。具體代碼,可以在文章最后下載。
//多種Controller實(shí)現(xiàn) public interface Controller { } // 注意這里每個(gè)實(shí)現(xiàn),都用了不同的方法名, 如果都用一樣的話,就可以放到接口中了 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..."); } }
// 定義一個(gè)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(); // 得到對應(yīng)適配器 HandlerAdapter adapter = getHandler(controller); // 通過適配器執(zhí)行對應(yīng)的controller對應(yīng)方法 adapter.handle(controller); } public HandlerAdapter getHandler(Controller controller) { //遍歷:根據(jù)得到的controller(handler), 返回對應(yīng)適配器 for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(controller)) { return adapter; } } return null; } public static void main(String[] args) { new DispatchServlet().doDispatch(); // http... } }
注意: Controller 接口的每個(gè)實(shí)現(xiàn)類,都用了不同的方法名, 這樣的話就需要用到適配器模式了,如果都用一樣的話,就可以放到接口中了,這樣是不是可以理解 SpringMVC 中此處的 HandlerAdapter 了
五、小結(jié)
還是做個(gè)小結(jié)吧。 SpringMVC 的 Handler 有多種實(shí)現(xiàn)方式(Controller,HttpRequestHandler,Servlet等),例如繼承Controller接口的形式,基于注解@Controller控制器方式的,HttpRequestHandler方式的。
由于實(shí)現(xiàn)方式不一樣,調(diào)用方式就不確定。
- 繼承 Controller 方式所使用的HandlerMapping:BeanNameUrlHandlerMapping,
- 繼承 Controller 方式所使用的適配器:SimpleControllerHandlerAdapter 、
- 注解方式@Controller的HandlerMapping器:RequestMappingHandlerMapping
- 注解方式@Controller適配器:RequestMappingHandlerAdapter、
這是一一對應(yīng)的。
如果說的不對的,反饋一下給我啊,謝謝……
到此這篇關(guān)于SpringMVC中的HandlerMapping和HandlerAdapter詳解的文章就介紹到這了,更多相關(guān)HandlerMapping和HandlerAdapter內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring擴(kuò)展之基于HandlerMapping實(shí)現(xiàn)接口灰度發(fā)布實(shí)例
- SpringMVC中的handlerMappings對象用法
- Spring MVC學(xué)習(xí)教程之RequestMappingHandlerMapping匹配
- SpringMVC源碼解讀之 HandlerMapping - AbstractDetectingUrlHandlerMapping系列初始化
- SpringMVC源碼解讀之HandlerMapping - AbstractUrlHandlerMapping系列request分發(fā)
- SpringMVC源碼解讀之HandlerMapping
相關(guān)文章
Java getRealPath("/")與getContextPath()區(qū)別詳細(xì)分析
這篇文章主要介紹了Java getRealPath("/")與getContextPath()區(qū)別詳細(xì)分析,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringCloud URL重定向及轉(zhuǎn)發(fā)代碼實(shí)例
這篇文章主要介紹了SpringCloud URL重定向及轉(zhuǎn)發(fā)代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03詳解spring boot 使用application.properties 進(jìn)行外部配置
這篇文章主要介紹了詳解spring boot 使用application.properties 進(jìn)行外部配置,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03SpringBoot整合Redis實(shí)現(xiàn)消息發(fā)布與訂閱的示例代碼
能實(shí)現(xiàn)發(fā)送與接收信息的中間介有很多,比如:RocketMQ、RabbitMQ、ActiveMQ、Kafka等,本文主要介紹了Redis的推送與訂閱功能并集成Spring Boot的實(shí)現(xiàn),感興趣的可以了解一下2022-08-08Java中IO流 RandomAccessFile類實(shí)例詳解
這篇文章主要介紹了Java中IO流 RandomAccessFile類實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Springboot Retry組件@Recover失效問題解決方法
在使用springboot的retry模塊時(shí),你是否出現(xiàn)過@Recover注解失效的問題呢?不用擔(dān)心,這篇文章就來告訴你解決@Recover失效的辦法,需要的小伙伴可以參考一下2021-11-11