了不起的厂长 2019-06-27
本章再学习另外两个ViewResolver,分别是XmlViewResolver和ResourceBundleViewResolver,从功能上说,这两个视图解析器都是从外部资源文件中查找视图View对象,所以放在一章学习。
本系列文章是基于Spring5.0.5RELEASE。
该类继承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的核心代码。
与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的核心代码。
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,共同学习、共同进步,谢谢!