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都是一样的。 

时间: 2024-10-14 19:17:52

SpringMVC源码总结(十二)ViewResolver介绍的相关文章

Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置,其实Ubuntu Kylin 16.04 LTS也只是为了体验,我们为了追求稳定,还是使用了Ubuntu14.04 这里提供一个国内镜像的下载链接,可以用迅雷,下载下来之后后缀

SpringMVC源码解读之HandlerMapping - AbstractUrlHandlerMapping系列request分发_java

AbstractHandlerMapping实现HandlerMapping接口定的getHandler 1. 提供getHandlerInternal模板方法给子类实现      2. 如果没有获取Handler,则使用默认的defaultHandler 3. 如果handler是string类型,从context获取实例 4. 通过getHandlerExecutionChain封装handler,添加interceptor // AbstractHandlerMapping /** * L

SpringMVC源码解读之 HandlerMapping - AbstractDetectingUrlHandlerMapping系列初始化_java

 AbstractDetectingUrlHandlerMapping是通过扫描方式注册Handler,收到请求时由AbstractUrlHandlerMapping的getHandlerInternal进行分发. 共有5个子类,一个抽象类. 与SimpleUrlHandlerMapping类似,通过覆写initApplicationContext,然后调用detectHandlers进行初始化. detectHandlers通过BeanFactoryUtils扫描应用下的Object,然后预留

SpringMVC源码解析- HandlerAdapter - ModelFactory(转)

ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中   我们来看看具体的处理逻辑(直接充当分析目录): 1. 初始化model 1.1 解析类上使用的sessionAttributres,将获取参数合并到mavContainer中 1.2 执行注解了@ModelAttribute的方法,并将结果同步到Model 参数名的生成规则:@ModelAttribute中定义的value > 方法的返回类型决

Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

一.综述       HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作.       首先上一段代码,客户端是如何写文件的: Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); Path file = new Path("demo.txt"); FSDataOutputStream

SpringMVC源码解读之HandlerMapping_java

概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根据请求url到控制器的映射(HandlerMapping中保存),HandlerMapping最终返回HandlerExecutionChain,其中包含了具体的处理对象handler(也即我们编程时写的controller)以及一系列的拦截器interceptors,此时DispatcherServlet会根据返

SpringMVC源码总结(十)自定义HandlerMethodArgumentResolver

上一篇文章介绍了HandlerMethodArgumentResolver的来龙去脉,这篇就要说说自定义HandlerMethodArgumentResolver来解决我们的需求,本文提供了四种解决方案.  需求,有一个Teacher类和Student类,他们都有属性name和age:  前端form表单为:  ? 1 2 3 4 5 6 7 <form action="/test/two" method="post" >             <

Netty源码解读(二)Netty中的buffer

感谢网友[黄亿华]投递本稿. 上一篇文章我们概要介绍了Netty的原理及结构,下面几篇文章我们开始对Netty的各个模块进行比较详细的分析.Netty的结构最底层是buffer模块,这部分也相对独立,我们就先从buffer讲起. What: buffer二三事 buffer中文名又叫缓冲区,按照维基百科的解释,是"在数据传输时,在内存里开辟的一块临时保存数据的区域".它其实是一种化同步为异步的机制,可以解决数据传输的速率不对等以及不稳定的问题. 根据这个定义,我们可以知道涉及I/O(特

SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门

刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊,最近花了一点时间稍微看了下源代码,再加上调试,开始逐渐理解它,网上的类似的内容有很多,写本文主要是自己加深一下理解.本文适合用过SpringMVC的开发者,言归正传,首先搭建一个最简单的工程体验一下.  该工程是基于maven的,pom配置不再说明,所使用的spring版本4.0.5.  首先是web.xml文件配置,最简单的配置  ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCT

解析xHTML源码的DLL组件AngleSharp介绍_实用技巧

AngleSharp是基于.NET(C#)开发的专门为解析xHTML源码的DLL组件. 项目地址:https://github.com/FlorianRappl/AngleSharp 国内:Jumony github地址: https://github.com/Ivony/Jumony 国外:Html Agility Pack 项目地址:http://htmlagilitypack.codeplex.com/ 具体大家可以自行搜索对比三者的区别和性能.接下来咱们主要讨论主角是AngleSharp