SpringMVC的工作原理是什麼樣的,跟Spring的關係是怎麼樣的?


Spring應用的IOC容器通過tomcat的Servlet或Listener監聽啟動載入;Spring MVC的容器由DispatchServlet作為入口載入;Spring容器是Spring MVC容器的父容器。

Spring MVC的工作原理如下:

(圖片來自網路)

  1. DispatcherServlet把請求分發到HandlerMapping
  2. HandlerMapping匹配到處理該url請求的Controller、Interceptor(根據xml配置、註解進行查找)返回給DispatcherServlet
  3. DispatcherServlet調用Interceptor、Controller進行請求處理
  4. Controller處理結果為ModelAndView返回給DispatcherServlet
  5. DispatcherServlet調用ViewResolver渲染ModelAndView為最終的View,最終轉為response返回給用戶


Spring MVC 的工作原理,很多人都不理解,今天借著題主的這個題目,我在此分享一篇外文翻譯的技術貼給大家,希望對大家理解這個知識點有所幫助。

  • 譯文鏈接:http://www.codeceo.com/article/how-spring-mvc-work.html
  • 英文原文:How Spring MVC Really Works
  • 翻譯作者:碼農網 – 小峰

本文將深入探討Spring框架的一部分——Spring Web MVC的強大功能及其內部工作原理。涉及的源代碼可以在GitHub上找到。

項目安裝

在本文中,我們將使用最新、最好的Spring Framework 5。我們將重點介紹Spring的經典Web堆棧,該堆棧從框架的第一個版本中就嶄露頭角,並且現在依然是用Spring構建Web應用程序的主要方式。

對於初學者來說,為了安裝測試項目,最好使用Spring Boot和一些初學者依賴項;還需要定義parent:

&


&org.springframework.boot&
&spring-boot-starter-parent&
&2.0.0.M5&
&
& &
&
&org.springframework.boot&
&spring-boot-starter-web&
&

&
&org.springframework.boot&
&spring-boot-starter-thymeleaf&
&

&

請注意,為了使用Spring 5,我們還需要使用Spring Boot 2.x。截止到撰寫本文之時,這依然是里程碑發布版,可在Spring Milestone Repository中找到。讓我們把這個存儲庫添加到你的Maven項目中:

&
&
&spring-milestones&
&Spring Milestones&
&https://repo.spring.io/milestone&
&
&false&
&

&

&

你可以在Maven Central上查看Spring Boot的當前版本。

示例項目

為了理解Spring Web MVC是如何工作的,我們將通過一個登錄頁面實現一個簡單的應用程序。為了顯示登錄頁面,我們需要為上下文根創建帶有GET映射的@Controller註解類InternalController。

hello()方法是無參數的。它返回一個由Spring MVC解釋為視圖名稱的String(在示例中是login.html模板):

import org.springframework.web.bind.annotation.GetMapping;
@GetMapping("/")
public String hello() {
return "login";
}

為了處理用戶登錄,需要創建另一個用登錄數據處理POST請求的方法。然後根據結果將用戶重定向到成功或失敗的頁面。

請注意,login()方法接收域對象作為參數並返回ModelAndView對象:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
@PostMapping("/login")
public ModelAndView login(LoginData loginData) {
if (LOGIN.equals(loginData.getLogin())
PASSWORD.equals(loginData.getPassword())) {
return new ModelAndView("success",
Collections.singletonMap("login", loginData.getLogin()));
} else {
return new ModelAndView("failure",
Collections.singletonMap("login", loginData.getLogin()));
}
}

ModelAndView是兩個不同對象的持有者:

  • Model——渲染頁面數據的鍵值映射
  • View——填充模型數據的頁面模板

連接這些是為了方便,這樣控制器方法可以一次返回它們。

要渲染HTML頁面,使用Thymeleaf作為視圖模板引擎,該引擎具有可靠和開箱即用的與Spring的集成。

Servlet作為Java Web應用程序的基礎

那麼,當在瀏覽器中輸入http:// localhost:8080/時,按Enter鍵,然後請求到達Web伺服器,實際發生了什麼?你如何從這個請求中看到瀏覽器中的Web表單?

鑒於該項目是一個簡單的Spring Boot應用程序,因此可以通過Spring5Application運行它。

Spring Boot默認使用Apache Tomcat。因此,運行應用程序時,你可能會在日誌中看到以下信息:

