Spring Environment

Environment 是对 Spring 应用环境 (Spring Application Environment) 的抽象; 应用于应用环境的两个方面:profiles 以及 properties。

Bean Definition Profiles

profiles 属于条件化创建 bean 的一种机制, 不同的 profile 包含了一组 bean 的定义,运行时,可以指定激活哪些 profile (spring.profiles.active), 以及应用哪些默认的 profile (spring.profiles.default),当激活这些 profiles 时,它们包含的 bean 定义会被注册到容器中。

这种机制的使用想必大家已经比较熟悉,关于细节,需要注意的就是 @Profile 注解,背后依赖的是 @Conditional 作为元注解 (我这里是 Spring 4.1.6 版本),@Conditional 注解是 Spring 为条件化创建 bean 提供的一种机制,需要提供一个 org.springframework.context.annotation.Condition 的实现类作为判断的依据。

@Profile 源码:

1
2
3
4
5
6
7
8
9
10
11
12
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();

}

ProfileCondition 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ProfileCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
// 获取 @Profile 注解中的 value 属性值,即 bean 定义时指定所属的 profiles
for (Object value : attrs.get("value")) {
// 判断是否激活
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}

}

@Profile 的属性值支持表达式

  • ! 逻辑非
  • & 逻辑与
  • | 逻辑或

* & 和 | 的组合使用需要使用括号:production & (us-east | eu-central)

将 @Profile 注解作为元注解创建自定义注解

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {

}

Properties

properties 包含了应用用到的所有属性对,可能来源于(不限于):

  • properties 文件
  • JVM 系统属性 (JVM System Properties)
  • 系统环境变量 (System Environment Variables)
  • JNDI
  • Servlet Context Parameters
  • 临时属性对象 (ad-hoc Properties object)
  • Map 对象

Environment 的作用就是为用户提供一个便利的服务接口用来配置属性源(property sources)以及从中解析属性值,属性源可配置, 并具有层次结构。

PropertySource

PropertySource 是对任何键值对的属性源的一种抽象,Spring 提供的 StandardEnvironment (在 standalone 模式下使用) 会配置两个 PropertySource 对象,分别代表 JVM 系统属性的集合( System.getProperties() )和系统环境变量 (System.getenv() )

Environment 对象提供了配置 PropertySource 以及在这些 PropertySource 对象上的检索功能,检索某个属性键是否存在或者对应的值。

Spring Web 应用中, StandardServletEnvironment 实现,检索的优先级如下:

  1. ServletConfig parameters
  2. ServletContext parameters ( web.xml context-param entries )
  3. JNDI environment variables (java:comp/env/ entries)
  4. JVM system properties (-D command-line arguments)
  5. JVM system environment (operating system environment variables)

整个机制是可配置的, 可以移除某个 PropertySource ,或者改变检索的优先级

1
2
3
4
5
6
7
ConfigurableApplicationContext ctx = new GenericApplicationContext();

// MutablePropertySources api exposes a number of methods that allow for precise manipulation of the set of property sources
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();

// MyPropertySource is a custom PropertySource implementation
sources.addFirst(new MyPropertySource());

@PropertySource 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class) // 允许重复使用
public @interface PropertySource {

String name() default "";

String[] value();

boolean ignoreResourceNotFound() default false;

String encoding() default "";

Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

@PropertySource 注解提供了一种向 Environment 添加属性源的便利方式,使用在 @Configuration 类上,需要提的一点是,任何出现在 @PropertySource属性源路径中的 ${..} 占位符会从已经注册的 PropertySource 中检索,也可以提供一个默认值,如 ${key:default}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@PropertySource("classpath:/com/myco/${spring.profiles.active:st}/app.properties")
public class AppConfig {

@Autowired
Environment env;

@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}

@PropertySource is repeatable

可以在一个 @Configuration 类上添加多个 @PropertySource 注解。

xml 定义中,import 其他bean定义时,可以使用占位符,Spring 会从当前 Environment 中进行解析

1
<import resource="${test}.xml"/>

但是当定义某个 bean 时,必须配置一个 PropertySourcesPlaceholderConfigurer, 才可以使用占位符

1
2
java.beans.PropertyEditor Spring implementation which can convert String paths to Resource objects.
Classloader.getResource()
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
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

/*
*
* 在这里处理 PropertySource
*
*/
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}
}
}