SpringMVC之源码分析--ViewResolver(六)

概述

本章再学习另外两个ViewResolver,分别是XmlViewResolver和ResourceBundleViewResolver,从功能上说,这两个视图解析器都是从外部资源文件中查找视图View对象,所以放在一章学习。

本系列文章是基于Spring5.0.5RELEASE。

XmlViewResolver

该类继承AbstractCachingViewResolver抽象类,也就是该解析器支持视图缓存。XmlViewResolver通过使用额外的xml配置文件来定义视图对象,xml配置文件默认加载/WEB-INF/views.xml,可通过location属性参数重置加载文件。

配置文件中定义视图对象,并指定bean名称(id或name),该名称与Controller处理器中返回的逻辑视图名称对应,从而通过url指定的路径找到真正的视图进行渲染。

源码如下:

public class XmlViewResolver extends AbstractCachingViewResolver
    implements Ordered, InitializingBean, DisposableBean {

    /** 默认加载的视图配置文件 */
    public static final String DEFAULT_LOCATION = "/WEB-INF/views.xml";
    /** 指定视图配置文件路径 */
    @Nullable
    private Resource location;
    /** 如果开启缓存(cacheLimit>0),bean工厂缓存在该属性 */
    @Nullable
    private ConfigurableApplicationContext cachedFactory;
    /** 视图解析器排序 */
    private int order = Ordered.LOWEST_PRECEDENCE;  

    ... 省略get/set方法 ...

    /** 启动时调用 */
    @Override
    public void afterPropertiesSet() throws BeansException {
        // 开启缓存(cacheLimit>0)时,在应用启动时创建bean工厂
        if (isCache()) {
            initFactory();
        }
    }
    
    /** 返回视图名称,在父类AbstractCachingViewResolver的resolveViewName方法中调用 */
    @Override
    protected Object getCacheKey(String viewName, Locale locale) {
        return viewName;
    }
    
    /** 
     *根据视图名称加载View视图 
     */
    @Override
    protected View loadView(String viewName, Locale locale) throws BeansException {
        // 创建bean工厂
        BeanFactory factory = initFactory();
        try {
            // 根据controller返回的逻辑视图名(视图名称与bean名称对应)查找视图对象
            return factory.getBean(viewName, View.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Allow for ViewResolver chaining...
            return null;
        }
    }

    /** 创建bean工厂 */
    protected synchronized BeanFactory initFactory() throws BeansException {
        // 如果启用缓存,第二次直接返回
        if (this.cachedFactory != null) {
            return this.cachedFactory;
        }

        ApplicationContext applicationContext = obtainApplicationContext();

        Resource actualLocation = this.location;
        if (actualLocation == null) {
            actualLocation = applicationContext.getResource(DEFAULT_LOCATION);
        }

        // Create child ApplicationContext for views.
        GenericWebApplicationContext factory = new GenericWebApplicationContext();
        factory.setParent(applicationContext);
        factory.setServletContext(getServletContext());

        // Load XML resource with context-aware entity resolver.
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.setEnvironment(applicationContext.getEnvironment());
        reader.setEntityResolver(new ResourceEntityResolver(applicationContext));
        reader.loadBeanDefinitions(actualLocation);

        factory.refresh();
        // 启用缓存,赋值属性变量进行存储
        if (isCache()) {
            this.cachedFactory = factory;
        }
        return factory;
    }
    
}

以上是XmlViewResolver的核心代码。

ResourceBundleViewResolver

与XmlViewResolver一样,该类继承AbstractCachingViewResolver抽象类,并且通过外部的属性文件定义逻辑视图名称与真正的视图View对象的关系,属性文件默认是classpath下的views.properties,可以通过basename或basenames属性来指定,该属性指的是文件的基名称,也就是说以basename属性值开头的属性文件。

ResourceBundleViewResolver类具有缓存功能,即把 properties 文件中定义好的属性按照它自身的规则生成一个个的 bean 对象注册到该 BeanFactory 中,之后会把该 BeanFactory 对象保存起来,所以 ResourceBundleViewResolver 缓存的是 BeanFactory ,而不是直接的缓存从 BeanFactory 中取出的视图 bean。

Spring 通过 properties 文件生成 bean 的规则是把 properties 文件中定义的属性名称按最后一个点“ . ”进行分割,把点前面的内容当做是 bean 名称,点后面的内容当做是 bean 的属性。

源码如下:

public class ResourceBundleViewResolver extends AbstractCachingViewResolver
    implements Ordered, InitializingBean, DisposableBean {

    /** 配置文件的默认基名称,即以此开头的属性文件,默认从classpath路径查找加载 */
    public static final String DEFAULT_BASENAME = "views";
    /** 支持多文件加载 */
    private String[] basenames = new String[] {DEFAULT_BASENAME};
    private ClassLoader bundleClassLoader = Thread.currentThread().getContextClassLoader();
    
    @Nullable
    private String defaultParentView;
    @Nullable
    private Locale[] localesToInitialize;

    private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered
    
    /* Locale -> BeanFactory */
    private final Map<Locale, BeanFactory> localeCache = new HashMap<>();

    /* List of ResourceBundle -> BeanFactory */
    private final Map<List<ResourceBundle>, ConfigurableApplicationContext> bundleCache = new HashMap<>();
    
    /**********get/set**********/
    public void setBasename(String basename) {
        setBasenames(basename);
    }
    public void setBasenames(String... basenames) {
        this.basenames = basenames;
    }
    public void setBundleClassLoader(ClassLoader classLoader) {
        this.bundleClassLoader = classLoader;
    }
    protected ClassLoader getBundleClassLoader() {
        return this.bundleClassLoader;
    }
    public void setDefaultParentView(String defaultParentView) {
        this.defaultParentView = defaultParentView;
    }
    public void setLocalesToInitialize(Locale... localesToInitialize) {
        this.localesToInitialize = localesToInitialize;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Override
    public int getOrder() {
        return this.order;
    }

    /** 启动时调用,创建初始化bean工厂 */
    @Override
    public void afterPropertiesSet() throws BeansException {
        // localesToInitialize属性通过配置进行设置
        if (this.localesToInitialize != null) {
            for (Locale locale : this.localesToInitialize) {
                initFactory(locale);
            }
        }
    }
    
    /** 查找视图View,在父类AbstractCachingViewResolver的resolverViewName方法中调用 */
    @Override
    protected View loadView(String viewName, Locale locale) throws Exception {
        // 初始化bean工厂
        BeanFactory factory = initFactory(locale);
        try {
            return factory.getBean(viewName, View.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Allow for ViewResolver chaining...
            return null;
        }
    }

    protected synchronized BeanFactory initFactory(Locale locale) throws BeansException {

        // 开启缓存,通过cacheLimit属性大于0控制
        if (isCache()) {
            // localeCache属性map中缓存locale和beanFactory映射
            BeanFactory cachedFactory = this.localeCache.get(locale);
            if (cachedFactory != null) {
                return cachedFactory;
            }
        }

        // 创建ResourceBundle集合,支持多属性文件
        List<ResourceBundle> bundles = new LinkedList<>();
        for (String basename : this.basenames) {
            ResourceBundle bundle = getBundle(basename, locale);
            bundles.add(bundle);
        }

        // 开启缓存,
        if (isCache()) {
            BeanFactory cachedFactory = this.bundleCache.get(bundles);
            if (cachedFactory != null) {
                this.localeCache.put(locale, cachedFactory);
                return cachedFactory;
            }
        }

        // 创建视图ApplicationContext上下文
        GenericWebApplicationContext factory = new GenericWebApplicationContext();
        factory.setParent(getApplicationContext());
        factory.setServletContext(getServletContext());

        // 从资源文件中加载bean定义
        PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(factory);
        reader.setDefaultParentBean(this.defaultParentView);
        for (ResourceBundle bundle : bundles) {
            reader.registerBeanDefinitions(bundle);
        }

        factory.refresh();

        // 设置缓存
        if (isCache()) {
            this.localeCache.put(locale, factory);
            this.bundleCache.put(bundles, factory);
        }

        return factory;
    }

    protected ResourceBundle getBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, getBundleClassLoader());
    }

    @Override
    public void destroy() throws BeansException {
        for (ConfigurableApplicationContext factory : this.bundleCache.values()) {
            factory.close();
        }
        this.localeCache.clear();
        this.bundleCache.clear();
    }

}

以上是ResourceBundleViewResolver的核心代码。

实战

  • 使用XmlViewResolver

spring mvc配置文件中配置XmlViewResolver视图解析器,代码如下:

<!-- XmlViewResolver -->
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="location" value="classpath:/views.xml"/>
</bean>

在classpath下创建views.xml配置文件,代码如下:

<bean id="test" class="org.springframework.web.servlet.view.JstlView">
    <property name="url" value="/WEB-INF/jsp/test.jsp"/>
</bean>

启动测试,可以正常进行渲染。

  • 使用ResourceBundleViewResolver

spring mvc配置文件中配置ResourceBundlerViewResolver视图解析器,代码如下:

<!-- ResourceBundleViewResolver -->
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <!-- 支持多文件 -->
    <property name="basenames">
        <array>
            <!-- 在classpath下创建properties文件夹及相应文件 -->
            <value>properties/test</value>
            <value>properties/views</value>
        </array>
    </property>
    <!-- 单文件 -->
    <!--<property name="basename" value="properties/test"/>-->
    <!-- 关闭缓存 -->
    <property name="cacheLimit" value="0"/>
</bean>

test.properties配置如下:

// 配置视图View
test.(class)=org.springframework.web.servlet.view.InternalResourceView
// 对应真实视图url
test.url=/WEB-INF/jsp/test.jsp

views.properties配置与test一样。

启动测试,正常解析渲染。

总结

经过六章的分析,学习了ViewResolver视图解析器,回想一下,可划分为三部分:

  • 组合的ViewResolver,这部分是直接继承ViewResolver接口的,不具有缓存功能,包括像ViewResolverComposite、ContentNegotiatingViewResolver等
  • 基于url的Viewresolver,这部分包括UrlBasedViewResolver、InternalResourceViewResolver等
  • 基于外部文件的Viewresolver,包括XmlViewResolver、ResourceBundleViewResolver

关于视图解析器,就分析到这,希望对大家有帮助,谢谢!

最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!

SpringMVC之源码分析--ViewResolver(六)

相关推荐