2017-10-16 20:36:11.626 INFO 57414 --- [main]
o.s.b.w.embedded.tomcat.TomcatWebServer :
Tomcat initialized with port(s): 8080 (http)
2017-10-16 20:36:11.634 INFO 57414 --- [main]
o.apache.catalina.core.StandardService :
Starting service [Tomcat]
2017-10-16 20:36:11.635 INFO 57414 --- [main]
org.apache.catalina.core.StandardEngine :
Starting Servlet Engine: Apache Tomcat/8.5.23

由於Tomcat是一個Servlet容器,因此發送給Tomcat Web伺服器的每個HTTP請求自然都由Java servlet處理。所以Spring Web應用程序入口點是一個servlet,這並不奇怪。

簡單地說,servlet就是任何Java Web應用程序的核心組件;它是低層次的,不會像MVC那樣在特定的編程模式中諸多要求。

一個HTTP servlet只能接收一個HTTP請求,以某種方式處理,然後發回一個響應。

而且,從Servlet 3.0 API開始,你現在可以超越XML配置,並開始利用Java配置(只有很小的限制條件)。

DispatcherServlet作為Spring MVC的核心

作為一個Web應用程序的開發人員,我們真正想要做的是抽象出以下繁瑣和模板化的任務,並專註於有用的業務邏輯:

  • 將HTTP請求映射到某個處理方法
  • 將HTTP請求數據和標題解析成數據傳輸對象(DTO)或域對象
  • 模型 – 視圖 – 控制器集成
  • 從DTO、域對象等生成響應

Spring DispatcherServlet能夠提供這些。它是Spring Web MVC框架的核心;此核心組件接收所有請求到應用程序。

正如你所看到的,DispatcherServlet是非常可擴展的。例如,它允許你插入不同的現有或新的適配器進行大量的任務:

  • 將請求映射到應該處理它的類或方法(HandlerMapping介面的實現)
  • 使用特定模式處理請求,如常規servlet,更複雜的MVC工作流,或POJO bean中的方法(HandlerAdapter介面的實現)
  • 按名稱解析視圖,允許你使用不同的模板引擎,XML,XSLT或任何其他視圖技術(ViewResolver介面的實現)
  • 通過使用默認的Apache Commons文件上傳實現或編寫你自己的MultipartResolver來解析多部分請求
  • 使用任何LocaleResolver實現解決語言環境,包括cookie,會話,Accept HTTP頭,或任何其他確定用戶所期望的語言環境的方式

處理HTTP請求

首先,我們將簡單的HTTP請求的處理追蹤到在控制器層中的一個方法,然後返回到瀏覽器/客戶端。

DispatcherServlet具有很長的繼承層次結構;自上而下地逐個理解這些是有價值的。請求處理方法最讓我們感興趣。

理解HTTP請求,無論是在本地還是遠程的標準開發中,都是理解MVC體系結構的關鍵部分。

GenericServlet

GenericServlet是Servlet規範的一部分,不直接關注HTTP。它定義了接收傳入請求併產生響應的service()方法。

注意,ServletRequest和ServletResponse方法參數如何與HTTP協議無關:

public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;

這是最終被任何請求調用到伺服器上的方法,包括簡單的GET請求。

HttpServlet

顧名思義,HttpServlet類就是規範中定義的基於HTTP的Servlet實現。

更實際的說,HttpServlet是一個抽象類,有一個service()方法實現,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);
// ...
}

HttpServletBean

接下來,HttpServletBean是層次結構中第一個Spring-aware類。它使用從web.xml或WebApplicationInitializer接收到的servlet init-param值來注入bean的屬性。

在請求應用程序的情況下,doGet(),doPost()等方法應特定的HTTP請求而調用。

FrameworkServlet

FrameworkServlet集成Servlet功能與Web應用程序上下文,實現了ApplicationContextAware介面。但它也能夠自行創建Web應用程序上下文。

正如你已經看到的,HttpServletBean超類注入init-params為bean屬性。所以,如果在servlet的contextClass init-param中提供了一個上下文類名,那麼這個類的一個實例將被創建為應用程序上下文。否則,將使用默認的XmlWebApplicationContext類。

由於XML配置現在已經過時,Spring Boot默認使用AnnotationConfigWebApplicationContext配置DispatcherServlet。但是你可以輕鬆更改。

例如,如果你需要使用基於Groovy的應用程序上下文來配置Spring Web MVC應用程序,則可以在web.xml文件中使用以下DispatcherServlet配置:

dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.GroovyWebApplicationContext

使用WebApplicationInitializer類,可以用更現代的基於Java的方式來完成相同的配置。

