帶你一步一步手撕Spring MVC原始碼加手繪流程圖
Servlet 與 MVC
什麼是Spring MVC 其實應該說 什麼是 MVC ?
Model 資料,View 檢視,Controller 控制器。啪!三個東西合在一起,MVC就出來了。
這麼簡單? 沒錯,其實就是這麼簡單。
當然如果你對MVC不太熟悉的話還是乖乖往下看吧。
其實MVC就是處理Web請求的一種框架模式,我們思考一下使用者請求的流程:
- 輸入url
- 傳送請求
- 接受響應
對於使用者來說其實也就這三個步驟,但是對於服務端來說需要做很多,這裡我畫了一張圖供大家理解。這裡其實忽略了很多 Tomcat 本身已經為我們做的,而且 Tomcat 並不僅僅只有 Host,Context。
我來解釋一下,使用者傳送請求的 url 其實對應著很多東西。
比如說 localhost ,當然這個就是 ip 地址。這個 ip 地址對應著 Tomcat 裡面的 Host (站點) 層。
Context 代表著一個 web 應用,還記得當初寫 Servlet 專案的時候有一個 webapp 資料夾(裡面還有個WEB-INF,最裡面是web.xml)嗎?也可以理解為當初寫的 servlet 專案就是一個 web 應用,而使用者通過 ip 地址的埠對映去找到了這個應用。
這時候我們已經通過 ip 和埠尋找到了指定的 web 應用,我們知道一個 web 應用中存在多個 servlet ,而我們如何去尋找每個請求對應的 servlet 呢? 答案還是 url ,我們通過後面的 /news 去web.xml裡面尋找已經註冊到應用中的 Servlet 類。
具體我再配合圖中解釋一下: 找到了指定的 web 應用之後,通過請求的路徑 /news 去 web.xml 中尋找是否有對應的 標籤,其中這個標籤的子標籤 標籤的值需要匹配到請求的路徑,這個時候 標籤的值為 /news 正好匹配到了,所以我們獲取了上面的標籤的值然後再尋找是否有 標籤的子標籤 和這個值相等,如果有則獲取到底下的 對應的類 並通過這個類去解析請求
總結來說就是通過 url 從 web.xml 檔案中尋找到匹配的 servlet 的類
其實這就是原生的 servlet ,那麼 MVC 的影子在哪呢?
別急,你要記住的是 MVC 就是對 Servlet 的封裝,想要理解 MVC 就必須理解 Servlet 和 MVC 與 Servlet 的關係
SpringMVC中的DispatcherServlet
DispatcherServlet的繼承結構
有沒有發現這個 DispatcherServlet 其實就是一個 Servlet。也就是說 Spring MVC中最核心的部分其實就是一個 Servlet 。
我來簡單解釋一下相應的部分(先簡單瞭解一下)
- FrameworkServlet : 是 DispatcherServlet 的一個抽象父類。其中提供了載入某個對應的 web 應用程式環境的功能,還有將 GET、POST、DELETE、PUT等方法統一交給 DispatcherServlet 處理。
- Servlet : 一個規範,用來解決 HTTP伺服器和業務程式碼之間的耦合問題
- GenericServlet : 提升了 ServletConfig 的作用域,在init(servletConfig)方法中呼叫了init()無參方法,子類可以重寫這個無參初始化方法來做一些初始化自定義工作(後面分析原始碼中會講到)。
- HttpServletBean : 可以將 Servlet 配置資訊作為 Bean 的屬性 自動賦值給 Servlet 的屬性。
- DispatcherServlet :整個繼承鏈中的最後一個也是最重要的一個類,是SpringMVC 實現的核心類。MVC 通過在 web.xml 中配置 DispatcherServlet 來攔截所有請求,然後通過這個 DispatcherServlet 來進行請求的分發,並呼叫相應的處理器去處理請求和響應訊息。
有沒有想起來在 SSM 框架配置的時候在 web.xml 中的配置
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 把所以請求都交給DispatcherServlet處理-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!-- 攔截所有 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
複製程式碼
好的,現在我們知道了 springMVC 中使用了一個 DispatcherServlet 去處理所有請求,而我們知道真正處理的肯定不是 DispatcherServlet ,而是具體我們在 Controller 層中寫的帶有 @Controller @RequestMapping 註解的類和底下的方法。DispatcherServlet 只是一個為我們分發請求到具體處理器的一個分發Servlet。
那麼,這個 DispatcherServlet 具體怎麼工作的呢?它是如何分發請求的呢? 且聽我慢慢道來。
和 DispatcherServlet 一起工作的一些元件
首先我先將這些元件簡單化,並把一些不必要的先省略為了便於理解。
其實要分發請求 和 處理請求並相應,我們可以肯定的是 我們需要使用一個對映關係Mapping 來表示 url 和對應的 處理器,使用一個 處理器Handler 來處理對應的請求。這樣,我們就出來了兩個最根本的角色: HandlerMapping 和 Handler。
我們再來強調一下這兩者的工作。
- HandlerMapping : 建立請求和處理器的對映關係,即我們可以通過請求去獲取對應的 handler。
- Handler : 處理請求的處理器。
這樣,我們就可以再畫出一個簡單的流程圖了。
有沒有疑惑,這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的?
如果有,那麼就帶著這些問題往下看。
首先,這個 handlerMapping 的集合從哪來的?甭說集合,連單個你都不知道從哪來。 那麼我們就從原始碼中找答案吧。為了你省力,我直接告訴你,DispatcherServlet 中的 doDispatch 方法中進行了 分發的主要流程。
這裡我給出了簡化版的 doDispatch 方法
public void doDispatcher(HttpServletRequest req,HttpServletResponse resp) throws Exception {
// 通過request在處理器對映HandlerMapping中獲取相應處理器
Object handler = getHandler(req);
if (handler != null) {
... 呼叫handler的處理方法
}
}
複製程式碼
那麼這個 getHandler(request) 方法又是什麼樣的呢?這裡我直接放 DispatcherServlet 類的原始碼
@Nullable
// 這裡返回的是 HandlerExecutionChain
// 其實這是一個處理器和攔截器鏈的組合
// 現在你就理解為返回的是一個 handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍歷 handlerMapping 呼叫它的getHanlder獲取處理器
// 如果不為空直接返回
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
複製程式碼
我們繼續追蹤 HandlerMapping 的getHandler(request) 方法。
其實進入原始碼你會發現,HandlerMapping 是一個介面,故這裡給出一個簡單的 HandlerMapping 介面程式碼,如果有能力可以去看原始碼。
public interface HandlerMapping {
/**
* 獲取請求對應的處理器
* @param request 請求
* @return 處理器
* @throws Exception 異常
*/
Object getHandler(HttpServletRequest request) throws Exception;
}
複製程式碼
那麼,具體的實現類又是什麼呢?我們思考一下,這個mapping是一個請求和處理器的對映,它是如何存的?我們當初怎麼做的?
想必,你已經有答案了,在我們使用 SSM 框架的時候我們是通過 給類和方法 配置相應的註解(@Controller,@ReqeustMapping)來建立相應的 url 和 處理器方法的對映關係的。
我們再回來看原始碼 在idea中 可以使用 ctrl+alt+B 來檢視方法實現和類實現繼承。我們檢視 HandlerMapping 介面的 getHandler 方法的實現,我們會發現直接跳到了 AbstractHandlerMapping 這個抽象類的方法,我們檢視該方法的原始碼
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 獲取 handler 這其實是一個抽象方法
// 子類可以通過不同實現來獲取handler
// 例如通過 url 和 name的對映邏輯
// 通過 requestMapping 中的 url 和對應方法的對映邏輯
Object handler = getHandlerInternal(request);
if (handler == null) {
// 如果獲取為null 的獲取預設的處理器
// 這裡子類也可以設定自己的預設處理器
handler = getDefaultHandler();
}
// 如果還是沒有則返回 這時候 DispatcherServlet會返回 404
if (handler == null) {
return null;
}
// Bean name or resolved handler?
// 如果返回的處理器是字串 則認為它是一個beanName
if (handler instanceof String) {
String handlerName = (String) handler;
// 通過beanName從IOC容器中獲取相應的處理器
handler = obtainApplicationContext().getBean(handlerName);
}
// 下面是將處理器 和 攔截器封裝成處理器執行鏈
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler,request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler,request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request,executionChain,config);
}
// 返回處理器執行鏈
return executionChain;
}
複製程式碼
如果其他的你看不懂,你只要理解我註釋區域的程式碼就行了。我們從上面得到最重要的資訊就是:真正的handler獲取是在子類實現的getHandlerInternal(request)中,那我們來看一下有哪些子類。
我們可以看到其中有 AbstractHandlerMethodMapping、AbstractUrlHandlerMapping、WelcomeHandlerMapping。
我們主要關注 AbstractHandlerMethodMapping (提供方法處理器) 和 AbstractUrlHandlerMapping(提供url對應處理器對映),這裡為了不耽誤時間,我們直接分析 AbstractHandlerMethodMapping ,它是註解方法的對映的一個抽象類。
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 獲取請求的路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
// 通過 lookupPath 來從中獲取 HandlerMethod
// 這個HandlerMethod 又是什麼?
// 先不用管 我們繼續看lookupHandlerMethod原始碼
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath,request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
// 這裡的邏輯稍微有些複雜
// 你只要知道它通過請求來匹配返回處理器方法
// 如果有多個處理器方法可以處理當前Http請求 那麼返回最佳匹配的處理器
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath,HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches,matches,request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(),request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch,secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + "," + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE,bestMatch.handlerMethod);
handleMatch(bestMatch.mapping,lookupPath,request);
// 返回最佳匹配
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(),request);
}
}
複製程式碼
到現在邏輯慢慢變得複雜起來,我們做一個小結:在 DispatcherServlet 中我們通過遍歷 handlerMapping 集合並呼叫它的 getHandler 方法來獲取handler ,這個handler 是一個 Object (因為spring會整合其他框架的處理器,並使用這些處理器處理請求 所以這裡選擇Object)。而 HandlerMapping 僅僅是一個介面 為了方便 抽象類 AbstractHandlerMapping 實現了這個方法並且為子類提供了自定義獲取handler的 getHandlerInternal(request) 方法。 對於我們通用方式註解來標識控制器方法和url請求路徑的對映是通過 AbstractHandlerMethodMapping 來獲取請求對應的 HandlerMethod 的。
那麼,疑問又來了,HandlerMethod是什麼?
還記得剛剛上面的問題麼,這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的?
我們現在可以來回答一下Handler的類結構了,Handler是一個Object,為了第三方框架的處理器能夠接入來處理請求,spring使用了Object,而對於註解形式來說 一個處理器是一個 HandlerMethod。這裡我給出 HandlerMethod 的簡單實現形式,如果有能力可以檢視原始碼。
@Data
public class HandlerMethod {
// bean 其實這個是標識 Controller 註解的類的物件
private Object bean;
// 該物件的型別
private Class<?> beanType;
// 該類上被標識RequestMapping註解的方法
private Method method;
}
複製程式碼
在 HandlerMethod 中存放了控制器類和對應的方法。為什麼要存放他們?你想一下,我們用@RequestMapping註解標識的方法不就是處理方法嗎,HandlerMethod 中存放他們,到時候呼叫處理方法只需要通過反射呼叫這個bean的method就行了。如果不理解可以看一下我下面寫的程式碼。
// 這裡先不用管 ModelAndView
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {
ModelAndView modelAndView = null;
HandlerMethod handlerMethod = ((HandlerMethod) handler);
// 獲取HandlerMethod的method
Method method = handlerMethod.getMethod();
if (method != null) {
// 通過反射呼叫方法並返回檢視物件(這就是處理方法)
modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(handlerMethod.getBeanType()));
}
return modelAndView;
}
複製程式碼
再來看看上面的問題。這個 HandlerMapping 集合從哪來?HandlerMapping 的類結構是啥樣的?Handler的類結構又是什麼樣的
第三個問題解決了,第二個問題上面也解決了,那麼第一個問題來了。
我們從一開始就只討論瞭如何在HandlerMapping中取出handler 並且呼叫handler的處理方法,那麼我們一開始遍歷的這個handlerMappings集合到底從哪兒來,或者說它是什麼時候被初始化的?
這個時候,我們又得回到根源。我再來放這張圖,不知道你們是否還記得
你能想到什麼呢?我這裡假設你對servlet還是有一些瞭解的。
我們知道 DispatcherServlet 是一個 servlet 。一個 servlet 肯定有init()方法 (還記得我上面講的GenericServlet的作用嗎?現在來了,如果不是很懂init(),建議去了解一下servlet的生命週期)。
我們可以大膽的猜測,對 handlerMappings 的初始化就是在 servlet 的初始化方法中進行的。
很遺憾我們沒有能在 DispatcherServlet 中找到 init 方法,那麼就找他爹,找不到再找他爺爺,曾爺爺。我們知道因為 DispatcherServlet 繼承了 GenericServlet 所以我們需要找到 實現的 init() 無參方法。所以我們找到了 HttpServletBean 中重寫的 init() 方法了
@Override
// 這傢伙還不允許被重寫 final
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 將servlet配置資訊存入bean的屬性中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class,new ResourceEditor(resourceLoader,getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs,true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'",ex);
}
throw ex;
}
}
// 重點在這裡 這裡子類才可以自由發揮
// 該方法不能被重寫是因為 上面的步驟是必須的
// 別忘了上面的步驟是 HttpServletBean 的職責
// 接下去繼續看
// Let subclasses do whatever initialization they like.
initServletBean();
}
// 進入FrameworkServlet 檢視實現的initServletBean方法
@Override
protected final void initServletBean() throws ServletException {
// log不用管
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
// 重點來了
try {
// 初始化容器和上下文
// 我們要記得現在在 FrameworkServlet中執行呢
// 我們進入initWebApplicationContext方法
this.webApplicationContext = initWebApplicationContext();
// 初始化FrameworkServlet 這裡沒給實現 子類也沒給
// 所以不用管
initFrameworkServlet();
}
// log不用管
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed",ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
// 初始化容器和上下文
protected WebApplicationContext initWebApplicationContext() {
// 查詢是否有專門的根環境 先不用管
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果不存在專用根環境 通常我們不會走到這 先不用管
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context,setting the application context id,etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 如果為空
if (wac == null) {
// 檢視是否在servlet中已經註冊
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists,it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// 自己建立一個
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// 判斷這個環境是否支援重新整理 如果不支援 下面手動重新整理
// 如果支援則前面已經重新整理了
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// !!!!!!!!!!!!!!!!!!!!!重點
// DispacherServlet 就是在這裡實現的
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName,wac);
}
return wac;
}
// DispatcherServlet 重寫了該方法
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 一系列的初始化工作
protected void initStrategies(ApplicationContext context) {
// 前面一些不用管
initMultipartResolver(context);
// 地域
initLocaleResolver(context);
// 主題
initThemeResolver(context);
// 重點來了!!!!
// 初始化HandlerMapping
initHandlerMappings(context);
// 初始化介面卡
initHandlerAdapters(context);
// 初始化異常處理
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
複製程式碼
我們先暫停一下,理一下思路。
在 HttpServletBean 中重寫了 GenericServlet 的 init() 無參方法開始初始化動作,其中HttpServletBean中先實現了 servlet 配置資訊到 bean 屬性資訊的賦值,然後呼叫 initServletBean() 該方法是子類進行自定義初始化的方法。FrameworkServlet 實現了該方法並且呼叫了 initWebApplicationContext() 方法進行了容器和上下文的初始化工作,並且其中呼叫了 onRefresh(ApplicationContext context) 方法。 這裡FrameworkServlet沒有做任何操作而是子類 DispatcherServlet 在其中呼叫了 initStrategies(context) 進行初始化工作。
好了我們繼續看初始化 handlerMappings方法。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext,including ancestor contexts.
// 在應用上下文中尋找 handlerMappings
Map<String,HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,HandlerMapping.class,true,false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore,we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping,by registering
// a default HandlerMapping if no other mappings are found.
// 前面不用管 其實一般我們使用預設的
if (this.handlerMappings == null) {
// 這裡是獲取預設的handlerMappings
this.handlerMappings = getDefaultStrategies(context,HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context,Class<T> strategyInterface) {
String key = strategyInterface.getName();
// 獲取defaultStrategies的內容
String value = defaultStrategies.getProperty(key);
if (value != null) {
// 解析相應內容並初始化 handlerMappings
// 獲取內容中的類名陣列
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
//通過反射建立並加入陣列中取返回給上面
Class<?> clazz = ClassUtils.forName(className,DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context,clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]",ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]",err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
複製程式碼
事情馬上明瞭了,我們現在已經知道了 handlerMapping 是怎麼加入佇列中了(獲取到 defaultStrategies 的資源內容 遍歷內容獲取類名 並通過反射建立物件加入佇列),所以我們可以大膽猜測 defaultStrategies 中藏著祕密,它肯定已經定義好了預設的 handlerMapping 的類名。
果不其然,我們來看程式碼
private static final Properties defaultStrategies;
// 在靜態塊中已經載入了defaultStrategies
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// 通過資源初始化defaultStrategies
// 這裡的資源路徑 很重要!!!!
ClassPathResource resource = new
ClassPathResource(DEFAULT_STRATEGIES_PATH,DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
// 在這裡呀 DispatcherServlet.properties
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
複製程式碼
我們去尋找一下 DispatcherServlet.properties 這個檔案, 原來都給我們定義好了,我們可以看見預設的handlerMapping有兩個。 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping。
好了,我們現在終於可以總結一下了。
在我定義的簡單的 DispatcherServlet 的 “同事”中,主要有 HandlerMapping 和 Handler。HandlerMapping 會在 DispatcherServlet 初始化的時候被在載入 ,然後在 DispatcherServlet 呼叫到執行方法 doDispatch() 的時候,會遍歷 handlerMappings 集合獲取對應的 handler。handler 是一個 Object(因為需要適配其他框架的處理器),在註解方式中是一個 HandlerMethod (裡面存了Controller類的例項和method,在處理方法的時候使用反射呼叫該例項的method方法)。獲取完 handler之後通過 處理器的處理方法返回一個檢視物件並渲染頁面。
再來一個元件 Adapter
其實對於“正宗”的MVC流程中,在遍歷 handlerMappings 獲取到相應的 handler 之後,其實並不是直接通過 handler 來執行處理方法的,而是通過 HandlerAdapter 來執行處理方法的。
這裡我寫了一個簡單的介面卡介面,原始碼也不復雜 你可以直接看原始碼
public interface HandlerAdapter {
ModelAndView handleRequest(HttpServletRequest request,Object handler) throws Exception;
boolean support(Object handler);
}
複製程式碼
handleRequest 不必說,用來執行處理的,裡面傳進去一個 handler 肯定最終呼叫的 handler的執行方法。這是典型的介面卡模式。
support 判斷該handler是否被該介面卡支援。
其實理解上面的過程了之後再加入一個介面卡就不難了,我們主要思考一下 為什麼要加入介面卡,我們知道 handler 是一個 Object 它的處理方法是不固定的,如果我們要在 DispatcherServlet 中通過 Handler 執行處理方法,那麼就要做很多型別判斷,這對於 DispatcherServlet 是非常難受的,所以需要通過介面卡擴充套件。
這樣我們可以寫出一個簡單的 doDispatch 方法了,有能力的可以檢視原始碼
public void doDispatcher(HttpServletRequest req,HttpServletResponse resp) throws Exception {
// 通過request在處理器對映HandlerMapping中獲取相應處理器
Object handler = getHandler(req);
if (handler != null) {
// 通過handler獲取對應的介面卡
HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
if (handlerAdapter != null) {
// 通過介面卡呼叫處理器的處理方法並返回ModelAndView檢視物件
ModelAndView modelAndView = handlerAdapter.handleRequest(req,resp,handler);
... 處理檢視並渲染
}
}
}
複製程式碼
檢視解析
我們知道在 doDispatch 方法呼叫完 HandlerAdapter 的處理方法後統一返回的是一個 ModelAndView 物件,那麼這個 ModelAndView 物件是什麼呢?
字面意思,模型和檢視。也就是 MVC 的 Model 和 View。在 SpringMVC 中 ModelAndView 是給 框架本身支援的網頁生成器使用的,它是用來連線後臺和網頁的類,而如今在前後端分離的趨勢下,基本不怎麼使用了,這裡我只簡單提一下。
我們知道在 HandlerAdapter 呼叫處理方法之後會返回一個檢視物件 ModelAndView ,而在這之後,doDispatch方法會呼叫 processDispatchResult(processedRequest,response,mappedHandler,mv,dispatchException) 去處理檢視資訊,這個方法又會呼叫一個 render() 方法進行真正的檢視渲染。
非常有用的RequestResponseBodyMethodProcessor
還記不記得 @RequestBody @ResponseBody @RestController 這些註解。沒錯,現在我們大部分都用它們,那麼它們是如何工作的呢?
奧祕要從 doDispatch() 方法中的
mv = ha.handle(processedRequest,mappedHandler.getHandler());
複製程式碼
這條語句開始。 這個語句就是呼叫 相應的介面卡的 handle 方法並返回 ModelAndView 物件。
當然通過前面的學習,我們知道最終呼叫到的是 RequestMappingHandlerAdapter 類的 handleInternal方法。
// 檢視這個方法的原始碼 你會發現 除了處理一些 session 的問題
// 最終都會呼叫 處理器方法 invokeHandlerMethod
@Override
protected ModelAndView handleInternal(HttpServletRequest request,HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
// 如果配置了 session 同步
if (this.synchronizeOnSession) {
// 則獲取 session
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request,handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request,handlerMethod);
}
}
else {
// 如果沒有配置 session 內同步 或者還沒有建立 session 直接呼叫處理器方法
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request,handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response,this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
複製程式碼
我們來看一下 invokeHandlerMethod 中幹了什麼事, 看上去好密密麻麻,其實我們只要關注重點就行了,關注我註釋的地方。
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HandlerMethod handlerMethod) throws Exception {
// 構造 Web 請求 其實就是一個代理類 封裝了 請求和響應
ServletWebRequest webRequest = new ServletWebRequest(request,response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod,binderFactory);
// 重點來了!!!!!
// 將handlerMethod 封裝成 ServletInvocableHandlerMethod類
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 為invocableMethod 做一些配置
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest,mavContainer,invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request,response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger,traceOn -> {
String formatted = LogFormatUtils.formatValue(result,!traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 重點來了!!!!! 呼叫 ServletInvocableHandlerMethod 的 invokeAndHandle 方法
invocableMethod.invokeAndHandle(webRequest,mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer,modelFactory,webRequest);
}
finally {
webRequest.requestCompleted();
}
}
複製程式碼
總結一下上面的方法就是:將 HandlerMethod 物件封裝成 ServletInvocableHandlerMethod 然後做一些配置並呼叫它的 invokeAndHandle 方法
public void invokeAndHandle(ServletWebRequest webRequest,ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 這一步很重要 執行請求並獲取返回值
// 這裡裡面就涉及到了 RequestBody 註解了
Object returnValue = invokeForRequest(webRequest,providedArgs);
setResponseStatus(webRequest);
// 處理返回值
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null,"No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue,getReturnValueType(returnValue),webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue),ex);
}
throw ex;
}
}
複製程式碼
我們首先來解析一下 Object returnValue = invokeForRequest(webRequest,providedArgs) 方法 ,這裡涉及到了 @RequestBody 註解的使用。
@Nullable
public Object invokeForRequest(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 方法引數的解析 這裡可以做很多關於 引數和請求 的事情
// 這是我們需要深入檢視原始碼的
Object[] args = getMethodArgumentValues(request,providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 返回呼叫結果 很簡單 就是通過反射呼叫方法 這裡不做贅述
return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 很簡單 獲取到引數
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 遍歷引數
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter,providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter,"No suitable resolver"));
}
try {
// 重點來了!!!!
// 將請求中的資訊通過引數解析器解析到對應的引數
// 最終遍歷完之後將引數陣列返回
args[i] = this.resolvers.resolveArgument(parameter,request,this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later,exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter,exMsg));
}
}
throw ex;
}
}
return args;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter,NativeWebRequest webRequest,@Nullable WebDataBinderFactory binderFactory) throws Exception {
// 獲取對應的引數解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 通過解析器解析引數 重點就在這了 因為 @RequestBody註解的存在
// 我們會呼叫到 RequestResponseBodyProcessor 類的這個方法
return resolver.resolveArgument(parameter,webRequest,binderFactory);
}
@Override
public Object resolveArgument(MethodParameter parameter,@Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 主要這裡面通過 MessageConverters 訊息轉換器 來實現了 @RequestBody 的功能
// 由於篇幅有限 這裡不再深入分析 如果想找到答案 順著往下檢視原始碼就行
Object arg = readWithMessageConverters(webRequest,parameter,parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest,arg,name);
if (arg != null) {
validateIfApplicable(binder,parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder,parameter)) {
throw new MethodArgumentNotValidException(parameter,binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name,binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg,parameter);
}
複製程式碼
下面我還放了一下 readWithMessageConverters 方法的程式碼,其實裡面主要就是遍歷訊息轉換器,然後通過轉換器執行HTTP報文到引數的轉換。
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType,contextClass,contentType) :
(targetClass != null && converter.canRead(targetClass,contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message,targetType,converterType);
body = (genericConverter != null ? genericConverter.read(targetType,msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass,msgToUse));
body = getAdvice().afterBodyRead(body,msgToUse,converterType);
}
else {
body = getAdvice().handleEmptyBody(null,message,converterType);
}
break;
}
}
複製程式碼
知道了 @RequestBody 註解的原理,@ResponseBody 註解的原理也馬上浮出水面了。答案就在 ServletInvocableHandlerMethod 類中的 invokeAndHandle 方法獲取了 returnValue 之後的步驟
// 答案就在這裡
try {
this.returnValueHandlers.handleReturnValue(
returnValue,webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue,MethodParameter returnType,NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue,returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue,returnType,webRequest);
}
複製程式碼
上面兩個方法你可以追蹤原始碼,其實最終呼叫的還是在 RequestResponseBodyMethodProcessor 這個類中。
@Override
public void handleReturnValue(@Nullable Object returnValue,NativeWebRequest webRequest)
throws IOException,HttpMediaTypeNotAcceptableException,HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
// 封裝web請求
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 通過訊息解析器解析返回的value
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue,inputMessage,outputMessage);
}
// 這裡我貼出了writeWithMessageConverters方法的主要程式碼 因為這個方法有點長。。
// 這裡遍歷 Http訊息轉換器集合
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType,valueType,selectedMediaType) :
converter.canWrite(valueType,selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body,selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger,traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody,!traceOn) + "]");
addContentDispositionHeader(inputMessage,outputMessage);
// 通過轉換器來輸出 重點。。。
if (genericConverter != null) {
genericConverter.write(body,outputMessage);
}
else {
((HttpMessageConverter) converter).write(body,outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
複製程式碼
我們來總結一下: 在呼叫 HandlerAdapter 的 處理方法的時候 會跳轉呼叫到 RequestMappingHandlerAdapter 的 handleInternal 方法。這裡面會將 原本的處理器 HandlerMethod 封裝成 ServletInvocableHandlerMethod,然後會呼叫這個類中的 invokeAndHandle 方法,這個方法中主要進行了相應方法處理器的方法的呼叫,在呼叫之前,會將Http報文中的內容轉換為對應的引數內容。在呼叫完成返回 returnValue 之後,會呼叫相應 HttpMessageConvert 的轉換方法 然後返回。
最終變成什麼樣了呢?
現在,你理解 Spring MVC了麼?