前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用【享学Spring】

详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用【享学Spring】

作者头像
YourBatman
发布2019-09-03 15:55:49
3.2K0
发布2019-09-03 15:55:49
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

本文主要从PropertiesLoaderSupportPropertyResourceConfigurer出发,聊聊由它衍生出来的一些实用API。(和加载Properties有关)

Spring的PropertyResourceConfigurer是个抽象类,继承自PropertiesLoaderSupport,并实现了接口BeanFactoryPostProcessor

此处注意:它是个Bean工厂的后置处理器,而不是Bean的后置处理器

它抽象了容器启动时,BeanFactory后置处理阶段对容器中所有bean定义中的属性进行配置的一般逻辑,属性配置所使用的属性来源是基类PropertiesLoaderSupport方法所规定的那些属性。

从命名中就能看出,它和PropertyResource有关,且和Bean工厂相关~~

PropertiesLoaderSupport

org.springframework.core.io.support.PropertiesLoaderSupport是一个抽象基类,它抽象了从不同渠道加载属性的通用逻辑,以及这些属性应用优先级上的一些考虑,它所提供的这些功能主要供实现子类使用。

它将属性分成两类:

  1. 本地属性(也叫缺省属性):直接以Properties对象形式设置进来的属性
  2. 外来属性:通过外部资源Resource形式设置进来需要加载的那些属性

对于本地属性和外来属性之间的的使用优先级,通过属性localOverride来标识。如果localOverride为false,表示外部属性优先级高,这也是缺省设置。如果localOverride为true,表示本地属性优先级高。

它还有一个属性fileEncoding用来表示从属性文件加载属性时使用的字符集。(毕竟Properties文件存在乱码问题)

代码语言:javascript
复制
// @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的直接实现子类有PropertiesFactoryBeanPropertyResourceConfigurer

PropertiesFactoryBean

实现了FactoryBean,用来生产Properties,可以配置是否单例(默认是单例)。

代码语言:javascript
复制
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时代的时候,我们其中一种导入配置文件的方式如下(这种代码有木有一种遗失的美好的感觉):

代码语言:javascript
复制
<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里面去。so我们在使用@Value引用的时候比如使用SpEL才能引用到:

代码语言:javascript
复制
@Value("#{prop['datasource.url']}")  // 用@Value("${datasource.url}") 这样是读取不到的  此处务必要注意

附:其实xml时代还有一种常见的引用配置文件的方式如下:(Spring加载properties文件的两种方式)

代码语言:javascript
复制
<context:property-placeholder location="classpath:jdbc.properties"/>

它的原理其实是PropertyPlaceholderConfigurer,下面会说到。它等价于下面这么配置:

代码语言:javascript
复制
 <!-- 与上面的配置等价,下面的更容易理解  这个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环境抽象里面去,但我们取值仍然可以如下取值:(至于原因,小伙伴们可自行思考)

代码语言:javascript
复制
@Value("${datasource.url}") // 占位符取值即可


下面以Java配置方式示例,使用PropertiesFactoryBean加载属性配置文件:

代码语言:javascript
复制
@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表示它的优先级是非常高的。

代码语言:javascript
复制
// @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

代码语言:javascript
复制
//  它实现了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它的子类们它最多的应用场景是处理这种配置场景:

代码语言:javascript
复制
<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文件的实现类:PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer

Spring 对于Properties的操作都是分别基于上面两个类,而且两个类的实现方式是不一样的。(当然还有上面说的PropertiesFactoryBean,但它的方式不太一样~~)

PropertyPlaceholderConfigurer(重要)

这个类应该是N多个小伙伴最初使用Spring时候最初接触到的类。做个粗略的介绍;比如:

代码语言:javascript
复制
<context:property-placeholder location="classpath:jdbc.properties"/>

它的原理就是使用的PropertyPlaceholderConfigurer。当然我们也可以自己定义一个(或者多个)这个Bean。

代码语言:javascript
复制
// @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:

代码语言:javascript
复制
@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**里的,请务必注意。

所以为了保存下他们,我们之前经常可看到如下代码:

代码语言:javascript
复制
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;所以它能够控制取值优先级、顺序,并且还提供了访问的方法,后期再想获取也不成问题

代码语言:javascript
复制
// @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;
	}
}

溜个例子:

代码语言:javascript
复制
@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这个注解能够通过占位符访问到呢?

因为这篇博文:【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“ 详细讲解了@Value这个注解,所以这个思考题有兴趣的小伙伴可以结合本文思考一番~


PropertyOverrideConfigurer

它是抽象类PropertyResourceConfigurer的子类,它和PlaceholderConfigurerSupport平级。

PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,与PropertyPlaceholderConfigurer 不同的是: PropertyOverrideConfigurer 利用属性文件的相关信息,覆盖XML 配置文件中定义。即**PropertyOverrideConfigurer**允许XML 配置文件中有默认的配置信息。

如果PropertyOverrideConfigurer 的属性文件有对应配置信息,则XML 文件中的配 置信息被覆盖:否则,直接使用XML 文件中的配置信息。

代码语言:javascript
复制
// @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属性文件:

代码语言:javascript
复制
beanName.property=value  //第一个.前面一定是beanName

请保证这个beanName一定存在。它会根据beanName找到这个bean,然后override这个bean的相关属性值的。

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

关于Spring下和SpringBoot下属性配置文件使用${}占位符的说明

比如有这个属性文件;

代码语言:javascript
复制
# 故意把它放在第一位 最顶部
app.full=${app.key} + ${app.myname}

app.myname=fsx
app.key=${user.home}

在Spring环境下测试使用:

代码语言:javascript
复制
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, JdbcConfig.class})
public class TestSpringBean {

    @Value("${app.full}")
    private String key;

    @Test
    public void test1() throws SQLException {
        System.out.println(key); //C:\Users\fangshixiang + fsx
    }
}

可以看到在Spring环境下,Properties属性文件是能够使用占位符和直接读取环境中的值的。并且亲测发现不管是使用@PropertySource还是使用配置Bean PropertySourcesPlaceholderConfigurer的方式加载进来,都是能够正常work的。

至于它的原理,为何支持占位符的解析呢?其实上面都说了,此处不再说明了

关于SpringBoot下,就更不用说了。它的application.properties等配置文件里更是能够世界使用占位符和读取环境变量(系统属性值)的。

有个小细节一定要注意:它作为一个PropertySource存在的时候永远是原样。

真正解析占位符是取出来以后:

具体参考类:PropertySourcesPropertyResolver#getProperty方法~ 由它解析这些占位符。可以想到,最终都是委托给PropertyPlaceholderHelper去解析的

总结

从上面的分析可以看出,PropertyResourceConfigurer自身主要是抽象了对容器中所有bean定义的属性进行处理的一般逻辑,实现在接口BeanFactoryPostProcessor所定义的方法postProcessBeanFactory中。

这样容器启动时,bean容器的后置处理阶段,所有bean定义的属性都会被当前configure进行处理,处理时所使用的属性来源自当前configure基类PropertiesLoaderSupport所约定的那些属性,至于做什么样的处理,由当前configure的具体实现类,也就是PropertyResourceConfigurer的实现子类自己提供实现。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年06月11日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • PropertiesLoaderSupport
    • PropertiesFactoryBean
      • PropertyResourceConfigurer
        • PlaceholderConfigurerSupport
        • PropertyOverrideConfigurer
        • 关于Spring下和SpringBoot下属性配置文件使用${}占位符的说明
        • 总结
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档