SpringMVC源码(九):无异常View视图解析

  在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

  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处理。

 

 
posted @ 2023-02-18 20:12  无虑的小猪  阅读(38)  评论(0编辑  收藏  举报