SpringMVC原始碼學習:容器初始化+MVC初始化+請求分發處理+引數解析+返回值解析+檢視解析
阿新 • • 發佈:2020-05-09
[toc]
# 一、前言
> 版本:
>
> springMVC 5.0.2RELEASE
>
> JDK1.8
前端控制器的配置:
web.xml
```xml
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
dispatcherServlet
/
```
springmvc.xml配置
```xml
```
# 二、初始化
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509113735172.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
DispatcherServlet的啟動與Servlet的啟動過程緊密聯絡,我們通過以上繼承圖就可以發現。
## 1. 容器初始化
Servlet中定義的init()方法就是其生命週期的初始化方法,接著往下走,GenericServlet並沒有給出具體實現,在HttpServletBean中的init()方法給出了具體的實現:
`HttpServletBean.init()`方法(忽略日誌)
```java
@Override
public final void init() throws ServletException {
//根據初始化引數設定bean屬性(我們設定了contextConfigLocation,故可以獲取)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//包裝DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//獲取資源載入器,用以載入springMVC的配置檔案
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//註冊一個ResourceEditor
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//該方法為空實現,可以重寫,初始化BeanWrapper
initBeanWrapper(bw);
//最終將init-param讀取的值spirng-mvc.xml存入contextConfigLocation中
bw.setPropertyValues(pvs, true);
}
}
// 讓子類實現初始化
initServletBean();
}
```
那就來看看`FrameworfServlet.initServletBean()`幹了啥(基本都是日誌記錄,還有計時,省略了這些部分):
```java
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
//WebApplicationContext的初始化
this.webApplicationContext = initWebApplicationContext();
//也是空實現,允許子類自定義
initFrameworkServlet();
}
```
所以重頭戲就在initWebApplicationContext方法上,我們可以先來看看執行後的效果:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509113912399.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
可以看到springMVC九大元件被賦值,除此之外webApplicationContext也已被賦值。
我們再來看看原始碼,看看其內部具體實現:`FrameworkServlet.initWebApplicationContext()`
```java
protected WebApplicationContext initWebApplicationContext() {
//根容器查詢
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//在構建時注入了DispatcherServlet並且webApplicationContext已經存在-> 直接使用
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//如果context還沒有refresh-->進行設定父級context以及application context的id等等操作
if (cwac.getParent() == null) {
//在沒有顯式父級的情況下注入了context例項-> 將根應用程式上下文設定為父級
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//在構造時未注入任何上下文例項-->從ServletContext中查詢
wac = findWebApplicationContext();
}
if (wac == null) {
// ServletContext中沒有-->就建立一個被本地的
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//如果context不支援refresh或者在初始化的時候已經refresh-->就手動觸發onfresh
onRefresh(wac);
}
//把當前建立的上下文存入ServletContext中,使用的屬性名和當前Servlet名相關
if (this.publishContext) {
// 將上下文釋出為servlet上下文屬性
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
```
### 根容器查詢的方法
```java
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
```
`WebApplicationContextUtils.getWebApplicationContext`
```java
//SpringMVC支援Spring容器與Web容易同時存在,並且Spring容器視作根容器,通常由ContextLoaderListener進行載入。
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
//根據ServletName.ROOT為鍵查詢值
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
return (WebApplicationContext) attr;
}
```
Spring容器和Web容器如果同時存在,需要使用ContextLoaderListener載入Spring的配置,且它會以key為
`WebApplicationContext.class.getName() + ".ROOT`存到ServletContext中。
### 容器建立的方法
構建的時候沒有任何Context例項注入,且ServletContext中也沒有找到WebApplicationContext,此時就會建立一個local Context,這個方法允許顯式傳入父級容器作為引數。
```java
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//預設:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;可以在初始化引數中指定contextClass
Class> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//獲取ConfigurableWebApplicationContext物件
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
```
我們可以發現:在這個過程中,Web容器的IoC容器被建立,也就是XmlWebApplicationContext,,從而在web容器中建立起整個spring應用。
`configureAndRefreshWebApplicationContext(wac);`
```java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//省略給ConfigurableWebApplicationContext物件設定一些值...
//每次context refresh,都會呼叫initPropertySources
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//初始化webApplication容器,重啟
wac.refresh();
}
```
### 載入配置檔案資訊
其實也就是refresh()這個關鍵方法,之前瞭解過spring容器的初始化的過程,對這一步應該相當熟悉,還是分為三步:
- BeanDefinition的Resource的定位,我們這定位到了classpath:springmvc.xml。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114015533.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
- beanDefinition的載入過程,springMVC做了一些改變,比如定義了針對mvc的名稱空間解析MvcNamespaceHandler。
![](https://img-blog.csdnimg.cn/20200509114052620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
- 接著是beanDefinition在IoC中的註冊,也就是把beanName:beanDefinition以鍵值對的形式存入beandefinitionMap中。
## 2. MVC的初始化
MVC的初始化在DispatcherServlet的initStratefies方法中執行,通過方法名,我們就可以得出結論,就是在這進行了對九大元件的初始化,其實基本上都是從IoC容器中獲取物件:
```java
protected void initStrategies(ApplicationContext context) {
//檔案上傳解析器
initMultipartResolver(context);
//區域資訊解析器,與國際化相關
initLocaleResolver(context);
//主題解析器
initThemeResolver(context);
//handler對映資訊解析
initHandlerMappings(context);
//handler的介面卡
initHandlerAdapters(context);
//handler異常解析器
initHandlerExceptionResolvers(context);
//檢視名轉換器
initRequestToViewNameTranslator(context);
//檢視解析器
initViewResolvers(context);
//flashMap管理器
initFlashMapManager(context);
}
```
### 檔案上傳解析器
```java
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 預設是沒有配置multipartResolver的.
this.multipartResolver = null;
}
}
```
配置檔案上傳解析器也很簡單,只需要在容器中註冊MultipartResolver即可開啟檔案上傳功能。
### 區域資訊解析器
```java
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 使用預設策略,利用反射建立物件
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
}
}
```
`org.springframework.web.servlet.DispatcherServlet`同級目錄下的`DispatcherServlet.properties`檔案中規定了幾大元件初始化的預設策略。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114118673.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
### handler對映資訊解析
handlerMappings存在的意義在於為HTTP請求找到對應的控制器Controller。
```java
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//從所有的IoC容器中匯入HandlerMappings,包括其雙親上下文
if (this.detectAllHandlerMappings) {
Map 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.
}
}
//保證至少有一個handlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
```
接下來幾個操作都差不多,就不贅述了。
總的來說,MVC初始化的過程建立在IoC容器初始化之後,畢竟要從容器中取出這些元件物件。
## 3. HandlerMapping的實現原理
### HandlerExecutionChain
HandlerMapping在SpringMVC扮演著相當重要的角色,我們說,它可以為HTTP請求找到 對應的Controller控制器,於是,我們來好好研究一下,這裡面到底藏著什麼玩意。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114140116.png)
HandlerMapping是一個介面,其中包含一個getHandler方法,能夠通過該方法獲得與HTTP請求對應的handlerExecutionChain,而這個handlerExecutionChain物件中持有handler和interceptorList,以及和設定攔截器相關的方法。可以判斷是同通過這些配置的攔截器對handler物件提供的功能進行了一波增強。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114200838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
### RequestMappingHandlerMapping
我們以其中一個HandlerMapping作為例子解析一下,我們關注一下:
```java
protected void initHandlerMethods() {
//獲取所有上下文中的beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class> beanType = null;
//得到對應beanName的Class
beanType = obtainApplicationContext().getType(beanName);
//判斷是否為控制器類
if (beanType != null && isHandler(beanType)) {
//對控制器中的方法進行處理
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
```
isHandler方法:判斷該類是否存在@Controller註解或者@RequestMapping註解
```java
@Override
protected boolean isHandler(Class> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
```
detectHandlerMethods方法:
```java
protected void detectHandlerMethods(final Object handler) {
//獲取到控制器的型別
Class> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//對型別再次進行處理,主要是針對cglib
final Class> userType = ClassUtils.getUserClass(handlerType);
//遍歷方法,對註解中的資訊進行處理,得到RequestMappingInfo物件,得到methods陣列
Map methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup) method -> {
return getMappingForMethod(method, userType);
});
//遍歷methods[Method,{path}]
for (Map.Entry entry : methods.entrySet()) {
//對方法的可訪問性進行校驗,如private,static,SpringProxy
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
//獲取最終請求路徑
T mapping = entry.getValue();
//註冊
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
```
mapping物件的屬性:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114241660.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
methods物件中儲存的元素:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114253549.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
註冊方法在AbstractHandlerMethodMapping中實現:
```java
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
//處理方法的物件
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//判斷對映的唯一性
assertUniqueMethodMapping(handlerMethod, mapping);
//將mapping資訊和控制器方法對應
this.mappingLookup.put(mapping, handlerMethod);
//將path與處理器對映(一個方法可能可以處理多個url)
List directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
//控制器名的大寫英文縮寫#方法名
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
//跨域請求相關配置
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
//將所有配置統一註冊到registry中
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
```
至此,所有的Controller,以及其中標註了@RequestMapping註解的方法,都被一一解析,註冊進HashMap中,於是,對應請求路徑與處理方法就一一匹配,此時HandlerMapping也初始化完成。
# 三、請求響應處理
## 1. 請求分發
我們需要明確的一個點是,請求過來的時候,最先執行的地方在哪,是Servlet的service方法,我們只需要看看該方法在子類中的一個實現即可:
FrameworkServlet重寫的service方法:
```java
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//獲取請求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//攔截PATCH請求
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
```
其實最後都是呼叫了processRequest方法,該方法中又呼叫了真正的doService()方法,其中細節先不探討,我們直奔,看看DispatcherServlet的這個doService幹了哪些事情(DispatcherServlet這個類確實是核心中的核心,既建立了IoC容器,又負責請求分發):
```java
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//忽略一大串前期準備,使其能夠處理view 物件
//接著進入真正的分發
doDispatch(request, response);
}
```
doService:
```java
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);
//為當前的request請求尋找合適的handler
mappedHandler = getHandler(processedRequest);
//如果沒有handler可以處理該請求,就跳轉到錯誤頁面
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//為當前的request請求尋找合適的adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
//判斷是否支援getLastModified,如果不支援,返回-1
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//執行註冊攔截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正處理請求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果mv!=null&&mv物件沒有View,則為mv物件設定一個預設的ViewName
applyDefaultViewName(processedRequest, mv);
//執行註冊攔截器的applyPostHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
//進行檢視解析和渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
```
需要注意的是,mappedHandler和HandlerAdapter都是從對應的集合中遍歷查詢,一旦找到可以執行的目標,就會停止查詢,我們也可以人為定義優先順序,決定他們之間的次序。
## 2. 請求處理
RequestMappingHandlerAdapter的handleInternal方法,含有真正處理請求的邏輯。
```java
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//定義返回值變數
ModelAndView mav;
//對請求進行檢查 supportedMethods和requireSession
checkRequest(request);
// 看看synchronizeOnSession是否開啟,預設為false
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
//Httpsession可用
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
//加鎖,所有請求序列化
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 沒有可用的Httpsession -> 沒必要上鎖
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 正常呼叫處理方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//檢查響應頭是否包含Cache-Control
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
```
RequestMappingHandlerAdapter的invokeHandlerMethod方法,真正返回mv。
```java
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//對HttpServletRequest進行包裝,產生ServletWebRequest處理web的request物件
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//建立WebDataBinder物件的工廠
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//建立Model物件的工廠
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//將handlerMethod物件進行包裝,建立ServletInvocableHandlerMethod物件
//向invocableMethod設定相關屬性(最後是由invocableMethod物件呼叫invokeAndHandle方法
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//建立ModelAndViewContainer物件,裡面存放有向域中存入資料的map
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);
//省略非同步處理
//正常呼叫
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
//獲取ModelAndView物件
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
```
ServletInvocableHandlerMethod的invokeAndHandle方法:反射呼叫方法,得到返回值。
```java
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//獲取引數,通過反射得到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//設定響應狀態
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
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), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
```
### 引數解析過程
我們可以知道的是,傳遞引數時,可以傳遞Map,基本型別,POJO,ModelMap等引數,解析之後的結果又如何呢?我們以一個具體的例子舉例比較容易分析:
```java
@RequestMapping("/handle03/{id}")
public String handle03(@PathVariable("id") String sid,
Map map){
System.out.println(sid);
map.put("msg","你好!");
return "success";
}
```
```java
/**
* 獲取當前請求的方法引數值。
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//獲取引數物件
MethodParameter[] parameters = getMethodParameters();
//建立一個同等大小的陣列儲存引數值
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
//引數處理器處理引數(針對不同型別的引數有不同型別的處理引數的策略)
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
if (args[i] == null) {
throw new IllegalStateException();
}
return args;
}
```
resolveArgument方法:
```java
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//獲取註解的資訊
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//包裝parameter物件
MethodParameter nestedParameter = parameter.nestedIfOptional();
//獲取@PathVariable指定的屬性名
Object resolvedName = resolveStringValue(namedValueInfo.name);
//
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//根據name從url中尋找並獲取引數值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
//沒有匹配
if (arg == null) {
//如果有default值,則根據該值查詢
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
//如果required為false,則可以不指定name,但預設為true。
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
//雖然匹配,路徑中傳入的引數如果是“ ”,且有預設的name,則按照預設處理
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
```
getNameValueInfo方法:
```java
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
//從快取中獲取
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
//建立一個namedValueInfo物件
namedValueInfo = createNamedValueInfo(parameter);
//如果沒有在註解中指定屬性名,預設為引數名
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
//更新快取
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
```
createNamedValueInfo:獲取@PathVariable註解的資訊,封裝成NamedValueInfo物件
```java
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}
```
updateNamedValueInfo:
```java
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
//如果註解中沒有指定name,則為引數名
name = parameter.getParameterName();
if (name == null) {
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
}
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}
```
resolveName方法:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114328336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
引數解析的過程:
- 根據方法物件,獲取引數物件陣列,並建立儲存引數的陣列。
- 遍歷引數物件陣列,並根據引數解析器argumentResolver解析。
- 如果沒有引數解析器,報錯。
- 引數解析時,先嚐試獲取註解的資訊,以@PathVariable為例。
- 根據指定的name從url中獲取引數值,如果沒有指定,則預設為自己傳入的引數名。
### 傳遞頁面引數
我們可能會通過Map、Model、ModelMap等向域中存入鍵值對,這部分包含在請求處理中。
我們要關注的是ModelAndViewContainer這個類,它裡面預設包含著BindingAwareModelMap。
在解析引數的時候,就已經通過MapMethodProcessor引數處理器初始化了一個BindingAwareModelMap。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114341776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114347661.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
當然其實這裡重點還是引數解析,至於資料為什麼封裝進map,就很簡單了,無非是反射執行方法的時候,通過put將資料存入,當然最後的資料也就存在於ModelAndViewContainer中。
### 返回值解析
省略尋找返回值解析器的過程,因為返回值為檢視名,所以解析器為:ViewNameMethodReturnValueHandler。
```java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
//獲取檢視名
String viewName = returnValue.toString();
//向mavContainer中設定
mavContainer.setViewName(viewName);
//是否是isRedirectViewName
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null){
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
```
isRedirectViewName方法
```java
protected boolean isRedirectViewName(String viewName) {
//是否符合自定義的redirectPatterns,或者滿足redirect:開頭的名字
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
```
最後通過getModelAndView獲取mv物件,我們來詳細解析一下:
```java
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//Promote model attributes listed as @SessionAttributes to the session
modelFactory.updateModel(webRequest, mavContainer);
//如果請求已經處理完成
if (mavContainer.isRequestHandled()) {
return null;
}
//從mavContainer中獲取我們存入的資料map
ModelMap model = mavContainer.getModel();
//通過檢視名、modelmap、和status建立一個ModelAndView物件
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
```
最後返回的都是ModelAndView物件,包含了邏輯名和模型物件的檢視。
返回值解析的過程相對比較簡單:
- 根據返回的引數,獲取對應的返回值解析器。
- 獲取檢視名,如果是需要redirect,則`mavContainer.setRedirectModelScenario(true);`
- 其他情況下,直接給mvcContainer中的ViewName檢視名屬性設定上即可。
- 最後將mvcContainer的model、status、viewName取出,建立mv物件返回。
【總結】
引數解析、返回值解析兩個過程都包含大量的解決策略,其中尋找合適的解析器的過程都是先遍歷初始化的解析器表,然後判斷是否需要非同步處理,判斷是否可以處理返回值型別,如果可以的話,就使用該解析器進行解析,如果不行,就一直向下遍歷,直到表中沒有解析器為止。
## 3. 檢視解析
```java
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 保證渲染一次,cleared作為標記
if (mv != null && !mv.wasCleared()) {
//渲染過程!!!
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}
```
DispatcherServlet的render方法
```java
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
//獲取檢視名
String viewName = mv.getViewName();
if (viewName != null) {
//通過檢視解析器viewResolvers對檢視名進行處理,建立view物件
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
}
else {
view = mv.getView();
}
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
```
獲取檢視解析器,解析檢視名:
```java
@Nullable
protected View resolveViewName(String viewName, @Nullable Map model,
Locale locale, HttpServletRequest request) throws Exception {
//這裡我們註冊的是InternalResourceViewResolver
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
```
UrlBasedViewResolver的createView方法:
```java
@Override
protected View createView(String viewName, Locale locale) throws Exception {
//如果解析器不能處理所給的view,就返回null,讓下一個解析器看看能否執行
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
//判斷是否需要重定向
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
//判斷是否需要轉發
}
//呼叫父類的loadView方法
return super.createView(viewName, locale);
}
```
最後返回的檢視物件:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114403100.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
> 檢視解析器 viewResolver --例項化 --> view(無狀態的,不會有執行緒安全問題)
AbstractView的render方法
```java
@Override
public void render(@Nullable Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
//獲取合併後的map,有我們存入域中的map,還有PathVariable對應的鍵值等
Map mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//根據給定的model渲染內部資源,如將model設定為request的屬性
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
```
InternalResourceView的renderMergedOutputModel
```java
@Override
protected void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//將model中的值設定到request域中
exposeModelAsRequestAttributes(model, request);
// 如果有的話,給request設定helpers
exposeHelpers(request);
// 將目標地址設定到request中
String dispatcherPath = prepareForRendering(request, response);
// 獲取目標資源(通常是JSP)的RequestDispatcher。
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
// 如果已經包含或響應已經提交,則執行包含,否則轉發。
if (useInclude(request, response)) {
response.setContentType(getContentType());
rd.include(request, response);
}
else {
// Note: 轉發的資源應該確定內容型別本身。
rd.forward(request, response);
}
}
```
exposeModelAsRequestAttributes
```java
protected void exposeModelAsRequestAttributes(Map model,HttpServletRequest request) throws Exception {
//遍歷model
model.forEach((modelName, modelValue) -> {
if (modelValue != null) {
//向request中設定值
request.setAttribute(modelName, modelValue);
}
else {
//value為null的話,移除該name
request.removeAttribute(modelName);
}
});
}
```
### 檢視解析器
檢視解析器(實現ViewResolver介面):將邏輯檢視解析為具體的檢視物件。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200509114444607.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70)
每個檢視解析器都實現了Ordered介面,並開放order屬性,order越小優先順序越高。
按照檢視解析器的優先順序對邏輯檢視名進行解析,直到解析成功並返回檢視物件,否則丟擲異常。
### 檢視
檢視(實現View介面):渲染模型資料,將模型資料以某種形式展現給使用者。
最終採取的檢視物件對模型資料進行渲染render,處理器並不關心,處理器關心生產模型的資料,實現解耦。