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。它是对父类的增强,它能解决如下两个可能问题:

  1. 配置文件不能放在主目录中,因为某些OS(如Win9X)没有主目录的概念
  2. 没有标准的文件命名规则,存在文件名冲突的可能性

使用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的相关属性值的。

因为这个类使用得相对较少,但使用步骤基本同上,因此此处就不再叙述了。

 

 

posted @   残城碎梦  阅读(843)  评论(0编辑  收藏  举报
编辑推荐:
· 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 的过期策略
点击右上角即可分享
微信分享提示