SpringMVC 视图解析 - ViewResolver

基本概念

所有的 MVC 框架都有一套它自己的解析视图的机制,SpringMVC 也不例外。

它使用 ViewResolver 进行视图解析,让用户在浏览器中渲染模型。

ViewResolver 接口在视图名称和真正的视图之间提供映射,它是一种开箱即用的技术,能够解析 JSP、Velocity 模板和 XSLT 等多种视图:

视图解析器在 SpringMVC 中配置如下(以 InternalResourceViewResolver 为例 ):

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
    <property name="prefix" value="/WEB-INF/page/" />
    <property name="suffix" value=".jsp" />
</bean>
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • bean:表示指定的视图解析器

  • viewClass: 表示要解析的视图类型

  • prefix/suffix: 表示路径前缀/后缀,假设 ViewName 为 hello,则完整路径为 /WEB-INF/page/hello.jsp


内部构造

ViewResolver ,即视图解析器,该接口的主要作用就是根据视图名 (ViewName),返回视图 (View)。下面来它的源码:

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}
  
  
  • 1
  • 2
  • 3

再它的继承关系:

这里写图片描述


AbstractCachingViewResolver

该类是 ViewResolver 接口的抽象实现类,用来表示一个带有缓存功能的视图解析器。

下面来看它的 resolveViewName:

// 成员变量
private boolean cacheUnresolved = true;

public View resolveViewName(String viewName, Locale locale) throws Exception {

    // 1.判断缓存是否打开
    if (!isCache()) {
        // 2.缓存关闭,则创建视图
        return createView(viewName, locale);
    } else {

        // 3.1.缓存开启,从 viewAccessCache 取出视图
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);

        // 3.2.为空,从 viewCreationCache 取出视图
        if (view == null) {
            synchronized (this.viewCreationCache) {
                // 3.3.仍然为空,则创建视图
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    view = createView(viewName, locale);

                    // 3.3.1.仍然为空,标记为[不可解析视图]
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }

                    // 3.3.2.不为空,添加进缓存
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        // 省略部分代码...
                    }
                }
            }
        }

        return ( view != UNRESOLVED_VIEW ? view : 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
  • 39
  • 40
  • 41

再来看看它的 createView 方法,该方法负责视图的创建。

protected View createView(String viewName, Locale locale) throws Exception {
    // 空方法
    return loadView(viewName, locale);
}
  
  
  • 1
  • 2
  • 3
  • 4

观察代码,可以该类的视图解析过程如下:

  • 缓存关闭,则创建视图。

  • 缓存开启,则步骤如下:

    • 依次从 viewCreationCache,viewCreationCache 缓存中查找。
    • 存在,则返回视图;不存在,则创建视图。
    • 创建视图完再次判断,为空则标记为不可解析视图,否则添加到两个缓存。

UrlBasedViewResolver

继承自 AbstractCachingViewResolver 抽象类、并实现 Ordered 接口的类,是 ViewResolver 接口简单的实现类,该类重写了 createView、loadView 这两个方法。

首先来看它的签名:

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered
  
  
  • 1

1.createView

public static final String REDIRECT_URL_PREFIX = "redirect:";
public static final String FORWARD_URL_PREFIX = "forward:";

protected View createView(String viewName, Locale locale) throws Exception {

    // 是否支持该视图的名的处理
    if (!canHandle(viewName, locale)) {
        return null;
    }

    // 判断视图名是否以 redirect: 开头
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {

        // 获取真正的视图名
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());

        RedirectView view = new RedirectView(redirectUrl,
            isRedirectContextRelative(), isRedirectHttp10Compatible());

        return applyLifecycleMethods(viewName, view);
    }

    // 判断视图名是否以 forward: 开头
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());

        // 返回视图
        return new InternalResourceView(forwardUrl);
    }

    // 其他情况由父类处理,其实就是调用该类的 loadView 方法
    return super.createView(viewName, locale);
}
  
  
  • 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

再来看看 applyLifecycleMethods 方法:

