SpringMVC源码总结(十二)ViewResolver介绍

简介:
首先我们先看看ModelAndView中重要的View接口。 
View接口:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String getContentType();
 
     /**
      * Render the view given the specified model.
      * <p>The first step will be preparing the request: In the JSP case,
      * this would mean setting model objects as request attributes.
      * The second step will be the actual rendering of the view,
      * for example including the JSP via a RequestDispatcher.
      * @param model Map with name Strings as keys and corresponding model
      * objects as values (Map can also be {@code null} in case of empty model)
      * @param request current HTTP request
      * @param response HTTP response we are building
      * @throws Exception if rendering failed
      */
//上面说的很清楚,对于jsp来说,第一步就是将model作为request的attributes;第二步才开始渲染view
     void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

再看下ViewResolver接口: 
?
1
View resolveViewName(String viewName, Locale locale) throws Exception;

它是对给定的viewName找到对应的View对象,然后使用该view对象的render方法将本身的内容写到response中。 
然后就看下,当我们的处理函数返回一个viewName时,SpringMVC是如何渲染的。
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try {
                     // Actually invoke the handler.
                     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                 }
                 finally {
                     if (asyncManager.isConcurrentHandlingStarted()) {
                         return ;
                     }
                 }
 
                 applyDefaultViewName(request, mv);
                 mappedHandler.applyPostHandle(processedRequest, response, mv);
             }
             catch (Exception ex) {
                 dispatchException = ex;
             }
//这里是我们的关注重点,就是进行视图渲染的过程
             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
         }
         catch (Exception ex) {
             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
         }
         catch (Error err) {
             triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
         }

继续看下processDispatchResult是如何来渲染的 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
             HandlerExecutionChain mappedHandler, ModelAndView mv, 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 );
             }
         }
 
         // Did the handler return a view to render?
//这里是我们关注的重点
         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 );
         }
     }

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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.resolveLocale(request);
         response.setLocale(locale);
 
         View view;
         if (mv.isReference()) {
             // We need to resolve the view name.
             view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
             if (view == null ) {
                 throw new ServletException( "Could not resolve view with name '" + mv.getViewName() +
                         "' in servlet with name '" + getServletName() + "'" );
             }
         }
         else {
             // No need to lookup: the ModelAndView object contains the actual View object.
             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 {
             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;
         }
     }

这里可以看到整体的处理流程。首先判断view是不是一个视图的名称,若是需要找到这个视图名称对应的View对象,然后便是调用view对象的render方法,渲染到response中。 
由于我们的处理函数经常仅仅是返回一个view名称,所以我们重点要看看它是如何根据视图名称来找到对应的View对象的,即resolveViewName方法内容。其实上文已经说明了View接口和ViewResolver 接口,ViewResolver 接口就是根据view名称来找到对应的View对象的,所以看下面就会很清晰明白
 
?
1
2
3
4
5
6
7
8
9
10
11
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
             HttpServletRequest request) throws Exception {
 
         for (ViewResolver viewResolver : this .viewResolvers) {
             View view = viewResolver.resolveViewName(viewName, locale);
             if (view != null ) {
                 return view;
             }
         }
         return null ;
     }

这里就是对DispatcherServlet的private List<ViewResolver> viewResolvers属性进行遍历找到一个能够获取View对象的ViewResolver,并返回这个view对象。 
至此整个流程便走通了,接下来就是要看看有哪些ViewResolver以及它们的注册来源是什么? 

常用的ViewResolver有:FreeMarkerViewResolver、InternalResourceViewResolver、VelocityViewResolver等。 

接下来就是如何来注册这些ViewResolver:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
protected void initStrategies(ApplicationContext context) {
         initMultipartResolver(context);
         initLocaleResolver(context);
         initThemeResolver(context);
         initHandlerMappings(context);
         initHandlerAdapters(context);
         initHandlerExceptionResolvers(context);
         initRequestToViewNameTranslator(context);
//我们关注的重点
         initViewResolvers(context);
         initFlashMapManager(context);
     }