DispatcherServlet:統一請求處理

HttpServlet.service()實現,會根據HTTP動詞的類型來路由請求,這在低級servlet的上下文中是非常有意義的。然而,在Spring MVC的抽象級別,方法類型只是可以用來映射請求到其處理程序的參數之一。

因此,FrameworkServlet類的另一個主要功能是將處理邏輯重新加入到單個processRequest()方法中,processRequest()方法反過來又調用doService()方法:

@Override
protected final void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
// …

DispatcherServlet:豐富請求

最後,DispatcherServlet實現doService()方法。在這裡,它增加了一些可能會派上用場的有用對象到請求:Web應用程序上下文,區域解析器,主題解析器,主題源等:

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());

另外,doService()方法準備輸入和輸出Flash映射。Flash映射基本上是一種模式,該模式將參數從一個請求傳遞到另一個緊跟的請求。這在重定向期間可能非常有用(例如在重定向之後向用戶顯示一次性信息消息):

FlashMap inputFlashMap = this.flashMapManager
.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

然後,doService()方法調用負責請求調度的doDispatch()方法。

DispatcherServlet:調度請求

dispatch()方法的主要目的是為請求找到合適的處理程序,並為其提供請求/響應參數。處理程序基本上是任何類型的object,不限於特定的介面。這也意味著Spring需要為此處理程序找到適配器,該處理程序知道如何與處理程序「交談」。

為了找到匹配請求的處理程序,Spring檢查HandlerMapping介面的註冊實現。有很多不同的實現可以滿足你的需求。

SimpleUrlHandlerMapping允許通過URL將請求映射到某個處理bean。例如,可以通過使用java.util.Properties實例注入其mappings屬性來配置,就像這樣:

/welcome.html=ticketController
/show.html=ticketController

可能處理程序映射最廣泛使用的類是RequestMappingHandlerMapping,它將請求映射到@Controller類的@ RequestMapping注釋方法。這正是使用控制器的hello()和login()方法連接調度程序的映射。

請注意,Spring-aware方法使用@GetMapping和@PostMapping進行注釋。這些注釋依次用@RequestMapping元注釋標記。

dispatch()方法還負責其他一些HTTP特定任務:

  • 在資源未被修改的情況下,GET請求的短路處理
  • 針對相應的請求應用多部分解析器
  • 如果處理程序選擇非同步處理該請求,則會短路處理該請求

處理請求

現在Spring已經確定了請求的處理程序和處理程序的適配器,是時候來處理請求了。下面是HandlerAdapter.handle()方法的簽名。請注意,處理程序可以選擇如何處理請求:

  • 自主地編寫數據到響應對象,並返回null
  • 返回由DispatcherServlet呈現的ModelAndView對象

@Nullable
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;

有幾種提供的處理程序類型。以下是SimpleControllerHandlerAdapter如何處理Spring MVC控制器實例(不要將其與@ Controller注釋POJO混淆)。

注意控制器處理程序如何返回ModelAndView對象,並且不自行呈現視圖:

public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
return ((Controller) handler).handleRequest(request, response);
}

第二個是SimpleServletHandlerAdapter,它將常規的Servlet作為請求處理器。

Servlet不知道任何有關ModelAndView的內容,只是簡單地自行處理請求,並將結果呈現給響應對象。所以這個適配器只是返回null而不是ModelAndView:

public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
((Servlet) handler).service(request, response);
return null;
}

我們碰到的情況是,控制器是有若干@RequestMapping注釋的POJO,所以任何處理程序基本上是包裝在HandlerMethod實例中的這個類的方法。為了適應這個處理器類型,Spring使用RequestMappingHandlerAdapter類。

處理參數和返回處理程序方法的值

注意,控制器方法通常不會使用HttpServletRequest和HttpServletResponse,而是接收和返回許多不同類型的數據,例如域對象,路徑參數等。

此外,要注意,我們不需要從控制器方法返回ModelAndView實例。可能會返回視圖名稱,或ResponseEntity,或將被轉換為JSON響應等的POJO。

RequestMappingHandlerAdapter確保方法的參數從HttpServletRequest中解析出來。另外,它從方法的返回值中創建ModelAndView對象。

在RequestMappingHandlerAdapter中有一段重要的代碼,可確保所有這些轉換魔法的發生:

ServletInvocableHandlerMethod invocableMethod
= createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(
this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(
this.returnValueHandlers);
}

