在MVC请求流程中,处理Controller控制器逻辑后获取到的ModelAndView对象并不能直接返回到浏览器,需要通过对ModelAndView中的View属性做解析获取视图,并用Model属性中的数据完成对视图的渲染再返回浏览器。
1、核心流程图
2、核心流程源码分析
处理ModelAndView返回结果进行View视图渲染,有关处理过程中出现异常的视图处理,在MVC异常处理时统一分析。本文着重对处理过程中无异常,返回结果为String类型的视图解析处理,DispatcherServlet#processDispatchResult() 核心伪代码:
1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 2 @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, 3 @Nullable Exception exception) throws Exception { 4 5 // 标记是否为处理生成异常的ModelAndView对象 6 boolean errorView = false; 7 8 // 如果请求处理过程中有异常抛出则处理异常 9 if (exception != null) { 10 // 从ModelAndViewDefiningException中获得ModelAndView对象 11 if (exception instanceof ModelAndViewDefiningException) { 12 logger.debug("ModelAndViewDefiningException encountered", exception); 13 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 14 } 15 // 处理异常,生成ModelAndView对象 16 else { 17 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 18 mv = processHandlerException(request, response, handler, exception); 19 errorView = (mv != null); 20 } 21 } 22 23 // 是否进行页面渲染 24 if (mv != null && !mv.wasCleared()) { 25 // 渲染页面 26 render(mv, request, response); 27 } 28 29 // 发出请求处理完成通知,触发Interceptor的afterCompletion 30 if (mappedHandler != null) { 31 mappedHandler.triggerAfterCompletion(request, response, null); 32 } 33 }
有关处理过程中出现异常的场景,本节暂不分析。
2.1、是否需要页面渲染
当前ModelAndView对象不为空,当前ModelAndView对象是否被清理。
ModelAndView#wasCleared() 核心代码
1 // 当前实例是否被清理标识,通过调用clear()方法,可完成清理,默认为false 2 private boolean cleared = false; 3 4 // 当前ModelAndView是否被清理判断 5 public boolean wasCleared() { 6 // 判断清理标识及 ModelAndView中的model属性、View属性 7 return (this.cleared && isEmpty()); 8 }
ModelAndView#wasCleared() 核心代码
1 // ModelAndView是否为一个空对象或者model属性、view为空 2 public boolean isEmpty() { 3 return (this.view == null && CollectionUtils.isEmpty(this.model)); 4 }
页面渲染条件:
ModelAndView对象不为空、当前ModelAndView对象未被清理,并且ModelAndView中的model属性、View属性都不为空,
2.2、页面渲染
页面渲染,DispatcherServlet#render() 核心伪代码:
1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 2 // 解析request中获得Locale对象,并设置到response 中 3 Locale locale = 4 (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); 5 response.setLocale(locale); 6 7 // 声明 View 对象 8 View view; 9 // 获取ModelAndView中的视图 10 String viewName = mv.getViewName(); 11 // 返回视图为字符串类型,通过对视图名称的解析获取视图 12 if (viewName != null) { 13 // 根据 viewName 获得 View 对象 14 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); 15 // 获取不到view,抛出 ServletException 异常 16 if (view == null) { 17 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 18 "' in servlet with name '" + getServletName() + "'"); 19 } 20 } 21 else { 22 // 直接使用 ModelAndView 对象的 View 对象 23 view = mv.getView(); 24 // 获取不到view,抛出 ServletException 异常 25 if (view == null) { 26 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 27 "View object in servlet with name '" + getServletName() + "'"); 28 } 29 } 30 31 // 设置响应的状态码 32 if (mv.getStatus() != null) { 33 response.setStatus(mv.getStatus().value()); 34 } 35 // 渲染页面 36 view.render(mv.getModelInternal(), request, response); 37 }
1、解析请求Request获取Locale设置到响应Response中
语言处理器LocaleResolver为MVC内置的九大组件之一,在源码(三):MVC九大内置组件初始化中已经提到DispatcherServlet#localeResolver属性的初始化,获取默认处理器AcceptHeaderLocaleResolver,这里不再赘述。
2、获取视图View
获取View视图名称,ModelAndView#getViewName() 核心代码
1 // 若未view为字符串类型,则返回view;否则返回null 2 public String getViewName() { 3 return (this.view instanceof String ? (String) this.view : null); 4 }
获取ModelAndView对象中的view属性,对view类型做如下判断
2.1、ModelAndView属性view类型为String
解析返回的字符串获取View对象,DispatcherServlet#resolveViewName() 核心代码
1 // 解析视图名称 2 protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, 3 Locale locale, HttpServletRequest request) throws Exception { 4 // 视图解析器为空,返回null 5 if (this.viewResolvers != null) { 6 // 遍历 ViewResolver 数组 7 for (ViewResolver viewResolver : this.viewResolvers) { 8 // 根据 viewName + locale 参数,解析出 View 对象 9 View view = viewResolver.resolveViewName(viewName, locale); 10 // 解析成功,直接返回 View 对象 11 if (view != null) { 12 return view; 13 } 14 } 15 } 16 return null; 17 }
在源码(三):MVC九大内置组件初始化中已提及视图解析器ViewResolver的初始化,获取MVC默认的视图解析器InternalResourceViewResolver,类图如下:
![0](/i/l/?n=23&i=blog/1680081/202302/1680081-20230218195947012-305969937.png)
InternalResourceViewResolver对象的构造函数如下,在创建InternalResourceViewResolver对象时,初始化viewClass属性为JstlView.class。
1 public InternalResourceViewResolver() { 2 Class<?> viewClass = requiredViewClass(); 3 if (InternalResourceView.class == viewClass && jstlPresent) { 4 viewClass = JstlView.class; 5 } 6 setViewClass(viewClass); 7 }
resolveViewName()方法在父类AbstractCachingViewResolver中实现,下面来看看视图是如何被解析出来的,AbstractCachingViewResolver#resolveViewName()核心伪代码
1 public View resolveViewName(String viewName, Locale locale) throws Exception { 2 // 如果禁用缓存,则创建 viewName 对应的 View 对象 3 if (!isCache()) { 4 return createView(viewName, locale); 5 } 6 else { 7 // 获得缓存 KEY 8 Object cacheKey = getCacheKey(viewName, locale); 9 // 从 viewAccessCache 缓存中,获得 View 对象 10 View view = this.viewAccessCache.get(cacheKey); 11 // 如果获得不到缓存,则从 viewCreationCache 中,获得 View 对象 12 if (view == null) { 13 synchronized (this.viewCreationCache) { 14 // 从 viewCreationCache 中,获得 View 对象 15 view = this.viewCreationCache.get(cacheKey); 16 if (view == null) { 17 // Ask the subclass to create the View object. 18 // 创建 viewName 对应的 View 对象 19 view = createView(viewName, locale); 20 // 如果创建失败,但是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW 21 if (view == null && this.cacheUnresolved) { 22 view = UNRESOLVED_VIEW; 23 } 24 // 如果 view 非空,则添加到 viewAccessCache 缓存中 25 if (view != null && this.cacheFilter.filter(view, viewName, locale)) { 26 this.viewAccessCache.put(cacheKey, view); 27 this.viewCreationCache.put(cacheKey, view); 28 } 29 } 30 } 31 } 32 33 return (view != UNRESOLVED_VIEW ? view : null); 34 } 35 }
1、如果禁用缓存,创建视图
2、获取缓存的key
获取缓存的Key,直接返回viewName,UrlBasedViewResolver#getCacheKey() 核心代码
1 // 获取缓存的key 2 protected Object getCacheKey(String viewName, Locale locale) { 3 // 重写了父类的方法,去除locale直接返回viewName 4 return viewName; 5 }
3、创建视图View
根据key从AbstractCachingViewResolver的viewAccessCache缓存中获取View视图,若为缓存中不存在,创建View视图并添加进缓存中;若缓存中存在,返回缓存中的视图。
当前缓存中不存在需创建视图,创建视图 UrlBasedViewResolver#createView() 核心伪代码
1 // 创建视图 2 protected View createView(String viewName, Locale locale) throws Exception { 3 // ... 4 // 调用父类实现,加载视图 5 return super.createView(viewName, locale); 6 }
-> 创建视图 AbstractCachingViewResolver#createView() 核心代码
1 // 创建视图 2 protected View createView(String viewName, Locale locale) throws Exception { 3 // 根据视图名称加载视图 4 return loadView(viewName, locale); 5 }
-> 加载视图 UrlBasedViewResolver#loadView() 核心伪代码
1 // 加载视图 2 protected View loadView(String viewName, Locale locale) throws Exception { 3 // 创建 viewName 对应的 View 对象 4 AbstractUrlBasedView view = buildView(viewName); 5 // 完成view实例初始化bean的处理 6 View result = applyLifecycleMethods(viewName, view); 7 // 返回view对象 8 return (view.checkResource(locale) ? result : null); 9 }
构建并返回View视图,构建View视图,UrlBasedViewResolver#buildView() 核心伪代码:
1 protected AbstractUrlBasedView buildView(String viewName) throws Exception { 2 // 获取Class对象,在InternalResourceViewResolver类构造器中设置JstlView.class 3 Class<?> viewClass = getViewClass(); 4 // 通过反射实例化View对象 5 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); 6 // 设置url: 配置文件中 前缀 + 视图名称 + 后缀 7 view.setUrl(getPrefix() + viewName + getSuffix()); 8 view.setAttributesMap(getAttributesMap()); 9 // ... 10 return view; 11 }
完成View对象的创建及视图路径url的拼接设置,返回view对象详情。
4、 如果视图创建失败,但是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW 进缓存中
5、如果解析视图失败返回null,否则返回解析的View视图对象
2.2、ModelAndView属性view类型为View
如果ModelAndView属性view为View类型,直接获取View对象,若属性为空,抛出异常。
2.3、View页面渲染
页面渲染,AbstractView#render() 核心伪代码:
1 public void render(@Nullable Map<String, ?> model, HttpServletRequest request, 2 HttpServletResponse response) throws Exception { 3 4 // 合并返回结果,将 Model 中的静态数据和请求中的动态数据进行合并 5 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); 6 // 进行一些准备工作(修复 IE 中存在的 BUG) 7 prepareResponse(request, response); 8 // 进行渲染 9 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); 10 }
1、将request请求中的参数与model属性中的参数合并
2、进行页面渲染
页面渲染,InternalResourceView#renderMergedOutputModel() 核心伪代码
1 protected void renderMergedOutputModel( 2 Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { 3 4 // 将model内容设置进request的属性中 5 exposeModelAsRequestAttributes(model, request); 6 7 // 往请求中设置一些属性,Locale、TimeZone、LocalizationContext 8 exposeHelpers(request); 9 10 // 获取需要转发的路径 11 String dispatcherPath = prepareForRendering(request, response); 12 13 // 获取请求转发器 14 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); 15 16 // ... 17 // 进行转发 18 rd.forward(request, response); 19 }
1、将model属性设置到request的attribute属性中,并设置request其他相关属性
2、根据需要转发的路径与request请求,获取请求转发器
3、由请求转发器进行转发,后续流程由tomcat处理。
标签:null,viewName,SpringMVC,视图,源码,ModelAndView,view,View From: https://www.cnblogs.com/RunningSnails/p/17133432.html