PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用
Spring的PropertyResourceConfigurer是个抽象类,继承自PropertiesLoaderSupport,并实现了接口BeanFactoryPostProcessor。
注意:它是个Bean工厂的后置处理器,而不是Bean的后置处理器
它抽象了容器启动时,BeanFactory后置处理阶段对容器中所有bean定义中的属性进行配置的一般逻辑,属性配置所使用的属性来源是基类PropertiesLoaderSupport方法所规定的那些属性。
PropertiesLoaderSupport
org.springframework.core.io.support.PropertiesLoaderSupport是一个抽象基类,它抽象了从不同渠道加载属性的通用逻辑,以及这些属性应用优先级上的一些考虑,它所提供的这些功能主要供实现子类使用。
它将属性分成两类:
- 本地属性(也叫缺省属性):直接以Properties对象形式设置进来的属性
- 外来属性:通过外部资源Resource形式设置进来需要加载的那些属性
对于本地属性和外来属性之间的的使用优先级,通过属性localOverride来标识。如果localOverride为false,表示外部属性优先级高,这也是缺省设置。如果localOverride为true,表示本地属性优先级高。它还有一个属性fileEncoding用来表示从属性文件加载属性时使用的字符集。
// @since 1.2.2
public abstract class PropertiesLoaderSupport {
@Nullable
protected Properties[] localProperties;
protected boolean localOverride = false;
// 外部配置 Resource[]
@Nullable
private Resource[] locations;
private boolean ignoreResourceNotFound = false;
@Nullable
private String fileEncoding;
// 从流中读取 Properties / xml // 外来属性加载工具
private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
public void setProperties(Properties properties) {
this.localProperties = new Properties[] {properties};
}
public void setPropertiesArray(Properties... propertiesArray) {
this.localProperties = propertiesArray;
}
public void setLocation(Resource location) {
this.locations = new Resource[] {location};
}
public void setLocations(Resource... locations) {
this.locations = locations;
}
public void setLocations(Resource... locations) {
this.locations = locations;
}
//... // 省略get/set
// 下面是提供给子类实现的方法们~~~~~~~~~
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();
// localOverride默认是false 若是true,提前先加载外部化配置
if (this.localOverride) {
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}
if (this.localProperties != null) {
for (Properties localProp : this.localProperties) {
// 厉害了 属性合并 把Properties合并进map localProp本地会覆盖掉后面的result~~
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
// 若是false,再装载一次 把它放进result里面~~~外部配置覆盖当前的result嘛~
if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
// 加载外部配置~~~~~~~ 从Resource读取进来~ 借助的还是PropertiesLoaderUtils
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
}
}
}
}
对于该类的相关属性,都提供了对应的set方法。
PropertiesLoaderSupport所实现的功能并不多,主要是设置要使用的本地属性和外部属性文件资源路径,最终通过mergeProperties方法将这些属性合并成一个Properties对象,本地属性和外部属性之间的优先级关系由属性localOverride决定。
PropertiesLoaderSupport的直接实现子类有PropertiesFactoryBean和PropertyResourceConfigurer。
PropertiesFactoryBean
实现了FactoryBean,用来生产Properties,可以配置是否单例(默认是单例)。
public class PropertiesFactoryBean extends PropertiesLoaderSupport
implements FactoryBean<Properties>, InitializingBean {
// 默认它是单例的
private boolean singleton = true;
@Nullable
private Properties singletonInstance;
public final void setSingleton(boolean singleton) {
this.singleton = singleton;
}
...
// 只有singleton为true时候,才会创建一个缓存着~~~
@Override
public final void afterPropertiesSet() throws IOException {
if (this.singleton) {
this.singletonInstance = createProperties();
}
}
// 通过合并本地属性 来得到一个Properties~~~~
protected Properties createProperties() throws IOException {
return mergeProperties();
}
@Override
@Nullable
public final Properties getObject() throws IOException {
if (this.singleton) {
return this.singletonInstance;
}
else {
return createProperties();
}
}
@Override
public Class<Properties> getObjectType() {
return Properties.class;
}
}
在我们还是xml时代的时候,我们其中一种导入配置文件的方式如下:
<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations"><!-- 这里是PropertiesFactoryBean类,它也有个locations属性,也是接收一个数组,跟上面一样 -->
<array>
<value>classpath:jdbc.properties</value>
</array>
</property>
</bean>
此处直接是向容器内放入了一个Bean,而这个Bean就是Properties类型,显然这种方式并不会加入到环境Environment里面去。所以我们在使用@Value引用的时候比如使用SpEL才能引用到:
@Value("#{prop['datasource.url']}") // 用@Value("${datasource.url}") 这样是读取不到的 此处务必要注意
其实xml时代还有一种常见的引用配置文件的方式如下:(Spring加载properties文件的两种方式)
<context:property-placeholder location="classpath:jdbc.properties"/>
它的原理其实是PropertyPlaceholderConfigurer,下面会说到。它等价于下面这么配置:
<!-- 与上面的配置等价,下面的更容易理解 这个beanName写不写无所谓 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations"> <!-- PropertyPlaceholderConfigurer类中有个locations属性,接收的是一个数组,即我们可以在下面配好多个properties文件 -->
<array>
<value>classpath:jdbc.properties</value>
</array>
</property>
</bean>
这种方式虽然也并没有加入到Environment环境抽象里面去,但我们取值仍然可以如下取值:
@Value("${datasource.url}") // 占位符取值即可
下面以Java配置方式示例,使用PropertiesFactoryBean加载属性配置文件:
@Configuration
public class RootConfig {
@Bean
public PropertiesFactoryBean prop() {
PropertiesFactoryBean prop = new PropertiesFactoryBean();
prop.setLocation(new ClassPathResource("jdbc.properties"));
return prop;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Value("#{prop['datasource.url']}")
private String url;
@Test
public void test1() {
System.out.println(url); //jdbc:mysql://localhost:3306/jedi?zeroDateTimeBehavior=convertToNull
}
}
PropertyResourceConfigurer
允许从属性资源(即属性文件)配置单个bean属性值。对于以系统管理员为目标的自定义配置文件很有用,这些文件覆盖在应用程序上下文中配置的bean属性。
它是个抽象类,它的继承图谱如下:
它实现了BeanFactoryPostProcessor并且还实现了PriorityOrdered表示它的优先级是非常高的。
// @since 02.10.2003
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
// 处理每个Bean~~~
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// 合并本地属性和外部指定的属性文件资源中的属性
Properties mergedProps = mergeProperties();
// 将属性的值做转换(仅在必要的时候做)
convertProperties(mergedProps);
// 对容器中的每个bean定义进行处理,也就是替换每个bean定义中的属性中的占位符
// 该方法为抽象方法,子类去处理~~~
processProperties(beanFactory, mergedProps);
} catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
protected void convertProperties(Properties props) {
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
// convertProperty是个空实现,子类不复写就不会有转换的动作~~~~
String convertedValue = convertProperty(propertyName, propertyValue);
// 转成功了 说明就不会相等了 那就set进去覆盖之前的~~
if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
props.setProperty(propertyName, convertedValue);
}
}
}
}
processProperties是抽象方法,留给子类去处理占位符等情况。不过其目的很明确,是对容器中每个bean定义中的属性进行处理。但具体处理是什么,就要看实现子类自身的设计目的了。比如实现子类PropertyOverrideConfigurer和实现子类PropertyPlaceholderConfigurer就分别有自己的bean定义属性处理逻辑。
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);
}
}
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文件的实现类
Spring 对于Properties的操作都是分别基于上面两个类,而且两个类的实现方式是不一样的。
PropertyPlaceholderConfigurer(重要)
这个类应该是很多小伙伴最初使用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;
}
}
示例:
@Configuration
public class RootConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setOrder(Ordered.HIGHEST_PRECEDENCE);
configurer.setLocation(new ClassPathResource("beaninfo.properties"));
return configurer;
}
@Scope("${bean.scope}") // 这里是能够使用占位符的
@Bean
public Person person() {
Person person = new Person();
return person;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private ConfigurableBeanFactory beanFactory;
// 通过它,可以把生效的配置都拿到
@Autowired
private PropertySourcesPlaceholderConfigurer configurer;
@Test
public void test1() {
Environment environment = applicationContext.getEnvironment();
BeanExpressionResolver beanExpressionResolver = beanFactory.getBeanExpressionResolver();
PropertySources appliedPropertySources = configurer.getAppliedPropertySources();
System.out.println(environment.containsProperty("bean.scope")); //false 注意环境里是没有这个key的
System.out.println(beanExpressionResolver);
System.out.println(appliedPropertySources);
// 获取环境的和我们自己导入的
PropertySource<?> envProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME);
PropertySource<?> localProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
System.out.println(envProperties.getSource() == environment); //true 可以看到这个envProperties的source和环境里的是同一个
System.out.println(localProperties.containsProperty("bean.scope"));//true 本地配置里是包含这个属性的
}
}
从上面测试结果可知,PropertySourcesPlaceholderConfigurer是一种更加强大的加载配置文件处理占位符的工具。在Spring3.1之后建议使用它来加载配置文件进来,这样我们若运行时真有需要的话也是可以访问的。
思考:竟然Properties属性最终都不会放进Environment环境抽象里,那为何@Value这个注解能够通过占位符访问到呢?
PropertyOverrideConfigurer
它是抽象类PropertyResourceConfigurer的子类,它和PlaceholderConfigurerSupport平级。
PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,与PropertyPlaceholderConfigurer 不同的是: PropertyOverrideConfigurer 利用属性文件的相关信息,覆盖XML 配置文件中定义。即PropertyOverrideConfigurer允许XML 配置文件中有默认的配置信息。
如果PropertyOverrideConfigurer 的属性文件有对应配置信息,则XML 文件中的配置信息被覆盖,否则,直接使用XML 文件中的配置信息。
// @since 12.03.2003
public class PropertyOverrideConfigurer extends PropertyResourceConfigurer {
// The default bean name separator.
public static final String DEFAULT_BEAN_NAME_SEPARATOR = ".";
// 下面提供了set方法可以修改
private String beanNameSeparator = DEFAULT_BEAN_NAME_SEPARATOR;
private boolean ignoreInvalidKeys = false; // 默认是不忽略非法的key
private final Set<String> beanNames = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 实现了父类的抽象方法:给每个bean进行覆盖处理 注意:这里不是处理占位符~~~~
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
// 按照props有的这些属性值们进行覆盖~~~~
for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements();) {
String key = (String) names.nextElement();
try {
// 1、拿到beanName 这样拿出来String beanName = key.substring(0, separatorIndex);
// 2、根据beanName拿到bean定义信息BeanDefinition
// 3、bdToUse.getPropertyValues().addPropertyValue(pv); 显然这样子存在的key就覆盖 否则保持原样
processKey(beanFactory, key, props.getProperty(key));
}
}
}
}
需要注意的是Properties属性文件:
beanName.property=value //第一个.前面一定是beanName
请保证这个beanName一定存在。它会根据beanName找到这个bean,然后override这个bean的相关属性值的。
因为这个类使用得相对较少,但使用步骤基本同上,因此此处就不再叙述了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-28 你知道为什么HashMap是线程不安全的吗?
2021-11-28 Java序列化与反序列化三连问:是什么?为什么要?如何做?
2021-11-28 什么情况用ArrayList or LinkedList呢?
2021-11-28 你能谈谈HashMap怎样解决hash冲突吗
2021-11-28 谈谈这几个常见的多线程面试题
2021-11-28 你能说说进程与线程的区别吗
2021-11-28 谈谈 Redis 的过期策略