argumentResolvers對象是不同的HandlerMethodArgumentResolver實例的組合。

有超過30個不同的參數解析器實現。它們允許從請求中提取任何類型的信息,並將其作為方法參數提供。這包括URL路徑變數,請求主體參數,請求標頭,cookies,會話數據等。

returnValueHandlers對象是HandlerMethodReturnValueHandler對象的組合。還有很多不同的值處理程序可以處理方法的結果來創建適配器所期望的ModelAndViewobject。

例如,當你從hello()方法返回字元串時,ViewNameMethodReturnValueHandler處理這個值。但是,當你從login()方法返回一個準備好的ModelAndView時,Spring會使用ModelAndViewMethodReturnValueHandler。

渲染視圖

到目前為止,Spring已經處理了HTTP請求並接收了ModelAndView對象,所以它必須呈現用戶將在瀏覽器中看到的HTML頁面。它基於模型和封裝在ModelAndView對象中的選定視圖來完成。

另外請注意,我們可以呈現JSON對象,或XML,或任何可通過HTTP協議傳輸的其他數據格式。我們將在即將到來的REST-focused部分接觸更多。

讓我們回到DispatcherServlet。render()方法首先使用提供的LocaleResolver實例設置響應語言環境。假設現代瀏覽器正確設置了Accept頭,並且默認使用AcceptHeaderLocaleResolver。

在渲染過程中,ModelAndView對象可能已經包含對所選視圖的引用,或者只是一個視圖名稱,或者如果控制器依賴於默認視圖,則什麼都沒有。

由於hello()和login()方法兩者都指定所需的視圖為String名稱,因此必須用該名稱查找。所以,這是viewResolvers列表開始起作用的地方:

for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}

這是一個ViewResolver實例列表,包括由thymeleaf-spring5集成庫提供的ThymeleafViewResolver。該解析器知道在哪裡搜索視圖,並提供相應的視圖實例。

在調用視圖的render()方法後,Spring最終通過發送HTML頁面到用戶的瀏覽器來完成請求處理。

REST支持

除了典型的MVC場景之外,我們還可以使用框架來創建REST Web服務。

簡而言之,我們可以接受Resource作為輸入,指定POJO作為方法參數,並使用@RequestBody對其進行注釋。也可以使用@ResponseBody注釋方法本身,以指定其結果必須直接轉換為HTTP響應:

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@PostMapping("/message")
public MyOutputResource sendMessage(
@RequestBody MyInputResource inputResource) {
return new MyOutputResource("Received: "
+ inputResource.getRequestMessage());
}

歸功於Spring MVC的可擴展性,這也是可行的。

為了將內部DTO編組為REST表示,框架使用HttpMessageConverter基礎結構。例如,其中一個實現是MappingJackson2HttpMessageConverter,它可以使用Jackson庫將模型對象轉換為JSON或從JSON轉換。

為了進一步簡化REST API的創建,Spring引入了@RestController註解。默認情況下,這很方便地假定了@ResponseBody語義,並避免在每個REST控制器上的明確設置:

import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestfulWebServiceController {
@GetMapping("/message")
public MyOutputResource getMessage() {
return new MyOutputResource("Hello!");
}
}

結論

在這篇文章中,我們詳細了介紹在Spring MVC框架中請求的處理過程。了解框架的不同擴展是如何協同工作來提供所有魔法的,可以讓你能夠事倍功半地處理HTTP協議難題。


Spring可以說是一個管理bean的容器,也可以說是包括很多開源項目的總稱,Spring MVC是其中一個開源項目,所以簡單走個流程的話,http請求一到,由容器(如:tomact)解析http搞成一個request,通過映射關係(路徑,方法,參數啊)被Spring MVC一個分發器去找到可以處理這個請求的bean,那tomcat裡面就由spring管理bean的一個池子(bean容器)裡面找到,處理完了就把響應返回回去。

經常看到寫個Spring MVC的controller裡面有個註解service吧,spring的ioc功能就可以把這個sercice插進去(反射)。當然具體過程好複雜的,這只是個大概的大概。

註:希望繼續在IT行業突破提升自己的各位朋友,也歡迎加群384053806,不管你自我感覺牛不牛B。


上圖只是Spring MVC比較粗淺的執行過程,詳細的執行過程有時間再來答吧。由上圖可知,Spring MVC大致的執行流程如下: 1、首先瀏覽器發送請求給前端控制器DispatcherServlet,DispatcherSerlvet根據請求信息,基於一定的原則選擇合適的控制器進行處理並把 請求委託給它。