还是在DispatcherServlet的初始化策略中,调用了initViewResolvers,如下: 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void initViewResolvers(ApplicationContext context) {
         this .viewResolvers = null ;
 
         if ( this .detectAllViewResolvers) {
             // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
             Map<String, ViewResolver> matchingBeans =
                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver. class , true , false );
             if (!matchingBeans.isEmpty()) {
                 this .viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
                 // We keep ViewResolvers in sorted order.
                 OrderComparator.sort( this .viewResolvers);
             }
         }
         else {
             try {
                 ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver. class );
                 this .viewResolvers = Collections.singletonList(vr);
             }
             catch (NoSuchBeanDefinitionException ex) {
                 // Ignore, we'll add a default ViewResolver later.
             }
         }
 
         // Ensure we have at least one ViewResolver, by registering
         // a default ViewResolver if no other resolvers are found.
         if ( this .viewResolvers == null ) {
             this .viewResolvers = getDefaultStrategies(context, ViewResolver. class );
             if (logger.isDebugEnabled()) {
                 logger.debug( "No ViewResolvers found in servlet '" + getServletName() + "': using default" );
             }
         }
     }

这和HandleMapping和HandlerAdapter的初始化过程基本类似。this.detectAllViewResolvers是DispatcherServlet的一个boolean属性,可以在web.xml文件中修改这个值,默认是true。 
?
1
2
/** Detect all ViewResolvers or just expect "viewResolver" bean? */
     private boolean detectAllViewResolvers = true ;

当detectAllViewResolvers为true,意味着就会获取从xml文件中解析出来的ViewResolver。如果为false,则直接去找bean name为"viewResolver"并且是ViewResolver类型的作为DispatcherServlet的ViewResolver。 
当上述两种情况都没有找到,则会启用默认的ViewResolver,在this.viewResolvers = getDefaultStrategies(context, ViewResolver.class)中,这个过程已经多次说过,可以见本系列第一篇HandleMapping的来源。它就是依据DispatcherServlet.properties文件中所配置的ViewResolver,如下:
 
?
1
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

也就是默认采用的是InternalResourceViewResolver。 
再说说在xml文件中配置ViewResolver的情况,如下:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean class = "org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer" >
         <property name= "templateLoaderPath" value= "/WEB-INF/views" />
         <property name= "defaultEncoding" value= "utf-8" />
         <property name= "freemarkerSettings" >
             <props>
                 <prop key= "locale" >zh_CN</prop>
             </props>
         </property>
     </bean>
     <bean class = "org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" >
         <property name= "suffix" value= ".html" />
         <property name= "contentType" value= "text/html;charset=utf-8" />
         <property name= "requestContextAttribute" value= "request" />
         <property name= "exposeRequestAttributes" value= "true" />
         <property name= "exposeSessionAttributes" value= "true" />
     </bean>

这里是以FreeMarkerViewResolver为例来说明,它的配置内容还是需要有待继续研究。这里只是粗略的说下它的继承情况。 
FreeMarkerViewResolver继承AbstractTemplateViewResolver继承UrlBasedViewResolver继承AbstractCachingViewResolver。 
首先是抽象类AbstractCachingViewResolver:它加入了缓存功能,它有几个重要的属性。
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** Default maximum number of entries for the view cache: 1024 */
     public static final int DEFAULT_CACHE_LIMIT = 1024 ;
 
     /** The maximum number of entries in the cache */
     private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
 
     
 
     /** Fast access cache for Views, returning already cached instances without a global lock */
     private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
 
     /** Map from view key to View instance, synchronized for View creation */
     @SuppressWarnings ( "serial" )
     private final Map<Object, View> viewCreationCache =
             new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0 .75f, true ) {
                 @Override
                 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
                     if (size() > getCacheLimit()) {
                         viewAccessCache.remove(eldest.getKey());
                         return true ;
                     }
                     else {
                         return false ;
                     }
                 }
             };

属性一:cacheLimit 最大的缓存数量,默认为1024。 
属性二:viewAccessCache 是ConcurrentHashMap类型的,适合高并发。 
属性三:viewCreationCache是LinkedHashMap类型的 
我们再来看下,由view名称来解析到view视图对象的具体过程:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public View resolveViewName(String viewName, Locale locale) throws Exception {
//这里进行了是否进行缓存的判断,即cacheLimit是否大于0
         if (!isCache()) {
                         //不进行缓存,始终每次都创建
             return createView(viewName, locale);
         }
         else {
                         //viewAccessCache viewCreationCache两者的key
             Object cacheKey = getCacheKey(viewName, locale);
             View view = this .viewAccessCache.get(cacheKey);
             if (view == null ) {
                 synchronized ( this .viewCreationCache) {
                     view = this .viewCreationCache.get(cacheKey);
                     if (view == null ) {
                         // Ask the subclass to create the View object.
                         view = createView(viewName, locale);
                         if (view == null && this .cacheUnresolved) {
                             view = UNRESOLVED_VIEW;
                         }
                         if (view != null ) {
                             this .viewAccessCache.put(cacheKey, view);
                             this .viewCreationCache.put(cacheKey, view);
                             if (logger.isTraceEnabled()) {
                                 logger.trace( "Cached view [" + cacheKey + "]" );
                             }
                         }
                     }
                 }
             }
             return (view != UNRESOLVED_VIEW ? view : null );
         }
     }

