本章再學習另外兩個ViewResolver,分別是XmlViewResolver和ResourceBundleViewResolver,從功能上說,這兩個視圖解析器都是從外部資源文件中查找視圖View對象,因此放在一章學習。web
本系列文章是基於Spring5.0.5RELEASE。spring
該類繼承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
與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的核心代碼。
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>
啓動測試,能夠正常進行渲染。
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視圖解析器,回想一下,可劃分爲三部分:
關於視圖解析器,就分析到這,但願對你們有幫助,謝謝!
最後建立了qq羣方便你們交流,可掃描加入,同時也可加我qq:276420284,共同窗習、共同進步,謝謝!