private View applyLifecycleMethods(String viewName, AbstractView view) {
    // 从 SpringMVC 容器中手动获取 Bean
    return (View) getApplicationContext().getAutowireCapableBeanFactory().
        initializeBean(view, viewName);
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5

2.loadView

再来看看它 的 loadView 方法,如果视图名不包含 redirect,forward 时,调用父类的 createView 的时会调用到它。

protected View loadView(String viewName, Locale locale) throws Exception {
    // 创建 View
    AbstractUrlBasedView view = buildView(viewName);

    // 从 SpringMVC 容器中获取 View
    View result = applyLifecycleMethods(viewName, view);

    return ( view.checkResource(locale) ? result : null );
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

再来看看 buildView 方法:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {

    // 1.创建 View ,利用反射实例类
    AbstractUrlBasedView view = 
        (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());

    // 2.设置 View 的 url
    view.setUrl(getPrefix() + viewName + getSuffix());

    // 3.设置 View 的 contentType  
    String contentType = getContentType();
    if (contentType != null) {
        view.setContentType(contentType);
    }

    // 4.设置 View 的其他相关属性

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());

    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }

    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }

    String [ ] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return 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
  • 33
  • 34
  • 35
  • 36
  • 37

实例探究

利用 ContentNegotiatingViewResolver 集成多种视图

  • 首先来看配置文件(这里集成 vm,jsp,json,xml 这四种视图显示)
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <!-- 内容协商管理器 -->
    <property name="contentNegotiationManager"  ref="contentNegotiationManager"/>
    <!-- 视图解析器  -->
    <property name="viewResolvers">
        <list>
            <!--Veocity 视图解析器  -->
            <bean class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
                <property name="order" value="0"/>
                <property name="cache" value="false" />
                <property name="suffix" value=".vm" />
            </bean>
            <!--JSP 视图解析器  -->
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
                <property name="prefix" value="/WEB-INF/page/"/>
                <property name="suffix" value=".jsp"></property>
            </bean>
        </list>
    </property>

    <!-- 默认视图 -->
    <property name="defaultViews">
        <list>
            <!-- json 视图解析 -->
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
            <!-- xml 视图解析  -->
            <bean class="org.springframework.web.servlet.view.xml.MarshallingView" >
                <property name="marshaller">
                    <bean class="org.springframework.oxm.xstream.XStreamMarshaller"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

<bean id="contentNegotiationManager"  class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <!-- 根据请求参数或拓展名映射到相应的MIME类型 -->
    <property name="mediaTypes">
        <map>
            <entry key="json"  value="application/json"/>
            <entry key="xml"  value="application/xml"/>
        </map>
    </property>
    <!-- 设置默认的MIME类型,如果没有指定拓展名或请求参数,则使用此默认MIME类型解析视图 -->
    <property name="defaultContentType"  value="text/html"/> 
    <!-- 是否不适用请求头确定MIME类型 -->
    <property name="ignoreAcceptHeader"  value="true"/> 
    <!-- 是否根据路径拓展名确定MIME类型 -->
    <property name="favorPathExtension"  value="true"/> 
     <!-- 是否使用参数来确定MIME类型 -->
    <property name="favorParameter" value="false" /> 
</bean>

<!-- Veocity 模版配置 -->
<bean class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="configLocation" value="/WEB-INF/velocity.properties" />
</bean>
  
  
  • 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
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • Velocity 属性文件配置
input.encoding=UTF-8
output.encoding=UTF-8
contentType=text/html;charset=UTF-8  
resource.loader=webapp
webapp.resource.loader.class=org.apache.velocity.tools.view.WebappResourceLoader
webapp.resource.loader.path=/WEB-INF/velocity/
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 测试
// 先用 velocity 视图解析(order = 0),然后再用 jsp 视图解析
http://localhost:8080/demo/hello

// 返回 json 视图
http://localhost:8080/demo/hello.json

// 返回 xml 视图
http://localhost:8080/demo/hello.xml
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意:

  • 这里利用路径扩展名(favorPathExtension)区分 MIME 类型,以此确定要返回的视图类型。也可以利用参数(默认为 fomart)来区分。

  • VelocityViewResolver(velocity 视图解析)的 order 优先级必须比 InternalResourceViewResolver(jsp 视图解析器)高。因为 InternalResourceViewResolver 不管路径下是否存在指定 jsp 文件都会返回 View(视图),这样会导致 VelocityViewResolver 不生效。


参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值