SpringMVC原始碼深入解析
感悟
通過前面SpringAOP原始碼深度解析,SpringIOC原始碼深度解析加上本文的SpringMVC的原始碼閱讀,我從中收穫很多,學習了各種設計模式,各種抽象思想,以及各種底層原理,比如動態代理,反射等等,雖然前前前後後大概花了一個多月,但是我不後悔,並不覺得是浪費時間。
本文比較長,我花了三天的時間完成本文,但是還是水平有限,不可能面面俱到,當中也可能會有錯的,還請讀者指出,一起交流一起進步。
本文采用的原始碼版本是5.2.x,同樣,為了能收穫更多,還請讀者開啟Spring的原始碼工程進行跟進。
基礎知識
Servlet的基礎知識
為什麼要先了解Servlet的知識呢,因為後面你會看到我們所熟悉的SpringMVC其實也是一個Servlet,只是它封裝了很多的東西並和Spring進行了整合,後面我們進行的原始碼分析就是圍繞著Servlet的生命週期進行的,所以有必要了解一下Servlet相關的知識。
Servlet概念
全稱Java Servlet,是用Java編寫的伺服器端程式。其主要功能在於 互動式地瀏覽和修改資料,生成動態Web內容。Servlet執行於支援 Java應用的伺服器中。從原理上講,Servlet可以響應任何型別的請求, 但絕大多數情況下Servlet只用來擴充套件基於HTTP協議的Web伺服器。
Servlet的工作原理
下面通過一張時序圖來理解Servlet的工作流程
從上面的時序圖總結如下:
- 首先客戶端請求接入
- Servlet容器,比如Tomcat處理請求
- Tomcat 建立HttpServletRequest物件,將請求的資訊封裝到這個物件中。
- Tomcat 建立HttpServletResponse物件,此時是一個空的物件等待請求返回並填充
- Tomcat 呼叫service方法,最終傳遞到子類HttpServlet中
- HttpServlet從request中獲取請求資訊,處理完畢後將返回值資訊封裝到HttpServletResponse物件
- Tomcat容器返回處理後的資訊
Servlet生命週期
開啟Servlet原始碼發現Servlet介面有幾個方法:
- init()方法Tomcat建立Servlet時會呼叫
- 每次有請求來時Tomcat都會呼叫service()方法
- 請求結束時Tomcat會呼叫destroy()方法
public interface Servlet {
//Servlet建立時會呼叫init方法進行初始化
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
// 每次有新的請求來時都會呼叫
void service(ServletRequest var1,ServletResponse var2) throws ServletException,IOException;
String getServletInfo();
// 請求結束時呼叫
void destroy();
}
複製程式碼
一個Servlet例子
寫一個AddUserServlet類繼承自HttpServlet(為什麼要繼承這個類後面有說明)
public class AddUserServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
response.setContentType("text/plain;charset=utf8");
response.getWriter().write("新增成功");
}
protected void doPost(HttpServletRequest request,IOException {
doGet(request,response);
}
}
複製程式碼
webapp/WEB-INF下新建web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<description></description>
<display-name>AddUserServlet</display-name>
<servlet-name>AddUserServlet</servlet-name>
<servlet-class>com.sjc.springmvc.servlet.AddUserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddUserServlet</servlet-name>
<url-pattern>/AddUserServlet</url-pattern>
</servlet-mapping>
</web-app>
複製程式碼
將程式部署到Tomcat就可以訪問了(具體不會的請讀者查相關資料)
有同學可能會產生疑惑:我都沒有看到main方法,怎麼就可以訪問了呢?
回答如下:
Servlet實際上是tomcat容器生成的,呼叫init方法可以初始化。他有別於 普通java的執行過程,普通java需要main方法;但是web專案由於是伺服器控 制的建立和銷燬,所以servlet的訪問也需要tomcat來控制。通過tomcat訪問 servlet的機制是通過使用http協議的URL來訪問,所以servlet的配置完想要訪 問,必須通過URL來訪問,所以沒有main方法。
應用系統三層架構
表現層
也就是我們常說的web層
- 它負責接收客戶端請求,向客戶端響應結果,通常客戶端使用http協議請求web層,web需要接收http請求,完成http響應
- 表現層包括展示層和控制層:控制層負責接收請求,展示層負責結果的展示
- 表現層依賴業務層,接收到客戶端請求一般會呼叫業務層進行業務處理,並將處理結果響應給客戶端
- 表現層的設計一般都使用MVC模型
業務層
- 也就是我們常說的service層。
- 它負責業務邏輯處理,和我們開發專案的需求息息相關。web層依賴業務層,但是業務層不依賴web層。
- 業務層在業務處理時可能會依賴持久層,如果要對資料持久化需要保證事務一致性。
持久層
- 也就是我們常說的dao層。
- 負責資料持久化,包括資料層即資料庫和資料庫訪問層,資料庫是對資料進行持久化的載體,資料訪問層是業務層和持久層互動的介面,業務層需要通過資料訪問層將資料持久化到資料庫中
- 通俗的講,持久層就是和資料庫互動,對資料庫表進行增刪改的。
MVC設計模式
MVC是模型(model)、檢視(view)、控制器(controller)的縮寫,是一種用於設計編寫web應用程式表現層的模式
MVC設計模式的三大角色:
-
Model(模型):
模型包含業務模型和資料模型,資料模型用於封裝資料,業務模型用於處理業務。
-
View(檢視): 通常指的是我們的jsp或者html。作用一般就是展示資料的。
通常檢視是依據資料模型建立的。
-
Controller(控制器):
是應用程式中處理使用者互動的部分。作用一般就是處理程式邏輯的。
SpringMVC的流程分析
理解SpringMVC,只要你理解了下面介紹的六大元件基本上就可以了。後面的原始碼分析我們也是圍繞著這六大元件來的。
SpringMVC流程如下圖所示:
總結如下:
-
DispatcherServlet: 前端控制器
使用者請求到達前端控制器,它就相當於MVC中的C,DispatcherServlet是整個流程控制的中心,由它呼叫其他元件處理使用者的請求,DispatcherServlet的存在降低了元件之間的耦合性
-
Handler:處理器
Handler是繼DispatcherServlet前端控制器的後端控制器,在DispatcherServlet的控制下Handler對具體的使用者請求進行處理
由於Handler涉及到具體的使用者業務請求,所以一般情況下需要程式設計師根據業務需求開發Handler
常用的有比如Controller,HttpRequestHandler,Servlet、@RequestMapping等等,所以說處理器其實是一個寬泛的概念
-
View:檢視
SpringMVC框架提供了很多的view檢視型別的支援,包括:jstlview、freemarkerview、pdfview等。我們最常見的檢視就是jsp。當然,現在很少用jsp了,現在大部分都是前後端分離了,所以後面原始碼分析我們會忽略檢視
-
HandlerMapping: 處理器對映器
HandlerMapping負責根據使用者請求找到Handler即處理器,SpringMVC提供了不同的對映器實現不同的對映方式比如:配置檔案方式(BeanNameUrlHandlerMapping)、實現介面方式和註解方式(RequestMappingHandlerMapping)等。
-
HandlerAdapter: 處理介面卡
SpringMVC通過介面卡模式,將不關聯的DispatcherServlet和Handler進行關聯,通過介面卡呼叫具體的Handler實現。
-
View Resolver:檢視解析器
View Resolver負責將處理結果生成view檢視,View Resolver首先根據邏輯檢視解析成物理檢視名即具體的頁面地址,再生成view檢視物件,最後對view進行渲染,將處理結果通過頁面展示給使用者。
Spring原始碼解析
初始化init()
我們上面說過,分析原始碼的時候從Servlet入手,我們看它的初始化init()。首先看下類結構圖,我們發現DispatcherServlet這個核心元件就是一個Servlet,回到開頭我們說的SpringMVC其實也是一個Servlet,只是做的事情比較多而已。
我們順著這個類關係圖,找到了FrameworkServlet#initServletBean
這裡初始化了spring容器WebApplicationContext
@Override
protected final void initServletBean() throws ServletException {
//...省略若干程式碼
// 初始化web環境中的spring容器WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
//...省略若干程式碼
}
複製程式碼
我們進入到:FrameworkServlet#initWebApplicationContext
我們找到了兩個分支
-
configureAndRefreshWebApplicationContext
這個分支會去初始化Spring容器,又會回到我們SpringIOC容器初始化的那十二步驟,相關的可以閱讀我之前分析的深度解析SpringIOC
-
onRefresh
會重新整理容器的策略,我們主要看這一分支
protected WebApplicationContext initWebApplicationContext() {
//...省略若干程式碼
// 初始化spring容器
configureAndRefreshWebApplicationContext(cwac);
// 重新整理容器中的策略
onRefresh(wac);
//...省略若干程式碼
}
複製程式碼
我們根據onRefresh,發現最終會進入到DispatcherServlet#onRefresh
protected void onRefresh(ApplicationContext context) {
// 初始化策略容器
initStrategies(context);
}
複製程式碼
我們進入到DispatcherServlet#initStrategies
這裡會初始各種解析器,比如我們比較關心的處理器對映器,處理器介面卡,至於為啥要在這裡做初始化呢?SpringMVC為了擴充套件性,使用策略模式,將這些對映器介面卡交給了配置檔案,這樣如果要再新增一個處理器就不需要改程式碼了,符合“對修改關閉,對擴充套件開放”的設計原則,這裡的初始化也是為了策略模式做準備這個也是我們學習原始碼學習到的知識,以後可以運用到我們實際的專案中。
比如下面的xml配置檔案:
initStrategies就是從SpringIOC容器中獲取到這些Bean,然後放入Map中來進行初始化的。
<beans>
<!-- Handler處理器類的配置 -->
<!-- 通過bean標籤,建立beanname和bean的對映關係 -->
<bean name="/queryUser2"
class="com.sjc.springmvc.handler.QueryUserHandler"></bean>
<bean name="/addUser2"
class="com.sjc.springmvc.handler.AddUserHandler"></bean>
<!-- HandlerMapping配置 -->
<bean
class="com.sjc.springmvc.handlermapping.BeanNameUrlHandlerMapping"
init-method="init"></bean>
<!-- HandlerAdapter配置 -->
<bean
class="com.sjc.springmvc.handleradapter.HttpRequestHandlerAdapter"></bean>
</beans>
複製程式碼
讀者感興趣的話可以當做為一個分支進行驗證。
protected void initStrategies(ApplicationContext context) {
// 初始化多部件解析器
initMultipartResolver(context);
// 初始化國際化解析器
initLocaleResolver(context);
// 初始化主題解析器
initThemeResolver(context);
// 初始化處理器對映器
initHandlerMappings(context);
// 初始化處理器介面卡
initHandlerAdapters(context);
// 初始化異常解析器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
// 初始會檢視解析器
initViewResolvers(context);
initFlashMapManager(context);
}
複製程式碼
RequestMappingHandlerMapping初始化
在介紹HandlerMapping找到Handler的過程前,我們先來看看,RequestMappingHandlerMapping的初始化過程發生了什麼。我這裡先給個結論:
我們最終的目的就是通過url找到Handler(HandlerMethod)
我們進入到RequestMappingHandlerMapping,看到其中只有這樣的方法:
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
// 呼叫父類AbstractHandlerMethodMapping的afterPropertiesSet方法
super.afterPropertiesSet();
}
複製程式碼
我們眼前一亮,它為我們提供了研究RequestMappingHandlerMapping初始化的入口,為什麼這麼說呢?我們知道SpringIOC提供了兩種初始化方式: 第一種、就是在配置檔案中中指定init-method方法,這種在我前面分析SpringIOC的文章可以看到**深入解析SpringIOC; **第二種、就是子類實現InitializingBean介面。這個介面有一個方法:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
複製程式碼
這樣在初始化Bean的時候會呼叫afterPropertiesSet()方法。
這個流程我們在SpringIOC原始碼哪裡可以看見呢?
我們進入AbstractAutowireCapableBeanFactory#invokeInitMethods
protected void invokeInitMethods(String beanName,final Object bean,@Nullable RootBeanDefinition mbd)
throws Throwable {
//判斷該bean是否實現了實現了InitializingBean介面,如果實現了InitializingBean介面,則只掉呼叫bean的afterPropertiesSet方法
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
//直接呼叫afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
return null;
},getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//直接呼叫afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
//判斷是否指定了init-method方法,如果指定了init-method方法,則再呼叫制定的init-method
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
//進一步檢視該方法的原始碼,可以發現init-method方法中指定的方法是通過反射實現
invokeCustomInitMethod(beanName,bean,mbd);
}
}
}
複製程式碼
這兩種方式哪一種先呼叫呢?
看原始碼我們發現實現了InitializingBean介面的類在Bean進行初始化的時候先被呼叫,然後呼叫init-method指定的方法;
哪一種方式的效率高呢?
當然是實現了InitializingBean介面的類方式,因為呼叫init-method指定的方法是通過反射實現的;但是通過對映檔案方式消除了對spring的依賴
好了別跑遠了,我們接著看RequestMappingHandlerMapping#afterPropertiesSet
裡面會呼叫父類AbstractHandlerMethodMapping#afterPropertiesSet
public void afterPropertiesSet() {
// 初始化處理器方法物件
initHandlerMethods();
}
複製程式碼
接著進入:AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
// 獲取當前spring容器的所有bean的name,並遍歷
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 處理候選的Bean
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
複製程式碼
我們關心的是processCandidateBean
進入:AbstractHandlerMethodMapping#processCandidateBean
這裡面主要做三件事:
- 根據bean的名稱,從當前spring容器中獲取對應的Bean的Type
- 判斷是否是Handler,也就是是否有@Controller或者@RequestMapping修飾類,如果有則是Handler物件
- 查詢並封裝HandlerMethod物件
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 根據bean的名稱,從當前spring容器中獲取對應的Bean的Type
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type,probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'",ex);
}
}
// 如果是Handler,則需要查詢HandlerMethod(如果帶有@Controller或者@RequestMapping則是Handler物件)
if (beanType != null && isHandler(beanType)) {
// 重要入口
// 從Controller或者RequestMapping註解的Bean中,找到所有的HandlerMethod物件,並進行儲存
detectHandlerMethods(beanName);
}
}
複製程式碼
我們進入AbstractHandlerMethodMapping#detectHandlerMethods
這個方法比較複雜,主要用的lambda表示式太多,主要做這幾件事:
- 將類上的@RequestMapping資訊和Method上的Method資訊封裝成RequestMappingInfo物件
- 將Method方法和RequestMappingInfo物件建立對映,儲存Map集合中
- 遍歷methods,註冊url和RequestMappingInfo對映關係,註冊RequestMappingInfo和HandlerMethod的對映關係
protected void detectHandlerMethods(Object handler) {
// 獲取處理器型別
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 如果該類是通過cglib代理的代理類,則獲取其父型別別,否則的話,直接返回該類
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 儲存Method方法和RequestMapping註解資訊的對映關係(重點)
// 該對映關係會解析成我們需要的其他兩個對映關係
// key是Controller類中的Method物件,value是RequestMappingInfo物件
Map<Method,T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> { // 此處是設定回撥函式
try {
// 獲取bean上面和method上面的RequestMapping註解資訊,封裝到RequestMappingInfo物件中
return getMappingForMethod(method,userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method,ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType,methods));
}
methods.forEach((method,mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method,userType);
// 註冊HandlerMethod和RequestMappingInfo物件的關係
// 註冊請求URL和RequestMappingInfo物件的關係
registerHandlerMethod(handler,invocableMethod,mapping);
});
}
}
複製程式碼
RequestMappingHandlerMapping初始化分析到此結束,在深入下去就會沒完沒了。
處理請求service()
同樣順著下面的流程圖我們找到實現Servlet#service()方法的類HttpServlet
我們來到:HttpServlet#service:
可以看到這裡面將ServletRequest轉成HttpServletRequest,ServletResponse轉成HttpServletResponse,這樣就可以做更多的事情了。
@Override
public void service(ServletRequest req,ServletResponse res)
throws ServletException,IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request,response);
}
複製程式碼
進入到HttpServlet#service(request,response):
所有的Http請求都會經過這裡,但是我們找半天沒發現service實現程式碼在哪裡,我們看到HttpServlet是一個抽象類,一般類被設計成抽象類有兩個因素:
- 類中有抽象方法,需要子類實現
- 沒有抽象方法,但是類不希望被例項化
那這個HttpServlet為什麼要設計成抽象類呢?別急,我們看下類的註釋檔案:
翻譯起來大概的意思就是我這個類不知道你子類是什麼處理請求的,我不會幫你處理的,我這裡定義好了各種請求,請你務必實現其中的某一個,不然我就給你返回錯誤。我們看到這裡就是用了抽象模板方法的設計模式:父類把其他的邏輯處理完,把不確定的業務邏輯抽象成一個抽象方法,交給子類去實現。
/**
*
* Provides an abstract class to be subclassed to create
* an HTTP servlet suitable for a Web site. A subclass of
* <code>HttpServlet</code> must override at least
* one method,usually one of these:
*
* <ul>
* <li> <code>doGet</code>,if the servlet supports HTTP GET requests
* <li> <code>doPost</code>,for HTTP POST requests
* <li> <code>doPut</code>,for HTTP PUT requests
* <li> <code>doDelete</code>,for HTTP DELETE requests
* <li> <code>init</code> and <code>destroy</code>,* to manage resources that are held for the life of the servlet
* <li> <code>getServletInfo</code>,which the servlet uses to
* provide information about itself
* </ul>
*
/
複製程式碼
protected void service(HttpServletRequest req,HttpServletResponse resp)
throws ServletException,IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since,no reason
// to go through further expensive logic
doGet(req,resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later,call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp,lastModified);
doGet(req,resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp,lastModified);
doHead(req,resp);
} else if (method.equals(METHOD_POST)) {
doPost(req,resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req,resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req,resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested,anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg,errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,errMsg);
}
}
複製程式碼
我們順藤摸瓜,找到實現HttpServlet的子類,看看哪個子類實現了service()方法,我們最終看到了DispatcherServlet實現了這個service()方法。
這裡我們千呼萬喚的doDispatch終於出來了,這個doDispatch做了它擅長的事情,就是請求的分發,我們得慢慢品,細細品這個方法。
@Override
protected void doService(HttpServletRequest request,HttpServletResponse response) throws Exception {
//...省略掉無數程式碼
// 處理請求分發(做排程)
doDispatch(request,response);
}
複製程式碼
我們進入到:DispatcherServlet#doDispatch
我們再回顧一下SpringMVC的處理流程:
這個方法就是幹這件事情的,一張圖勝似千言萬語。你品,你細品
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 {
// 處理檔案上傳的request請求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 通過處理器對映器HandlerMapping,獲取handler處理器執行鏈,該執行鏈封裝了處理器和對應該處理器的攔截器(可能有多個)
// 需要注意的是@Controller註解的類,它不是我們這裡要查詢的處理器,我們要查詢的處理器是@RequestMapping對應的方法,這個方法會封裝到HandlerMethod類中
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest,response);
return;
}
// Determine handler adapter for the current request.
// 通過找到的handler處理器,去匹配合適的處理器介面卡HandlerAdapter
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 (new ServletWebRequest(request,response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 執行攔截器(interceptor)的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest,response)) {
return;
}
// Actually invoke the handler.
// 通過處理器介面卡,真正呼叫處理器方法
mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 設定預設檢視名稱
applyDefaultViewName(processedRequest,mv);
// 執行攔截器(interceptor)的postHandle方法
mappedHandler.applyPostHandle(processedRequest,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);
}
// 處理排程結果(也就是ModelAndView物件)
processDispatchResult(processedRequest,mappedHandler,mv,dispatchException);
}
catch (Exception ex) {
// 執行攔截器(interceptor)的afterCompletion方法
triggerAfterCompletion(processedRequest,ex);
}
catch (Throwable err) {
// 執行攔截器(interceptor)的afterCompletion方法
triggerAfterCompletion(processedRequest,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);
}
}
}
}
複製程式碼
SpringMVC策略模式
通過HandlerMapping找到Handler的過程
我們先來看下請求通過HandlerMapping找到Handler的過程:
我們進入到DispatcherServlet#getHandler
不出我們所料,這裡就遍歷了我們初始化階段儲存的handlerMappings集合,返回HandlerExecutionChain。這裡使用到了策略模式:
@Nullable
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;
}
複製程式碼
何為策略模式呢?我們一起來看:
我們假設有這樣的需求:我們出行方式有很多種,比如火車,飛機,自行車,我們只需要輸入我們有的錢就可以智慧地匹配出我們的出行方式
我們來建立這樣的模型:
定義一個策略類:
// 策略類
public interface TravelStrategy {
//出行方式
void travelWay();
boolean isDone(int type);
}
複製程式碼
定義飛機出行方式類:AirStrategy
public class AirStrategy implements TravelStrategy {
@Override
public void travelWay() {
System.out.println("坐飛機");
}
@Override
public boolean isDone(int type) {
if (type <= 1000 && type >500) {
return true;
}
return false;
}
}
複製程式碼
定義自行車出行方式類: BicycleStrategy
public class BicycleStrategy implements TravelStrategy {
@Override
public void travelWay() {
System.out.println("自行車");
}
@Override
public boolean isDone(int type) {
if (type <= 20) {
return true;
}
return false;
}
}
複製程式碼
定義火車出行方式類:TrainStrategy
public class TrainStrategy implements TravelStrategy {
@Override
public void travelWay() {
System.out.println("坐火車");
}
@Override
public boolean isDone(int type) {
if (type >= 20 && type < 400) {
return true;
}
return false;
}
}
複製程式碼
定義一個策略模式環境類(Context)
public class PersonContext {
// 策略類集合
private List<TravelStrategy> strategylist;
public PersonContext() {
this.strategylist = new ArrayList<>();
strategylist.add(new AirStrategy());
strategylist.add(new TrainStrategy());
strategylist.add(new BicycleStrategy());
}
public void travel(int type) {
// 輸入一個數,迴圈遍歷每個策略類,進行最優選擇
for (TravelStrategy travelStrategy : strategylist) {
if (travelStrategy.isOK(type)) {
travelStrategy.travelWay();
break;
}
}
}
}
複製程式碼
測試類:
public class StrategyTest {
@Test
public void test() {
// 策略環境類
PersonContext person = new PersonContext();
// 坐飛機
person.travel(1500);
// 坐火車
person.travel(100);
// 自行車
person.travel(1);
}
}
複製程式碼
輸出:
坐飛機
坐火車
自行車
複製程式碼
我們再來看SpringMVC中的策略模式,首先環境類DispatcherServlet,初始化各個策略模式的是DispatcherServlet#initStrategies,遍歷策略選擇最合適的策略的是:
@Nullable
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;
}
複製程式碼
策略模式消除了很多的if....else...程式碼,通過配置檔案方式定義各種策略,是一種可擴充套件的設計模式。
我們進入到AbstractHandlerMapping#getHandler
主要做兩件事:
- 通過請求獲取到處理器物件
- 通過處理器物件建立處理器執行鏈
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 呼叫具體的子類去獲取不同型別的處理器物件(比如獲取到的@Controller和@RequestMapping註解的處理器是HandlerMethod物件)
Object handler = getHandlerInternal(request);
//...省略若干程式碼
// 建立處理器執行鏈
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler,request);
//...省略若干程式碼
return executionChain;
}
複製程式碼
我們來到實現類AbstractHandlerMethodMapping#getHandlerInternal
我們看到這裡的handler是HandlerMethod,這個類封裝了controller和Method,
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 獲取查詢路徑(部分URL)
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH,lookupPath);
this.mappingRegistry.acquireReadLock();
try {
// 根據請求查詢路徑(部分URL),獲取最合適的HandlerMethod物件
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath,request);
// 對HandlerMethod物件包含的的bean屬性進行例項化,再返回HandlerMethod物件
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
複製程式碼
我們進入到AbstractHandlerMethodMapping#lookupHandlerMethod
這裡主要做:
-
根據請求路徑(URL)去上面我們分析到的urlLookup集合中獲取匹配到的RequestMappingInfo集合
-
將RequestMappingInfo物件和HandlerMethod物件進行匹配,將匹配到的資訊封裝到Match物件,再將Match物件放入matches集合進行返回。
此時我們的處理器HandlerMethod已經找到。
protected HandlerMethod lookupHandlerMethod(String lookupPath,HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 根據查詢路徑(URL)去urlLookup集合中獲取匹配到的RequestMappingInfo集合
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 將RequestMappingInfo物件和HandlerMethod物件進行匹配,將匹配到的資訊封裝到Match物件,再將Match物件放入matches集合
addMatchingMappings(directPathMatches,matches,request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(),request);
}
//...省略尋找最優匹配的過程程式碼
}
複製程式碼
SpringMVC介面卡模式
我們再回到DispatcherServlet#doService 方法中
找到Handler後按照我們上面的流程圖,接下來就是要找到HandlerAdapter。
我們看到:
protected void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception {
// 通過找到的handler處理器,去匹配合適的處理器介面卡HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
}
複製程式碼
我們進入到:DispatcherServlet#getHandlerAdapter
這裡無非就是根據我們初始化過程中,將配置檔案中的HandlerAdapters集合進行遍歷,找到合適的HandlerAdapter。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
// 通過介面卡的適配功能,去適配處理器,如果適配成功,則直接將介面卡返回
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
複製程式碼
這裡用到了介面卡模式,我們來看看HandlerAdapter類:
public interface HandlerAdapter {
// 判斷是否與當前的介面卡支援,如果支援返回true,不支援返回false
boolean supports(Object handler);
// 呼叫handler中的請求處理方法
@Nullable
ModelAndView handle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception;
long getLastModified(HttpServletRequest request,Object handler);
}
複製程式碼
SpringMVC這裡為啥需要介面卡呢?我們來分析一下,由於處理器handler有很多,比如Controller,Servlet等等,從HandlerMapping中獲取到的Handler是一個Object物件,這樣在DispatcherServlet#getHandlerAdapter方法中如果不用介面卡模式就可能這樣寫:
private HandlerAdapter getHandlerAdapter(Object handler) {
if (handler instanceof HttpRequestHandler) {
return new HttpRequestHandlerAdapter();
} else if(handler instanceof Controller) {
return new Controller
}
// else if.....
return null;
}
複製程式碼
如果再新增一個handler就得改程式碼,不符合“對修改關閉,對擴充套件開放”的設計原則。
SpringMVC這裡就給每個handler對應一個相應的介面卡,比如針對Controller實現的handler對應的是SimpleControllerHandlerAdapter介面卡,由SimpleControllerHandlerAdapter介面卡呼叫Controller處理器處理相關邏輯,每個handler的處理邏輯不一樣,以後再新增一個handler的時候,只用新增相應的介面卡就可以了,這完美的解決了這個問題。希望讀者能細品其中的設計思想。
我們繼續看DispatcherServlet中的其他方法:
找到HandlerAdapter後,主要做以下幾件事:
- 執行攔截器(interceptor)的preHandle方法
- 通過處理器介面卡,真正呼叫處理器方法
- 執行攔截器(interceptor)的postHandle方法
- 處理排程結果(也就是ModelAndView物件)
- 執行攔截器(interceptor)的afterCompletion方法
SpringMVC的攔截器功能原始碼就在此步驟完成,如果讀者對此感興趣可以以此為分支進行深入研究,至此SpringMVC原始碼分析告一段落。
protected void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception {
//...省略若干程式碼
// 通過找到的handler處理器,去匹配合適的處理器介面卡HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 執行攔截器(interceptor)的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest,mappedHandler.getHandler());
// 設定預設檢視名稱
applyDefaultViewName(processedRequest,mv);
// 處理排程結果(也就是ModelAndView物件)
processDispatchResult(processedRequest,dispatchException);
// 執行攔截器(interceptor)的afterCompletion方法
triggerAfterCompletion(processedRequest,ex);
}
複製程式碼