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;
}
}
}