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

概述

本章再學習另外兩個ViewResolver,分別是XmlViewResolver和ResourceBundleViewResolver,從功能上說,這兩個視圖解析器都是從外部資源文件中查找視圖View對象,因此放在一章學習。web

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

XmlViewResolver

該類繼承AbstractCachingViewResolver抽象類,也就是該解析器支持視圖緩存。XmlViewResolver經過使用額外的xml配置文件來定義視圖對象,xml配置文件默認加載/WEB-INF/views.xml,可經過location屬性參數重置加載文件。緩存

配置文件中定義視圖對象,並指定bean名稱(id或name),該名稱與Controller處理器中返回的邏輯視圖名稱對應,從而經過url指定的路徑找到真正的視圖進行渲染。mvc

源碼以下:app

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的核心代碼。jsp

ResourceBundleViewResolver

與XmlViewResolver同樣,該類繼承AbstractCachingViewResolver抽象類,而且經過外部的屬性文件定義邏輯視圖名稱與真正的視圖View對象的關係,屬性文件默認是classpath下的views.properties,能夠經過basename或basenames屬性來指定,該屬性指的是文件的基名稱,也就是說以basename屬性值開頭的屬性文件。ide

ResourceBundleViewResolver類具備緩存功能,即把 properties 文件中定義好的屬性按照它自身的規則生成一個個的 bean 對象註冊到該 BeanFactory 中,以後會把該 BeanFactory 對象保存起來,因此 ResourceBundleViewResolver 緩存的是 BeanFactory ,而不是直接的緩存從 BeanFactory 中取出的視圖 bean。學習

Spring 經過 properties 文件生成 bean 的規則是把 properties 文件中定義的屬性名稱按最後一個點「 . 」進行分割,把點前面的內容當作是 bean 名稱,點後面的內容當作是 bean 的屬性。測試

源碼以下:this

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,共同窗習、共同進步,謝謝!

相關文章
相關標籤/搜索