SpringMVC源碼學習:容器初始化+MVC初始化+請求分發處理+參數解析+返回值解析+視圖解析
一、前言
版本:
springMVC 5.0.2RELEASE
JDK1.8
前端控制器的配置:
web.xml
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--加載類路徑下的配置文件-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--服務器啟動時創建對象,值越小,優先級越高,越先創建對象-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--注意不是/*,而是,因為/*還會攔截*.jsp等請求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
springmvc.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:mvc="//www.springframework.org/schema/mvc"
xmlns:context="//www.springframework.org/schema/context"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
//www.springframework.org/schema/beans
//www.springframework.org/schema/beans/spring-beans.xsd
//www.springframework.org/schema/mvc
//www.springframework.org/schema/mvc/spring-mvc.xsd
//www.springframework.org/schema/context
//www.springframework.org/schema/context/spring-context.xsd">
<!-- 開啟註解掃描 -->
<context:component-scan base-package="com.smday"/>
<!-- 視圖解析器對象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 開啟SpringMVC框架註解的支持 -->
<mvc:annotation-driven/>
<!--放行靜態資源-->
<mvc:default-servlet-handler/>
</beans>
二、初始化
DispatcherServlet的啟動與Servlet的啟動過程緊密聯繫,我們通過以上繼承圖就可以發現。
1. 容器初始化
Servlet中定義的init()方法就是其生命周期的初始化方法,接着往下走,GenericServlet並沒有給出具體實現,在HttpServletBean中的init()方法給出了具體的實現:
HttpServletBean.init()
方法(忽略日誌)
@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()
幹了啥(基本都是日誌記錄,還有計時,省略了這些部分):
/**
* 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方法上,我們可以先來看看執行後的效果:
可以看到springMVC九大組件被賦值,除此之外webApplicationContext也已被賦值。
我們再來看看源碼,看看其內部具體實現:FrameworkServlet.initWebApplicationContext()
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;
}
根容器查找的方法
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContextUtils.getWebApplicationContext
//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,這個方法允許顯式傳入父級容器作為參數。
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);
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。
- beanDefinition的載入過程,springMVC做了一些改變,比如定義了針對mvc的命名空間解析MvcNamespaceHandler。
- 接着是beanDefinition在IoC中的註冊,也就是把beanName:beanDefinition以鍵值對的形式存入beandefinitionMap中。
2. MVC的初始化
MVC的初始化在DispatcherServlet的initStratefies方法中執行,通過方法名,我們就可以得出結論,就是在這進行了對九大組件的初始化,其實基本上都是從IoC容器中獲取對象:
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);
}
文件上傳解析器
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 默認是沒有配置multipartResolver的.
this.multipartResolver = null;
}
}
配置文件上傳解析器也很簡單,只需要在容器中註冊MultipartResolver即可開啟文件上傳功能。
區域信息解析器
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
文件中規定了幾大組件初始化的默認策略。
handler映射信息解析
handlerMappings存在的意義在於為HTTP請求找到對應的控制器Controller。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//從所有的IoC容器中導入HandlerMappings,包括其雙親上下文
if (this.detectAllHandlerMappings) {
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.
}
}
//保證至少有一個handlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
接下來幾個操作都差不多,就不贅述了。
總的來說,MVC初始化的過程建立在IoC容器初始化之後,畢竟要從容器中取出這些組件對象。
3. HandlerMapping的實現原理
HandlerExecutionChain
HandlerMapping在SpringMVC扮演着相當重要的角色,我們說,它可以為HTTP請求找到 對應的Controller控制器,於是,我們來好好研究一下,這裏面到底藏着什麼玩意。
HandlerMapping是一個接口,其中包含一個getHandler方法,能夠通過該方法獲得與HTTP請求對應的handlerExecutionChain,而這個handlerExecutionChain對象中持有handler和interceptorList,以及和設置攔截器相關的方法。可以判斷是同通過這些配置的攔截器對handler對象提供的功能進行了一波增強。
RequestMappingHandlerMapping
我們以其中一個HandlerMapping作為例子解析一下,我們關注一下:
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註解
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
detectHandlerMethods方法:
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<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
return getMappingForMethod(method, userType);
});
//遍歷methods[Method,{path}]
for (Map.Entry<Method, T> entry : methods.entrySet()) {
//對方法的可訪問性進行校驗,如private,static,SpringProxy
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
//獲取最終請求路徑
T mapping = entry.getValue();
//註冊
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
mapping對象的屬性:
methods對象中存儲的元素:
註冊方法在AbstractHandlerMethodMapping中實現:
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<String> 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方法:
@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容器,又負責請求分發):
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//忽略一大串前期準備,使其能夠處理view 對象
//接着進入真正的分發
doDispatch(request, response);
}
doService:
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方法,含有真正處理請求的邏輯。
@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。
@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方法:反射調用方法,得到返回值。
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等參數,解析之後的結果又如何呢?我們以一個具體的例子舉例比較容易分析:
@RequestMapping("/handle03/{id}")
public String handle03(@PathVariable("id") String sid,
Map<String,Object> map){
System.out.println(sid);
map.put("msg","你好!");
return "success";
}
/**
* 獲取當前請求的方法參數值。
*/
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方法:
@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方法:
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對象
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}
updateNamedValueInfo:
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方法:
參數解析的過程:
- 根據方法對象,獲取參數對象數組,並創建存儲參數的數組。
- 遍歷參數對象數組,並根據參數解析器argumentResolver解析。
- 如果沒有參數解析器,報錯。
- 參數解析時,先嘗試獲取註解的信息,以@PathVariable為例。
- 根據指定的name從url中獲取參數值,如果沒有指定,則默認為自己傳入的參數名。
傳遞頁面參數
我們可能會通過Map、Model、ModelMap等向域中存入鍵值對,這部分包含在請求處理中。
我們要關注的是ModelAndViewContainer這個類,它裏面默認包含着BindingAwareModelMap。
在解析參數的時候,就已經通過MapMethodProcessor參數處理器初始化了一個BindingAwareModelMap。
當然其實這裡重點還是參數解析,至於數據為什麼封裝進map,就很簡單了,無非是反射執行方法的時候,通過put將數據存入,當然最後的數據也就存在於ModelAndViewContainer中。
返回值解析
省略尋找返回值解析器的過程,因為返回值為視圖名,所以解析器為:ViewNameMethodReturnValueHandler。
@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方法
protected boolean isRedirectViewName(String viewName) {
//是否符合自定義的redirectPatterns,或者滿足redirect:開頭的名字
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
最後通過getModelAndView獲取mv對象,我們來詳細解析一下:
@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<String, ?> 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. 視圖解析
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方法
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);
}
獲取視圖解析器,解析視圖名:
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> 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方法:
@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);
}
最後返回的視圖對象:
視圖解析器 viewResolver –實例化 –> view(無狀態的,不會有線程安全問題)
AbstractView的render方法
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
//獲取合併後的map,有我們存入域中的map,還有PathVariable對應的鍵值等
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//根據給定的model渲染內部資源,如將model設置為request的屬性
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
InternalResourceView的renderMergedOutputModel
@Override
protected void renderMergedOutputModel(
Map<String, Object> 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
protected void exposeModelAsRequestAttributes(Map<String, Object> 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接口):將邏輯視圖解析為具體的視圖對象。
每個視圖解析器都實現了Ordered接口,並開放order屬性,order越小優先級越高。
按照視圖解析器的優先順序對邏輯視圖名進行解析,直到解析成功並返回視圖對象,否則拋出異常。
視圖
視圖(實現View接口):渲染模型數據,將模型數據以某種形式展現給用戶。
最終採取的視圖對象對模型數據進行渲染render,處理器並不關心,處理器關心生產模型的數據,實現解耦。