2、頁面控制器接收到請求之後進行功能處理,首先需要收集、綁定請求參數到一個對象(命令對象),並進行驗證,然後將該對象委託給業務對象進行處理(service層);業務對象處理之後控制器將返回一個ModelAndView(模型數據和邏輯視圖名);

3、DispatcherServlet根據返回的邏輯視圖名,選擇合適的視圖進行渲染(界面展示、資源載入),並把模型數據傳入以便視圖渲染。

4、前端控制器將響應返回個客戶端瀏覽器。


建議看一下黃勇寫的《架構探險,從零開發一個web框架》。作者相當於實現了一個簡單的spring,springmvc框架。我只花了兩天時間看了前半本,就對spring與springmvc有了大致了解。


Spring 框架是一個分層架構,由 7 個定義良好的模塊組成。Spring模塊構建在核心容器之上,核心容器定義了創建、配置和管理 bean 的方式,SpringMVC屬於其中的一個模塊。

Spring MVC 框架:MVC框架是一個全功能的構建 Web應用程序的 MVC 實現。通過策略介面,MVC框架變成為高度可配置的,MVC 容納了大量視圖技術,其中包括 JSP、Velocity、Tiles、iText 和 POI。模型由javabean構成,存放於Map;視圖是一個介面,負責顯示模型;控制器表示邏輯代碼,是Controller的實現。

Spring框架的功能可以用在任何J2EE伺服器中,大多數功能也適用於不受管理的環境。Spring 的核心要點是:支持不綁定到特定 J2EE服務的可重用業務和數據訪問對象。毫無疑問,這樣的對象可以在不同J2EE 環境(Web 或EJB)、獨立應用程序、測試環境之間重用。


Spring MVC屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裡面。Spring 框架提供了構建 Web 應用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構,從而在使用Spring進行WEB開發時,可以選擇使用Spring的SpringMVC框架或集成其他MVC開發框架,如Struts1,Struts2等。

Spring MVC的整個工作過程是從一個HTTP請求開始:

1)DispatcherServlet接收到請求後,根據對應配置文件中配置的處理器映射,找到對應的處理器映射項(HandlerMapping),根據配置的映射規則,找到對應的處理器(Handler)。

2)調用相應處理器中的處理方法,處理該請求,處理器處理結束後會將一個ModelAndView類型的數據傳給DispatcherServlet,這其中包含了處理結果的視圖和視圖中要使用的數據。

3)DispatcherServlet根據得到的ModelAndView中的視圖對象,找到一個合適的ViewResolver(視圖解析器),根據視圖解析器的配置,DispatcherServlet將視圖要顯示的數據傳給對應的視圖,最後給瀏覽器構造一個HTTP響應。 DispatcherServlet是整個Spring MVC的核心。

DispatcherServlet負責接收HTTP請求組織協調Spring MVC的各個組成部分。其主要工作有以下三項:

1)截獲符合特定格式的URL請求。

2)初始化DispatcherServlet上下文對應的WebApplicationContext,並將其與業務層、持久化層的WebApplicationContext建立關聯。

3)初始化Spring MVC的各個組成組件,並裝配到DispatcherServlet中。

然後簡單說一下Spring MVC與Spring的關係。Spring可以說是一個管理bean的容器,也可以說是包括很多開源項目的總稱,而Spring MVC是其中一個開源項目。如果簡單進行一個流程,當http請求一到,由容器(如:Tomcat)解析http形成一個request,通過映射關係(比如路徑,方法,參數)被Spring MVC一個分發器去找到可以處理這個請求的bean,在Tomcat裡面就由Spring管理bean的一個池子(bean容器)裡面找到。處理完了就把響應返回。

基於Spring實現的MVC框架是不能不使用Spring的。單獨使用Spring MVC,因為其需要依賴IOC容器。但是如果單獨為了更好的理解SpringMVC這種MVC框架,就把它和Struts/Struts2等一系列的MVC框架對比理解,理解其只是基於DispatcherServlet或者Filter做一個前端分發器,最終把這個框架引導起來,進行其自己的邏輯處理。


推薦閱讀:

初學安卓開發應該用Eclipse還是Android studio?
如何才能刪除這個文件夾?
985本科畢業工科生,畢業兩年半,國企從事市場管理工作,想轉行IT行業,請問該如何著手?
程序員在工作期間是不是不喜歡接電話?
JAVA的內存是如何劃分的?

TAG:Java | Spring | SpringMVC框架 |