SpringMVC工作原理例項詳解
介紹
SpringWeb MVC是Spring Framework中的一部分,當我們需要使用spring框架建立web應用的時候就需要引入springweb mvc。對於程式設計師來說,我們只需要增加@Controller ,@RequestMapping註解然後,瀏覽器中的請求就會到達springweb應用。我們只需要在 controller中編寫相關邏輯即可。然而,請求是在哪裡接收的?@Controller ,@RequestMapping註解做了什麼?本文我們來探討一下。
從一個專案開始
本文假定你已經能熟練的使用springmvc。為了展開後續的討論,假設我們新建了一個spring-mvc-demo的專案。並由此專案來展開討論。專案中有一個控制器,程式碼如下:
@Controller @RequestMapping("/app") public class AppController { @RequestMapping(method=RequestMethod.GET,value="/hello") public @ResponseBody String hello(HttpServletRequest request,String name) { return "Hello,"+name; } }
控制器寫好之後,我們將專案打車war包,放入tomcat容器中,並使用8080埠啟動tomcat,執行專案,然後在瀏覽器中輸入http://localhost:8080/app/hello?name=world.
我們在瀏覽器中可以看到:Hello,world的輸出。
我們先記住這個例子,下面我們帶著一些疑問繼續看,這個請求是怎麼被接收到的?請求是怎麼交給AppController處理的?
Servlet是Java Web應用的基石
當你在瀏覽器中輸入 http://loalhost:8080/ ,按下enter建,然後請求命中了伺服器,這是怎麼發生的?又如何從這個請求中得到瀏覽器中可見的頁面?
本例中,我們給出的是一個簡單的spring-mvc應用,並放入了tomcat中(springboot 內嵌tomcat啟動其實也是一樣的)。 Tomcat 是一個servlet容器,這點我想每個Java程式設計師都十分清楚,我們在沒有使用spring-mvc之前,就是使用servlet+jsp來開發web應用。
由於Tomcat是一個web容器,每一個傳送給Tomcat伺服器的HTTP請求自然會被一個Java Servlet處理。所以,SpringMvc 必定有一個servlet,SpringWeb應用的入口必定是一個Servlet,這應該不難想到。
簡單來說,Servlet是任何Java Web應用的核心元件(除非你不用servlet規範,比如你使用netty)。Servlet它是低層次的,並且不會像MVC那樣強加於特定的程式設計模式。它只是可以讓你寫一個偶一個Servlet,一個HTTP Servlet可以接受一個HTTP請求,然後處理它,並返回一個響應。
而springmvc 就是使用了一個大的servlet,下面我們就來說這個大的servlet。
DispatcherServlet是Spring MVC的核心
上面我們已經提到Servlet 是Java web應用的基石,Spring應用入口必定是一個Servlet,這個Servlet 其實就是DispatcherServlet。
作為WEB應用的開發人員,我們真正想做的是抽象出以下繁瑣和模板化的任務,並專注於有用的業務邏輯:
- 對映一個HTTP請求到某個處理方法。
- 將HTTP請求資料,和頭資訊轉換成資料物件(DTO / domain object)。
- 模型 - 檢視 - 控制器 之間的互動。
- 從DTO,域物件等生成響應
Spring DispatcherServlet提供了這些。它是Spring Web MVC框架的核心, 這個核心元件接收所有請求到您的應用程式。
DispatcherServlet具有很強的可擴充套件性。 例如,它允許您插入不同的現有或新介面卡以執行大量任務:
- 將請求對映到應該處理它的類或方法(HandlerMapping介面的實現)
- 使用特定模式處理請求,例如常規servlet,更復雜的MVC工作流或者POJO bean中的方法(HandlerAdapter介面的實現)
- 通過名稱解析檢視,允許您使用不同的模板引擎,XML,XSLT或任何其他檢視技術(ViewResolver介面的實現)
- 通過使用預設的Apache Commons檔案上傳實現或編寫自己的MultipartResolver來解析multipart請求
- 使用任何LocaleResolver實現解決語言環境,包括Cookie,會話,Accept HTTP標頭或用於確定使用者期望的語言環境的任何其他方式
處理HTTP請求
首先,讓我們來追蹤一個簡單的HTTP請求到達controller中的方法,然後返回到 瀏覽器/客戶端的處理過程。
DispatcherServlet 有一個很長的繼承關係。它的繼承關係是這樣的:
GenericServlet <- HttpServlet <- HttpServletBean <- FreamworkServlet <- DispatcherServlet
GenericServlet
GenericServlet是Servlet規範的一部分,它並不直接關注HTTP。它定義了一個service()方法用來接收傳遞過來的請求,併產生響應。(這裡的請求和響應不是指HTTP請求)
public abstract void service(ServletRequest req,ServletResponse res) throws ServletException,IOException;
注意,這裡的引數中的ServletRequest,ServletResponse並不是和HTTP協議繫結的,Http有具體協議Servlet。
HttpServlet
顧名思義,HttpServlet類就是規範中定義的基於HTTP的Servlet實現。
更實際的是,HttpServlet是一個具有service()方法實現的抽象類,它通過HTTP方法型別分割請求,大致如下所示:
protected void service(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { // ... doGet(req,resp); } else if (method.equals(METHOD_HEAD)) { // ... doHead(req,resp); } else if (method.equals(METHOD_POST)) { doPost(req,resp); // ... }
根據請求的不同, get,post方法會分別被不同方法處理。
HttpServletBean
上面我們展示過 DispatcherServlet的繼承關係,在這個繼承鏈中,HttpServletBean是進入spring的第一個層次。從HttpServletBean開始往下的幾個servlet都是spring中的類。HttpServletBean 就是一個servlet,它繼承自HttpServlet,就像是我們在使用servlet+jsp開發時候定義的servlet一樣。
根據servlet的生命週期我們知道,servlet會被容器初始化,初始化時候,其init()方法會被呼叫。在springmvc框架中 HttpServletBean使用從web.xml或WebApplicationInitializer收到的servlet init-param值來注入bean的屬性。
在請求應用程式的情況下,為這些特定的HTTP請求呼叫doGet(),doPost()等方法。
FrameworkServlet
FrameworkServlet將Servlet功能與Web應用程式上下文整合,實現ApplicationContextAware介面。但它也能夠自行建立Web應用程式上下文。
正如上面所說,FrameworkServlet的超類HttpServletBean將init-param注入為bean屬性。所以,如果servlet的contextClass init-param中提供了context類的名字,那麼這個context類的例項將會被建立,用作應用的context。否則,將會使用XmlWebApplicationContext作為預設的。
DispatcherServlet: 統一請求處理
有了上面鋪墊,我們這裡可以開始關鍵的內容,即DispatcherServlet統一請求處理。在Springmvc的專案中,我們通常會在web.xml配置一個servlet,如下:
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
上面我們提到,DispatcherServlet繼承關係,其父類中正兒八經的servlet類是HttpServletBean這個servlet類是應用啟動入口。其生命週期的第一階段init()方法完成初始化工作。
doService()方法設定請求資訊
DispatcherServlet 初始化之後,便可以工作了。當請求到達之時,會呼叫其doService()方法。doService()方法的程式碼如下:
@Override protected void doService(HttpServletRequest request,HttpServletResponse response) throws Exception { // 刪除一下‘非核心程式碼'方便檢視 // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE,this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE,getThemeSource()); try { doDispatch(request,response); } finally { if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } // Restore the original attribute snapshot,in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request,attributesSnapshot); } } }
可以看到,doService()方法先設定一些 request資訊,這些資訊在後續的處理過程中會用到。設定完後,它呼叫doDispatch() 方法。
doDispatch()方法分發請求
doService()方法最終呼叫了doDispatch(),看名知意,這個方法是做分發工作的。其程式碼如下:
protected void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception { //刪除一些程式碼方便閱讀 HandlerExecutionChain mappedHandler = null; try { ModelAndView mv = null; Exception dispatchException = null; try { // 刪除一些程式碼方便閱讀 mappedHandler = getHandler(processedRequest,false); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); try { // Actually invoke the handler. mv = ha.handle(processedRequest,response,mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request,mv); mappedHandler.applyPostHandle(processedRequest,mv); } catch (Exception ex) { dispatchException = ex; // 這裡捕獲了異常TypeMismatchException } processDispatchResult(processedRequest,mappedHandler,mv,dispatchException); } catch (Exception ex) { } finally { // 刪除一些程式碼 } }
這個方法主要作用就是找到合適的 handler 來處理請求。handler通常是一個某個型別的物件,並且不限定特定的介面。因此spring需要找到這個handler的介面卡。這個Handler通常是一個HandlerMethod例項,
為了找到與請求匹配handler,spring需要從已註冊的HandlerMapping介面實現類裡邊去找。這個查詢過程就是在上面的getHandler() 方法完成得到的是一個HandlerExecutionChain。 這裡體現了責任鏈模式。
這個getHandler() 會遍歷一個HandlerMapping的map。由於我們一般都使用註解形式:@Controller,@RequestMapping註解。因此這裡找到HandlerMapping實現就是RequestMappingHandlerMapping
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
getHandlerAdapter()方法找到最終的handler介面卡,找到的介面卡就是RequestMappingHandlerAdapter,(因為我們使用的是@RequestMapping註解形式)。
本例中,我們定義了AppController 的hello()方法,並用@Controller,@RequestMapping對其分別進行註解,因此這裡得到的介面卡HandlerAdapter 所適配HandlerMethod就是 AppController 的hello()方法的 。
HandlerAdapter處理請求
上面通過 確定了HandlerAdapter之後,就要執行handle() 方法了,即上面程式碼中,try語句塊裡邊的ha.handle()。handle()方法定義為:
ModelAndView handle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception;
這個方法有兩種處理方式:
- 自己將資料寫到response,然後return null
- 返回一個ModelAndView 物件,讓DispatcherServlet自己來處理後面渲染工作。
HandlerAdapter有多重型別,例如
SimpleControllerHandlerAdapter處理spring mvc 的controller例項(注意,不要把這裡的controller例項和@Controller註解POJO混淆,這裡controller 指的是org.springframework.web.servlet.mvc.Controller ),並返回ModelAndView,程式碼如下:
public ModelAndView handle(HttpServletRequest request,Object handler) throws Exception { return ((Controller) handler).handleRequest(request,response); }
SimpleServletHandlerAdapter 適配的是 Servlet作為request handler的情況,Servlet是不知道MovelAndView的,所以,它的方法並不負責渲染頁面,因此沒有返回ModelAndView,只是返回null:
public ModelAndView handle(HttpServletRequest request,Object handler) throws Exception { ((Servlet) handler).service(request,response); return null; }
RequestMappingHandlerAdapter 就是我們上面提到的,用來處理@Controller和@RequestMapping註解的handler。
渲染檢視
handle()方法呼叫之後, DispatcherServlet 可以得到一個ModelAndView,當然也可能是null。對於ModelAndView不為null的時候,DispatcherServlet 將會呼叫render()方法。ModelAndView中可能已經包含了一個view或者只是一個view的名字。如果controller方法指定的是一個字串形式的檢視名字,那麼就需要進行試圖查詢:
for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName,locale); if (view != null) { return view; } }
render()方法完成之後,最終的HTML頁面會被髮送至瀏覽器端。
當然,springmvc不僅能渲染出頁面,也可以返回JSON形式或者XML形式。這種情況controller方法一般都是由@RequestBody標註的。這種情況就需要 HttpMessageConverter,例如渲染JSON的時候可以使用Jackson包,我們要返回的物件將由,MappingJackson2HttpMessageConverter來轉換。
到此,我們就大概說完了springmvc的整個流程。所以,springmvc其實就是一個大的Servlet,接收請求,分發執行請求,我們的每一個controller中的方法都是一個handler,然後最終渲染檢視。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。