本文是基于慕課網小馬哥的 《Spring Boot 2.0深度實踐之核心技術篇》的內容結合自己的需要和理解做的筆記。
理解 Spring Web MVC 架構
在解釋Spring Web Mvc 的架構之前,首先我們要了解一下基于Servlet的基礎架構之上構建的一種J2EE的設計模式--Front Controller(前端總控制器模式)
通過流程圖我們可以了解到:
- 當客戶端發(fā)送請求到前端總控制器(Front Controller),在這里前端總控制器有兩種實現《Servlet》和《JSP》穷遂。
- 前端總控制器通過委派發(fā)送給應用控制器(ApplicationController)栖茉。
- 應用控制器通過委派調用一個命令對象(Command)楼咳,然后分發(fā)給對應的視圖處理器(View)以清。
介紹完前端總控制器凸舵,接下來對于理解SpringMvc架構就很簡單了叠必。
通過流程圖我們可以了解到:
- 通過客戶端發(fā)送的請求到前端總控制器(Front Controller) 也就是我們經常提到的DispatcherServlet荚孵。
- 前端總控制器委派給Controller,也就是我們常用的SpringMVC使用的控制層纬朝,也就是標注@Controller的業(yè)務模型收叶。
- 然后通過DispatcherServlet將業(yè)務處理的結果委派給視圖渲染模型。
- 最終返回視圖對象給DispatcherServlet,響應結果玄组。
SpringMvc核心組件以及交互流程
SpringMvc核心組件
- 處理器管理
- 映射:HandlerMapping
- 適配:HandlerAdapter
- 執(zhí)行:HandlerExecutionChain
- 渲染
- 視圖解析:ViewResolver
- 國際化: LocaleResolver,LocaleContextResolver
- 個性化:ThemeResolver
- 異常處理
- HandlerExceptionResolver
各個組件的介紹內容在Web on Servlet Stack中的 1.2.2. Special Bean Types章節(jié)中滔驾。
SpringMvc交互流程
流程說明
- 用戶發(fā)送請求至前端控制器 DispatcherServlet谒麦。
- DispatcherServlet 收到請求調用 HandlerMapping 處理器映射器。處理器映射器根據請求 url 找到具體的處理器哆致,生成處理器對象及處理器攔截器(如果有則生成)一并返回給 DispatcherServlet绕德。
- DispatcherServlet 通過 HandlerAdapter 處理器適配器調用處理器。
- HandlerAdapter 執(zhí)行Controller層中的對應方法摊阀。
- Controller 執(zhí)行完成返回 ModelAndView或ViewName耻蛇。 HandlerAdapter 將 handler 執(zhí)行結果 ModelAndView 返回給 DispatcherServlet。
- DispatcherServlet 將 ModelAndView 傳給 ViewReslover 視圖解析器胞此。
- ViewReslover 解析后返回具體 View 對象臣咖。 DispatcherServlet 對 View 進行渲染視圖(即將模型數據填充至視圖中)。
- DispatcherServlet 響應用戶漱牵。
源碼理解交互流程
準備一個簡單的SpringMVC項目
在Debugger源碼之前夺蛇,先讓我們創(chuàng)建一個簡單的spring項目,只需要一個簡單的請求返回一個JSP頁面就可以酣胀。
我創(chuàng)建的spring項目全部都使用了注解驅動(版本必須是Spring Framework 3.1 +,Servlet 3.0)刁赦。
在這里不闡述是如何實現自動裝配Spring MVC項目的,后面會有單獨的介紹,先簡單給出代碼讓我們的程序跑起來闻镶,方便一步一步Debugger甚脉。
項目目錄如下:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
這里需要注意一下 我使用的是IntelliJ IDEA 商業(yè)版,所以不需要內置tomcat插件铆农。
WEB-INF/jsp/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>
替換web.xml的配置類 --DefaultAnnotationConfigDispatcherServletInitializer
package com.web.servlet.support;
import com.web.configuration.DispatchServletConfiguration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* web.xml的配置
*/
public class DefaultAnnotationConfigDispatcherServletInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() { //配置DispatcherServlet
return new Class[]{DispatchServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
替換application-context.xml的配置類 --WebMvcConfig
package com.web.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link DispatcherServlet}
* 掃描使用注解驅動的包
* 相當于 <context:component-scan base-package="com.web"/>
*/
@ComponentScan(basePackages = "com.web")
public class DispatchServletConfiguration {
}
以上的XML配置和java代碼通過對應的注釋已經一一對應解釋了牺氨。
簡單的Controller層 --HelloWorldController
package com.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 簡單controller
*/
@Controller
public class HelloWorldController {
@RequestMapping("/index")
public String index() {
return "index";
}
}
這里全部代碼已經給出。下面讓我們開始調試吧墩剖。讓我們啟動tomcat,訪問http://localhost:8080/index猴凹。
執(zhí)行流程
流程的核心就是 org.springframework.web.servlet.DispatcherServlet
這個類。
核心方法就是#doDispatch()
方法涛碑。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 確定當前請求的處理器也就是各種HandlerMapping
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 確定當前請求的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 調用實際的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
根據上面的源碼我們可以看到核心的過程
// Determine handler for the current request.
// 確定當前請求的處理器也就是各種HandlerMapping
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
// 確定當前請求的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
// 調用實際的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
現在讓我們分步來debugger這幾步精堕,一點一點理解其流程。
1.org.springframework.web.servlet.DispatcherServlet#getHandler
我們可以看到spring默認實現了 HandlerMapping
的5個實現類蒲障,而第一個就是我們配置常用的 RequestMappingHandlerMapping
。
接著往下看,通過RequestMappingHandlerMapping
獲取當前請求的處理程序執(zhí)行鏈也就是 HandlerExecutionChain
包含當前處理程序對象和任何處理程序攔截器瘫证。
我們可以清楚的看到,我們請求的URL路徑直接映射到了public java.lang.String com.web.controller.HelloWorldController.index()
這個方法揉阎。
2.org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
我們可以看到spring默認實現了 HandlerAdapter
的3個實現類,而第一個就是我們配置常用的RequestMappingHandlerAdapter
然后往下Debugger
這一步是判斷當前的HandlerMethod是否是期望實現的處理程序背捌。最后返回對應的適配處理器毙籽。
3.org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
使用給定的適配處理程序來處理此請求,返回ModelAndView
對象,這里viewName是 “index”,我們可以對應剛剛我們寫的HelloWorldController#index()
方法毡庆,確實方法返回的就是"index" 字符串坑赡。
4.org.springframework.web.servlet.DispatcherServlet#processDispatchResult
這個方法的目的就是 解析程序選擇處理或者處理程序調用的結果無論是ModelAndView或者Exception烙如。
最后通過render
方法來渲染視圖,我們在進入render
方法中看一下就可以查找到我們想要的org.springframework.web.servlet.view.JstlView
和對應返回的資源URL毅否。
至此亚铁,SpringMvc大致的執(zhí)行流程已經介紹完了。我們通過Debugger源碼再對應上面的交互流程圖螟加,就可以理解SpringMVC的架構和流程了徘溢。