在上一篇博客中解析到在InvocableHandlerMethod#doInvoke()中通过反射执行处理方法, 返回值为ModelAndView实例
InvocableHandlerMethod#doInvoke()方法调用链:
在RequestMappingHandlerAdapter#invokeHandlerMethod()方法中主要完成了两件事:
- 执行处理器方法(HandlerMethod)
- 封装并返回ModelAndView实例
(1) 执行处理方法以及封装ModelAndView
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//将Request和Response进行封装
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
/**
* 创建InvocableHandlerMethod实例,以及各个组件的配置;
* 后面通过调用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 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();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
/**
* 1. 执行处理器
*/
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
/**
* 2. 返回ModelAndView实例, 后面进行视图解析
*/
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
分析: 在invokeAndHandle()方法在执行处理器方法,并对返回值进行封装
ServletInvocableHandlerMethod#invokeAndHandle()方法实现:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
/**
* 1. 处理请求
* returnValue为返回的ModelAndView实例或者ViewName
*/
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 {
/**
* 2. 封装返回的数据信息
*/
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;
}
}
分析: 关于处理方法的执行逻辑不再陈述, 相关逻辑可以看这篇博客:SpringMVC源码解析五(HandlerMethod执行过程解析); 这里着重分析ModelAndView的解析
HandlerMethodReturnValueHandlerComposite#handleReturnValue()方法实现:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//根据返回值以及返回类型选择合适的返回值处理器, 对返回值进行解析
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
/**
* 解析结果
* {@link ModelAndViewMethodReturnValueHandler#handleReturnValue()}
*/
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
ModelAndViewMethodReturnValueHandler#handleReturnValue()方法实现:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
/**
* 1.获取视图
*/
ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = mav.getViewName();
//设置视图名称
mavContainer.setViewName(viewName);
if (viewName != null && isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
View view = mav.getView();
//设置视图实例
mavContainer.setView(view);
if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
/**
* 2.设置返回状态
*/
mavContainer.setStatus(mav.getStatus());
/**
* 3.设置数据Model
*/
mavContainer.addAllAttributes(mav.getModel());
}
分析: 将返回值强转为ModelAndView实例, 通过ModelAndView实例将返回状态, 返回视图以及数据模型信息封装到ModelAndViewContainer容器中
(2) 从ModelAndViewContainer容器中获取ModeAndView实例
RequestMappingHandlerAdapter#getModelAndView()方法实现
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
/**
* 根据从容器中获取的视图名称以及数据模型等信息创建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是如何获取的, 接下来开始解析ModelAndView
DispatcherServlet#processDispatchResult()方法的实现:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
/**
* 如果在解析过程中出现异常, 这里会对异常进行处理
*/
if (exception != null) {
//如果是默认异常,则获取异常视图
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
//如果是自定义异常, 则获取异常处理器, 进行解析
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
/**
* 异常视图解析
*/
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 处理程序是否返回了要渲染的视图?
if (mv != null && !mv.wasCleared()) {
/**
* 渲染视图
*/
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
分析:
- 如果出现异常,则解析异常视图
- 解析ModelAndView
(1) DispatcherServlet#processHandlerException()方法实现:
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
//检查注册的HandlerExceptionResolvers ...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
/**
* 遍历所有的异常解析器, 尝试对异常进行解析, 如果解析成功,跳出循焕
*/
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
//对于简单的错误模型,我们可能仍需要视图名称转换
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
//设置错误请求的相关属性
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
(2) 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) {
/**
* 根据ViewName解析view实例
*/
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
//无需查找:ModelAndView对象包含实际的View对象。
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
/**
* 视图渲染
* {@link AbstractView#render(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
*/
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
ModelAndView视图解析可以分为两个重要步骤: (1)根据视图名称创建View实例, (2)根据View实例渲染视图; 对于这两个操作SpringMVC抽象了两个接口ViewResolver和View, 这两个接口的定义如下:
public interface ViewResolver {
// 通过逻辑视图名和用户地区信息生成View对象
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
public interface View {
/**
* 获取返回值的contentType
*/
@Nullable
default String getContentType() {
return null;
}
/**
* 通过用户提供的模型数据与视图信息渲染视图
*/
void render(@Nullable Map model, HttpServletRequest request,HttpServletResponse response)throws Exception;
}
ViewResolver和View继承关系图如下:
视图解析执行链如下:
DispatcherServlet#resolveViewName()方法实现:
@Nullable
protected View resolveViewName(String viewName, @Nullable Map model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
/**
* ViewResolver ==> {@link org.springframework.web.servlet.view.InternalResourceViewResolver}
* 这里的视图解析器就是我们在配置文件中配置的那个
*
* {@link AbstractCachingViewResolver#resolveViewName(String,Locale)}
*/
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
分析: 视图实例View的创建委托给我们定义的视图解析器(InternalResourceViewResolver)来解析
AbstractCachingViewResolver#resolveViewName()方法实现:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//判断缓存是否可用
if (!isCache()) {
/**
* 如果缓存不可用, 则直接创建视图
*/
return createView(viewName, locale);
}
else {
/**
* 如果缓存可用, 则先尝试从缓存中获取
*/
//生成缓存Key
Object cacheKey = getCacheKey(viewName, locale);
//尝试从缓存中获取视图
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
/**
* 如果从缓存中获取视图失败, 则尝试从viewCreationCache缓存中获取
*/
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
/**
* 让子类创建View对象, 留给子类扩展[扩展开放,修改关闭原则]
*{@link UrlBasedViewResolver#createView(java.lang.String, java.util.Locale)}
*/
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
// 这里cacheUnresolved指的是是否缓存默认的空视图,UNRESOLVED_VIEW是
// 一个没有任何内容的View
view = UNRESOLVED_VIEW;
}
if (view != null) {
//将创建的view视图加入缓存
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
分析: 首先判断缓存是否可用, 如果缓存不可用则直接创建视图; 如果缓存可用, 则尝试从缓存中获取, 如果从缓存中获取失败, 则加锁,创建视图, 在获取锁之后再次尝试从缓存中获取; 如果创建视图失败, 则将创建一个空视图返回;
UrlBasedViewResolver#createView()方法实现:
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// 如果此解析器不应该处理给定的视图,则返回null以传递到链中的下一个解析器。
if (!canHandle(viewName, locale)) {
return null;
}
/**
* 检查特殊的"redirect:"前缀 REDIRECT_URL_PREFIX = "redirect:"
* 如果是以"redirect:" 开头, 说明该视图是重定向
*/
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
/**
* 检查特殊的"forward:"前缀 FORWARD_URL_PREFIX = "forward:"
* 如果是以"forward:" 开头, 说明该视图是请求转发
*/
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
/**
* 如果是普通视图, 创建该视图视图
*/
return super.createView(viewName, locale);
}
分析: 这里对视图名称进行解析, 如果视图名称是以" redirect: " 开头,则作为重定向处理; 如果视图是以" forward: "开头, 则作为请求转发处理; 否则就创建视图,并返回
AbstractCachingViewResolver#createView()方法实现:
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
/**
* {@link UrlBasedViewResolver#loadView(java.lang.String, java.util.Locale)}
*/
return loadView(viewName, locale);
}
UrlBasedViewResolver#loadView()方法实现:
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
/**
* 使用逻辑视图名按照指定规则生成View对象
* {@link InternalResourceViewResolver#buildView(java.lang.String)}
*/
AbstractUrlBasedView view = buildView(viewName);
/**
* 应用声明周期函数,也就是调用View对象的初始化函数和Spring用于切入bean创建的
* Processor和Aware函数
*/
View result = applyLifecycleMethods(viewName, view);
// 检查view的准确性,这里默认始终返回true
return (view.checkResource(locale) ? result : null);
}
分析:
- 使用逻辑视图名按照指定规则生成View对象
- 应用声明周期函数,也就是调用View对象的初始化函数和Spring用于切入bean创建的Processor和Aware函数
(1) InternalResourceViewResolver#buildView()方法实现:
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
UrlBasedViewResolver#buildView()方法实现:
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 对于InternalResourceViewResolver而言,其返回的View对象的具体类型是InternalResourceView
Class> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
// 使用反射生成InternalResourceView对象实例
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
//根据前缀和后缀拼接视图路径信息
view.setUrl(getPrefix() + viewName + getSuffix());
// 设置View的contentType属性
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
// 设置contextAttribute和attributeMap等属性
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
// pathVariables表示request请求url中的属性,这里主要是设置是否将这些属性暴露到视图中
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
// 这里设置的是是否将Spring的bean暴露在视图中,以供给前端调用
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
// 设置需要暴露给前端页面的bean名称
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
(2) UrlBasedViewResolver#applyLifecycleMethods()方法实现:
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
ApplicationContext context = getApplicationContext();
if (context != null) {
// 对生成的View对象应用初始化方法,主要包括InitializingBean.afterProperties()和一些
// Processor,Aware方法
Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
if (initialized instanceof View) {
return (View) initialized;
}
}
return view;
}
AbstractView#render()方法实现:
@Override
public void render(@Nullable Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
/**
* 这里主要是将request中pathVariable,staticAttribute与用户返回的model属性
* 合并为一个Map对象,以供给后面对视图的渲染使用
*/
Map mergedModel = createMergedOutputModel(model, request, response);
/**
* 判断当前View对象的类型是否为文件下载类型,如果是文件下载类型,则设置response的
* Pragma和Cache-Control等属性值
*/
prepareResponse(request, response);
/**
* 开始view视图渲染以及数据输出整理 ==> 重点
* {@link InternalResourceView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
*/
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
InternalResourceView#renderMergedOutputModel()方法实现:
@Override
protected void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
/**
* 将Model中的键值对数据全部写进RequestScope中
*/
exposeModelAsRequestAttributes(model, request);
//提供的一个hook方法,默认是空实现,用于用户进行request属性的自定义使用
exposeHelpers(request);
// 确定请求分配器的路径
String dispatcherPath = prepareForRendering(request, response);
/**
* 获取当前request的RequestDispatcher对象,该对象有两个方法:include()和forward(),
* 用于对当前的request进行转发,其实也就是将当前的request转发到另一个url,这里的另一个
* url就是要解析的视图地址,也就是说进行视图解析的时候请求的对于文件的解析实际上相当于
* 构造了另一个(文件)请求,在该请求中对文件内容进行渲染,从而得到最终的文件。这里的
* include()方法表示将目标文件引入到当前文件中,与jsp中的include标签作用相同;
* forward()请求则表示将当前请求转发到另一个请求中,也就是目标文件路径,这种转发并不会
* 改变用户浏览器地址栏的请求地址。
*/
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// 判断当前是否为include请求,如果是,则调用RequestDispatcher.include()方法进行文件引入
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
/**
* 请求转发
*
* 使用forward跳转则后面的response输出则不会执行,而用include来跳转,
* 则include的servlet执行完后,再返回到原来的servlet执行response的输出(如果有)
*/
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
/**
* 如果当前不是include()请求,则直接使用forward请求将当前请求转发到目标文件路径中,
* 从而渲染该视图
*/
rd.forward(request, response);
}
}
至此,视图解析分析完成;
相关文章:
SpringMVC源码解析一(在Spring源码项目中搭建SpringMVC源码模块)
SpringMVC源码解析二(请求过程解析)
SpringMVC源码解析三(处理映射器HandlerMapping的解析)
SpringMVC源码解析四(处理适配器HandlerAdapter的解析)
SpringMVC源码解析五(HandlerMethod执行过程解析)
SpringMVC源码解析六(ModelAndView解析)
SpringMVC源码解析七(初始化过程解析