Spring之属性管理简单剖析

本文对Spring(3.1之后的版本)的属性管理做了一些剖析,从源码层面对PropertySourcePropertyResolverEnvironment及其相关类进行了解析。

PropertySource

PropertySource其实就是一个属性源的适配器,它对一个任意类型的属性源(可以是Mapjava.util.PropertiesServletContext或其他任何对象)进行包装,然后对外提供一个统一的接口Object getProperty(String name)

例如,我们甚至可以用一个HttpClient来作为属性源,来实现一个HttpPropertySource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class HttpPropertySource extends PropertySource<HttpClient> {

public HttpPropertySource(String name, HttpClient source) {
super(name, source);
}

@Override
public Object getProperty(String name) {
if (name.equals("baidu.content")) {
try {
HttpGet get = new HttpGet("http://www.baidu.com");
return IOUtils.toString(source.execute(get).getEntity().getContent());
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

// test code
env.getPropertySources().addFirst(new HttpPropertySource("httpPropertySource", httpClientService.getHttpClient()));
System.out.println(env.getProperty("baidu.content"));

以上代码仅仅是演示了PropertySource的功能,其实就是个很简单的适配器而已。

PropertyResolver

以下是Spring中PropertyResolver相关类的结构图:

PropertyResolver是一个属性解析的接口,提供了属性解析的功能:

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
public interface PropertyResolver {

//是否包含某个属性
boolean containsProperty(String key);

//获取属性值 如果找不到返回null
String getProperty(String key);

//获取属性值,如果找不到返回默认值
String getProperty(String key, String defaultValue);

//获取指定类型的属性值,找不到返回null
<T> T getProperty(String key, Class<T> targetType);

//获取指定类型的属性值,找不到返回默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue);

//获取属性值为某个Class类型,找不到返回null,如果类型不兼容将抛出ConversionException
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);

//获取属性值,找不到抛出异常IllegalStateException
String getRequiredProperty(String key) throws IllegalStateException;

//获取指定类型的属性值,找不到抛出异常IllegalStateException
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

//替换文本中的占位符(${key})到属性值,找不到不解析
String resolvePlaceholders(String text);

//替换文本中的占位符(${key})到属性值,找不到抛出异常IllegalArgumentException
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

PropertyResolver的实现类PropertySourcesPropertyResolver提供了根据PropertySource进行属性解析的功能,并在此解析的过程中添加了可配置的类型转换服务(ConversionService)。

Environment

以下是Spring中Environment相关类的结构图:

其中PropertySources就是PropertySource的一个集合,在其子类MutablePropertySources中是用一个CopyOnWriteArrayList来存储这个集合的。

我们可以看到在AbstractEnvironment中组合了一个MutablePropertySources和一个ConfigurablePropertyResolver,分别是作为属性源列表和属性解析器。那么Environment的实现就非常清晰了,就是包装了一个PropertySource的列表,并提供了对其之上的解析。

对于标准Java项目(非web),Spring默认会创建一个StandardEnvironment,其中初始化了两个PropertySource,其顺序是:

  1. systemProperties: 系统属性,程序启动时用(-Dx=y)传入
  2. systemEnvironment: 环境变量

Environment在进行属性解析时,会按顺序寻找,如果匹配则直接返回。所以StandardEnvironment是优先在系统属性中查找,如果找不到才在环境变量中查找属性。

对于Java Web项目,Spring默认会创建一个StandardServletEnvironment,它比StandardEnvironment多出来一些属性源,其加载的顺序是:

  1. servletConfigInitParams: servletConfig中的初始化参数
  2. servletContextInitParams: servletContext中的初始化参数
  3. jndiProperties: JNDI的属性(如果有)
  4. StandardEnvironment中的属性源

Environment还有Profile的概念,类似于maven中的profile,很好理解,在这里就不赘述了。

总结

以上Spring的属性管理实现进行了剖析,理解了其实现原理之后,就可以非常灵活的使用它们。

例如,我们可以自定义PropertySource,然后通过ConfigurableEnvironment接口将其插入到当前的Environment中;也可以自定义Environment,去实现我们想要实现的功能。

此外,像<context:property-placeholder/>这样的配置功能,我们也可以马上想到,它就是用PropertySourcesPropertyResolver来实现的(在Spring 3.1之后)。

所以,刨根问底、多读源码吧,骚年。