对于Object cacheKey = getCacheKey(viewName, locale);默认为viewName + "_" + locale; 
但是可以被子类覆盖,子类UrlBasedViewResolver覆盖了它,变成只有viewName。 

先从viewAccessCache中看能否找到已缓存的view视图,若能找到则返回。若未找到则加上同步锁synchronized (this.viewCreationCache),进入这个方法之后,最关键的是仍需要进行一次判断view = this.viewCreationCache.get(cacheKey),看看是否已经创建过了,并不是viewAccessCache和viewCreationCache他们所缓存的内容不一样而是如果没有这个判断,则会有多线程问题。 

如线程1和线程2同时要解析相同的view名称,他们都来到同步锁synchronized (this.viewCreationCache)之前,线程2先拿到锁,线程1等待,线程2创建好view视图后,加入viewCreationCache和viewAccessCache,并释放锁。此时线程1获得锁,进入同步锁synchronized (this.viewCreationCache)内部,若不进行判断,则线程1又会去创建一次view视图。所以view = this.viewCreationCache.get(cacheKey)并判断view是否为null这一步骤是十分有用的。 

创建View视图的任务就交给了子类来实现。resolveViewName这个方法基本上就分析完了,应该还会想到,它的那个cacheLimit限制好像还没发挥出作用。 
继续回看
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
 
     private final Map<Object, View> viewCreationCache =
             new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0 .75f, true ) {
                 @Override
                 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
                     if (size() > getCacheLimit()) {
                         viewAccessCache.remove(eldest.getKey());
                         return true ;
                     }
                     else {
                         return false ;
                     }
                 }
             };

viewCreationCache 的类型是LinkedHashMap,但是它复写了protected boolean removeEldestEntry(Map.Entry<Object, View> eldest)方法,当该方法返回true时,LinkedHashMap则会删除最老的key。在这里我们可以看到,当viewCreationCache 的所存的View数量达到cacheLimit时,就会删除最老的那个key和value,同时也会使viewAccessCache删除这个key和value。 

viewAccessCache主要是用来高并发的访问,viewCreationCache 则是用来统计最老的key。他们所存储的view都是一样的。 

相关文章
|
7月前
|
前端开发 Java API
SpringMVC注解完全解析(上)
SpringMVC注解完全解析(上)
|
7月前
|
存储 前端开发 Java
SpringMVC注解完全解析(下)
SpringMVC注解完全解析(下)
|
设计模式 前端开发 JavaScript
SpringMVC实战入门教程,四天快速搞定springmvc框架!
SpringMVC 也叫Spring web mvc。是Spring 框架的一部分,是在Spring3.0 后发布的。 这里对SpringMVC框架进行一个简单的介绍: • springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合。 • springmvc是一个基于mvc的web框架。 • springmvc 表现层:方便前后端数据的传输 • Spring MVC 拥有控制器,作用跟Struts类似,接收外部请求,解析参数传给服务层 MVC是指,C控制层,M模块层,V显示层这样的设计理念,而SSM框架里面SPRING MVC本身就是MVC框架,
238 0
|
Java 容器 Spring
SpringMVC学习(十一):注解配置SpringMVC
使用配置类和注解代替web.xml和SpringMVC配置文件的功能
112 0
|
Java Spring 容器
SpringMVC注解开发入门案例
SpringMVC注解开发入门案例
SpringMVC注解开发入门案例
|
Java 数据库 网络架构
SpringMVC中常用注解(案例讲解)
SpringMVC中常用注解(案例讲解)
SpringMVC中常用注解(案例讲解)
springMVC源码探索之RequestMappingHandlerMapping
springMVC源码探索之RequestMappingHandlerMapping
239 0
|
前端开发 数据安全/隐私保护 网络架构
SpringMVC的拦截器(十四)中
SpringMVC的拦截器(十四)
117 0
SpringMVC的拦截器(十四)中
|
前端开发
SpringMVC的拦截器(十四)下
SpringMVC的拦截器(十四)
134 0
SpringMVC的拦截器(十四)下
|
前端开发 fastjson 数据安全/隐私保护
SpringMVC的拦截器(十四)上
SpringMVC的拦截器(十四)
116 0
SpringMVC的拦截器(十四)上