PlaceholderConfigurerSupport
它是一个抽象类,抽象基类,抽象了bean定义属性值中的占位符解析的功能,它继承自PropertyResourceConfigurer。从此抽象类命名就能看出,它的子类们肯定都和Placeholder处理占位符有关。
它的父类已经定义了后置处理阶段对容器中所有bean定义属性进行处理。PlaceholderConfigurerSupport则进一步的约定了要处理的占位符形式。同时提供了进行处理所需的一些属性(占位符前后缀等),以及一些工具方法。
实现子类会从一个属性源org.springframework.core.env.PropertySource里"拉取(pull)"属性值,然后替换处理Bean
// 它实现了BeanFactoryAware 接口 所以它知道自己的容器是谁。 public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer implements BeanNameAware, BeanFactoryAware { // 这三个符号已经非常熟悉了~ 参考:AbstractPropertyResolver public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; public static final String DEFAULT_VALUE_SEPARATOR = ":"; protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; @Nullable protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; // 默认不trim(其实我建议trim 否则太容器出错了) protected boolean trimValues = false; @Nullable protected String nullValue; protected boolean ignoreUnresolvablePlaceholders = false; @Nullable private String beanName; @Nullable private BeanFactory beanFactory; ... // 生路所有的get/set方法~ // 它并没有直接实现父类的方法processProperties,而是提供了这个do方法供子类使用~ // 注意此处入参:要求传入一个StringValueResolver~ protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) { // BeanDefinitionVisitor:遍历Bean的各个属性,用properties填充 // 它会将替换的操作委托给内部的一个StringValueResolver来执行 // 关于StringValueResolver这个上篇博文有详细讲解,出门右拐就到~ // 此处是唯一使用BeanDefinitionVisitor这个类的地方~~~~ BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); // 获取容器中所有bean的名称 String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); for (String curName : beanNames) { // Check that we're not parsing our own bean definition, // to avoid failing on unresolvable placeholders in properties file locations. // 确定处理的Bean不是自己,且保证自己只处理自己所在bean工厂里面的bean定义们~~别的工厂关我啥事呢~ if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); try { // 对bean定义bd进行属性值占位符解析 visitor.visitBeanDefinition(bd); } catch (Exception ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); } } } // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. // 使用StringValueResolver处理一下别名~ beanFactoryToProcess.resolveAliases(valueResolver); // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. // 把此处理器也加入到Bean工厂里吧~~~~ 赋能bean工厂 使用过的处理器都加进来 beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); } }
缺省的占位符格式为${…},遵从Ant/Log4J/JSP EL风格
PlaceholderConfigurerSupport仍未对父类PropertyResourceConfigurer定义的抽象方法processProperties提供实现。换句话讲,如果想使用PlaceholderConfigurerSupport的能力的话,需要继续提供实现子类。
其实PlaceholderConfigurerSupport它的子类们它最多的应用场景是处理这种配置场景:
<bean id="dataSourceDefault" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean>
启动容器时,初始化bean时,${key}就会替换成properties文件中的值。一般应用于基于xml配置中~
下面介绍Spring框架最常用的两种处理Properties文件的实现类:PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer。
Spring 对于Properties的操作都是分别基于上面两个类,而且两个类的实现方式是不一样的。(当然还有上面说的PropertiesFactoryBean,但它的方式不太一样~~)
PropertyPlaceholderConfigurer(重要)
这个类应该是N多个小伙伴最初使用Spring时候最初接触到的类。做个粗略的介绍;比如:
<context:property-placeholder location="classpath:jdbc.properties"/>
它的原理就是使用的PropertyPlaceholderConfigurer。当然我们也可以自己定义一个(或者多个)这个Bean。
// @since 02.10.2003 第一版就有了 public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport { public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0; public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1; public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2; //constants它把上面那三个常量 private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class); // 默认的mode模式:Check system properties if not resolvable in the specified properties. private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK; // spring.getenv.ignore=true就不会再去系统属性找了,否则没有找到还会去systemEnv里面去找的~~~~ 默认是会去找的 private boolean searchSystemEnvironment = !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME); // 使用constants的好处就是,让string能像类似枚举一样使用~ public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException { this.systemPropertiesMode = constants.asNumber(constantName).intValue(); } public void setSystemPropertiesMode(int systemPropertiesMode) { this.systemPropertiesMode = systemPropertiesMode; } // 处理占位符~~~~ 值都从Properties里来 可以指定systemPropertiesMode // placeholder比如:${user.home} @Nullable protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { String propVal = null; if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) { propVal = resolveSystemProperty(placeholder); } if (propVal == null) { propVal = resolvePlaceholder(placeholder, props); } // 是否fallback继续去 System.getProperty和System.getenv if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) { propVal = resolveSystemProperty(placeholder); } return propVal; } @Nullable protected String resolvePlaceholder(String placeholder, Properties props) { return props.getProperty(placeholder); } // 去系统里拿。含有System或者SystemEnv env是可选的 但默认是true @Nullable protected String resolveSystemProperty(String key) { try { String value = System.getProperty(key); if (value == null && this.searchSystemEnvironment) { value = System.getenv(key); } return value; } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Could not access system property '" + key + "': " + ex); } return null; } } // 这个是实现父类的方法~~~~~~~~~~~~~~~~~~~~~~ // 此处StringValueResolver是用的是PlaceholderResolvingStringValueResolver 它是一个内部类 // 内部列实现的这个没有EmbeddedValueResolver强大,它不支持SpEL,只是从Properties里取值而已~~~ @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); doProcessProperties(beanFactoryToProcess, valueResolver); } }
给一个示例Demo:
@Configuration public class RootConfig { @Bean public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer1() { PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setOrder(Ordered.HIGHEST_PRECEDENCE); configurer.setLocation(new ClassPathResource("beaninfo.properties")); return configurer; } @Scope("${bean.scope}") // 这里是能够使用占位符的 @Bean //@Bean("${bean.beanName}") 注意这里是不能解析的 public Person person(){ Person person = new Person(); return person; } }
其实,这个在注解驱动的应用中,使用场景已经非常少了,在xml时代异常的重要。
需要注意的是:若你配置了多个PropertyPlaceholderConfigurer,请设置它的Order属性来控制顺序(因为它是Bean工厂后置处理器)
但是强烈建议只配置一个即可,毕竟每次它都拿所有的Bean处理,配置多个会拖慢启动速度(不同容器内除外,需要多个~)
注意,注意,注意:这种加载配置文件的方式,应用启动完就"消失了",并不会留存在Environment里的,请务必注意。
所以为了保存下他们,我们之前经常可看到如下代码:
public class PropertyPlaceholder extends PropertyPlaceholderConfigurer { private static Map<String,String> propertyMap; @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { super.processProperties(beanFactoryToProcess, props); propertyMap = new HashMap<String, String>(); for (Object key : props.keySet()) { String keyStr = key.toString(); String value = props.getProperty(keyStr); propertyMap.put(keyStr, value); } } //自定义一个方法,即根据key拿属性值,方便java代码中取属性值 public static String getProperty(String name) { return propertyMap.get(name); } }
这个自定义的PropertyPlaceholder的作用就是把配置都保存下来,并提供static方法供后续获取使用。
另外PropertyPlaceholderConfigurer有个子类–>PreferencesPlaceholderConfigurer。它是对父类的增强,它能解决如下两个可能问题:
- 配置文件不能放在主目录中,因为某些OS(如Win9X)没有主目录的概念
- 没有标准的文件命名规则,存在文件名冲突的可能性
使用java.util.prefs.Preferences能解决这个问题。因为绝大多数情况下我们的配置文件都是跟着项目走的,所以使用PropertyPlaceholderConfigurer也没问题,但是仅尽显逼格可完全使用子类PreferencesPlaceholderConfigurer,它拥有父类所有功能且更加强大~
PropertySourcesPlaceholderConfigurer
在Spring3.1之后,我建议使用PropertySourcesPlaceholderConfigurer来取代PropertyPlaceholderConfigurer。
因为汇聚了Environment、多个PropertySource;所以它能够控制取值优先级、顺序,并且还提供了访问的方法,后期再想获取也不成问题
// @since 3.1 实现了EnvironmentAware 接口可以得到自己所在的环境 public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware { // 本地的 public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties"; // 环境信息 public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties"; // 这三个哥们 到此处应该已经非常熟悉了 @Nullable private MutablePropertySources propertySources; // 注意此处:它只表示当前的环境持有的~~~~ @Nullable private PropertySources appliedPropertySources; @Nullable private Environment environment; // 当前bean所处的环境~ // 显然,~~并不建议直接set public void setPropertySources(PropertySources propertySources) { this.propertySources = new MutablePropertySources(propertySources); } // 此处:它完全重写了Bean工厂后置处理器的处理方法~~~~~ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 若propertySources还没构造,就先构造一个出来~~~~~ if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { // 此处把当前环境都放进去了,所以占位符可以使用当前环境Environment内的任何key了~~~~ this.propertySources.addLast( new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable public String getProperty(String key) { return this.source.getProperty(key); } } ); } try { // 把本地的也作为一个source加进去 注意此处可能是addFirst和addLast~~~ // key为:localProperties PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties()); if (this.localOverride) { this.propertySources.addFirst(localPropertySource); } else { this.propertySources.addLast(localPropertySource); } } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } } processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources)); // 表示最终生效的 propertySources this.appliedPropertySources = this.propertySources; } protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException { // 设置ConfigurablePropertyResolver的几大参数~~~ propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); propertyResolver.setValueSeparator(this.valueSeparator); // 使用lambda表达式创建一个StringValueResolver~~~~ StringValueResolver valueResolver = strVal -> { // 解析占位符~~~~~ 此处只能解析占位符 String resolved = (this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal)); if (this.trimValues) { resolved = resolved.trim(); } // 返回null还是返回resolved 最后还得有个判断 return (resolved.equals(this.nullValue) ? null : resolved); }; // 调用父类的doProcessProperties 把属性扫描到Bean的身上去~~~ // 并且我们发现 我们自定义的EmbeddedValueResolver是会被添加到bean工厂里面的 doProcessProperties(beanFactoryToProcess, valueResolver); } @Override @Deprecated protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) { throw new UnsupportedOperationException( "Call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver) instead"); } // 外部可以获取到appliedPropertySources生效的 这个相当于把生效的给我们缓存下来了 我们并不需要自己再缓存了~~~~ // 你若需要 可以把它放进环境了(不建议) public PropertySources getAppliedPropertySources() throws IllegalStateException { Assert.state(this.appliedPropertySources != null, "PropertySources have not yet been applied"); return this.appliedPropertySources; } }