Spring中的Properties

摘自Environment注释:

Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

配置

XML方式

这种应该是最常见的方式了,在spring配置文件中:

1
<context:property-placeholder location="classpath:bean.properties" />

注解方式

如果不是用xml方式,也可以使用注解方式,使用@PropertySource

1
2
3
4
5
@Configuration
@PropertySource("classpath:bean.properties")
public class Application {

}

其他方式

PropertyPlaceholderConfigurer

XML方式:

1
2
3
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:bean.properties"/>
</bean>

注解方式:

1
2
3
4
5
6
@Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("bean.properties"));
return ppc;
}

PropertySourcesPlaceholderConfigurer

XML方式:

1
2
3
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:bean.properties"/>
</bean>

注解方式:

1
2
3
4
5
6
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
pspc.setLocation(new ClassPathResource("bean.properties"));
return pspc;
}

使用

XML

1
2
3
<bean id="dataSource">
<property name="url" value="${jdbc.url}"/>
</bean>

@Value

1
2
@Value("${jdbc.url}")
private String url;

Environment

1
2
3
4
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));

实现

PropertySourcesPlaceholderConfigurer

在看实现xml中占位符替换之前,先看下PropertySourcesPlaceholderConfigurer继承关系:

可以看到PropertySourcesPlaceholderConfigurer实现了BeanFactoryPostProcessor,而其中的postProcessBeanFactory()方法可以对BeanDefinition做属性更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// PropertySourcesPlaceholderConfigurer.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
// 新建一个`propertySources`
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
// 将`environment`作为一个`propertySource`加入`propertySources`
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
try {
// `mergeProperties()`从配置文件中读取(以例子来说,就是加载`bean.properties`)
// 之前说过`propertySources`存在顺序,此处根据参数是否覆盖,决定插入的位置
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);
}
}

// 处理占位符字符串,`PropertySourcesPropertyResolver`之前已经介绍过,是属性处理类(包括占位符替换)
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
this.appliedPropertySources = this.propertySources;
}

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {

// 可以在`bean`配置中自定义占位符格式,作用同于`PropertySourcesPropertyResolver`相关属性
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);

// 此处定义字符串处理类
// `propertyResolver`相关见`Spring中的PropertyResolver`
StringValueResolver valueResolver = new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
String resolved = ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal);
return (resolved.equals(nullValue) ? null : resolved);
}
};

// 父类`PlaceholderConfigurerSupport`中实现
doProcessProperties(beanFactoryToProcess, valueResolver);
}

可以看到PropertySourcesPlaceholderConfigurer在处理配置文件时,并没有将配置文件加入到environment,而是自己内部进行维护propertySource,因此无法通过environment中获取到配置文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// PlaceholderConfigurerSupport.java
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {

BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

// 获取所有Bean名称
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
// 循环所有Bean,做属性占位符替换
for (String curName : beanNames) {
// 此处检查防止陷入死循环
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 处理BeanDefinition属性中的占位符
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.
beanFactoryToProcess.resolveAliases(valueResolver);

// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

我们知道,Spring在注册Bean时会先将Bean转化为BeanDefinition,而这个BeanDefinition是未做任何属性上的处理(以例子来说,PropertiesService.name的值就是${properties.name}),而BeanDefinitionVisitor做的事就是将BeanDefinition的相关属性值做占位符替换处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class BeanDefinitionVisitor {

// 省略部分代码...

public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
visitPropertyValues(beanDefinition.getPropertyValues());
// 构造方法占位符处理
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}

// 例子中使用的是构造方法属性名称注入
protected void visitGenericArgumentValues(List<ConstructorArgumentValues.ValueHolder> gas) {
for (ConstructorArgumentValues.ValueHolder valueHolder : gas) {
// valueHolder.getValue()就是例子中的`${properties.name}`
Object newVal = resolveValue(valueHolder.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) {
valueHolder.setValue(newVal);
}
}
}

@SuppressWarnings("rawtypes")
protected Object resolveValue(Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
// 处理占位符并且重新设值
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}

protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
// valueResolver即前面定义的`StringValueResolver`
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}

}

可以看到visitBeanDefinition()对很多属性都做了占位符替换,每个属性替换方法实现都类似。以例子来说,通过构造方法属性注入,所以看visitGenericArgumentValues(),该方法实现也是比较简单,循环获取每个属性对应的属性值(${properties.name}),然后调用PropertyResolver实现占位符替换,最后对属性重新设值

context:property-placeholder

context:property-placeholder看下解析注册部分,因为占位符替换逻辑其实就是通过注册PropertySourcesPlaceholderConfigurer实现,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AbstractBeanDefinitionParser.java
// AbstractBeanDefinitionParser可以理解为一个`xml`解析类,从`xml`中解析`BeanDefinition `
@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// 解析`context:property-placeholder`应该使用的`BeanDefinition`
// 方法在子类`AbstractSingleBeanDefinitionParser`中实现
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
// 省略部分代码...

BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 注册`BeanDefinition`,其实就是注册了`PropertySourcesPlaceholderConfigurer`
registerBeanDefinition(holder, parserContext.getRegistry());

// 省略部分代码...
}
catch (BeanDefinitionStoreException ex) {
parserContext.getReaderContext().error(ex.getMessage(), element);
return null;
}
}
return definition;
}

parseInternal()在子类AbstractSingleBeanDefinitionParser中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// AbstractSingleBeanDefinitionParser.java
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
//
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}

// 省略部分方法...
return builder.getBeanDefinition();
}

看下getBeanClass()方法,决定了AbstractBeanDefinition类型:

1
2
3
4
5
6
7
8
9
10
11
12
// PropertyPlaceholderBeanDefinitionParser.java
@Override
protected Class<?> getBeanClass(Element element) {

// Spring3.1之后默认使用`PropertySourcesPlaceholderConfigurer`,Spring3.1之前默认使用`PropertyPlaceholderConfigurer`
// 可以通过设置`system-properties-mode`属性强制修改
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
return PropertySourcesPlaceholderConfigurer.class;
}

return PropertyPlaceholderConfigurer.class;
}

BeanFactory中注册了PropertySourcesPlaceholderConfigurer之后,占位符替换的逻辑就和上面一样了

@PropertySources

ConfigurationClassParser在注解启动时(使用@Configuration)是一个非常重要的类,功能就是解析使用@Configuration的配置类,包括@PropertySources@ComponentScan等类注解,以及@Bean等方法注解,此处看下@PropertySources解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ConfigurationClassParser.java
// 解析@PropertySource,@Import,@ImportResource,@ComponentScan,@Bean
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {

// 省略部分代码...

// 解析@PropertySource
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}

// 省略部分代码...

return null;
}

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
// 获取资源文件名称
String name = propertySource.getString("name");
// 获取资源文件路径
String[] locations = propertySource.getStringArray("value");
// 是否忽略不存在的资源
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
for (String location : locations) {
try {
// 路径支持占位符替换
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
ResourcePropertySource rps = (StringUtils.hasText(name) ?
new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
// 将资源文件添加到`environment`中
addPropertySource(rps);
}
catch (IllegalArgumentException ex) {
// from resolveRequiredPlaceholders
if (!ignoreResourceNotFound) {
throw ex;
}
}
catch (FileNotFoundException ex) {
// from ResourcePropertySource constructor
if (!ignoreResourceNotFound) {
throw ex;
}
}
}
}

因此,在这种配置下,资源文件中的内容可以通过注入Environment获取:

1
2
3
4
5
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));

参考

Properties with Spring