如何理解 Environment?
Environment 由 Spring 3.1 版本提出,表示当前应用的运行时环境。用于管理 Spring 中的条件配置 Profiles 和配置属性源。
Environment 的使用场景
Spring Boot 中,spring.profiles.active 属性可以指定激活的 profiles ,spring.profiles.default 属性可以指定无激活的 profiles 时使用的默认 profiles。属性可以配置在不同的数据源中,如配置文件,启动命令参数。
场景1:获取 Profiles
- 直接获取 Profiles 信息,不同运行环境激活不同的 Profiles,执行不同的逻辑。示例代码如下。
if(environment.acceptsProfiles("dev")){ doSomething(); }else{ doOtherSomething(); }
- 通过 @Profile 注解在不同环境注册不同的 bean。
@Profile 注解可以标注在表示组件的类或方法上,@Profile 注解 value 属性值和 Environment 中接受的 Profiles 值匹配时该组件才会被注册为 Spring 中的 bean。如下示例,可以在开发环境和生产环境使用不同的数据源。@Configuration public class SpringConfiguration { @Bean @Profile("dev") public DataSource devDataSource(){ return new SimpleDataSource(); } @Bean @Profile("prod") public DataSource prodDataSource(){ return new SimpleDataSource(); } }
场景2:获取属性源中的属性
- 通过 Environment#getProperty(String) 获取
- 通过 @Value 注解注入属性值到 bean 的成员变量中。
如何获取 Environment ?
- ApplicationContext#getEnvironment() 获取
- 可以通过 @Autowire 或实现 ApplicationContextAware 先获取 ApplicationContext。
- @Autowire 注入、实现 EnvironmentAware 在 bean 生命周期中获取
Environment Profiles 管理
理解 Profiles
Profiles 表示条件配置,可用于在不同的环境中配置不同的 bean。Environment 对 Profile 管理的方法定义如下。
public interface Environment extends PropertyResolver {
/**
* 获取激活的 profiles
*/
String[] getActiveProfiles();
/**
* 获取默认的 profiles
*/
String[] getDefaultProfiles();
/**
* 给定的 profiles 是否被激活
*/
@Deprecated
boolean acceptsProfiles(String... profiles);
/**
* 给定的 profiles 是否被激活
*/
boolean acceptsProfiles(Profiles profiles);
}
Environment 接口中只有获取和匹配的方法,对 profiles 设置的方法则由其子接口 ConfigurableEnvironment 提供。ConfigurableEnvironment 对 profiles 设置的方法如下。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
/**
* 设置激活的 profiles
*/
void setActiveProfiles(String... profiles);
/**
* 添加激活的 profiles
*/
void addActiveProfile(String profile);
/**
* 设置默认的 profiles
*/
void setDefaultProfiles(String... profiles);
}
@Profile 注解
伴随着 Environment,Spring 在 3.1 提出了一个 @Profile 注解,标注了该注解的 Component,只有指定的值满足 Environment 接受的 Profiles,Component 才会注册为 Spring 的 bean。使用方式可参见前面的示例,这里不再举例。
Environment 配置属性源管理
配置属性获取
Environment 接口并未定义有关配置属性源的方法,配置属性相关的方法由其父接口 PropertyResolver 定义。PropertyResolver 相关方法如下。
public interface PropertyResolver {
/**
* 是否存在给定名称的属性
*/
boolean containsProperty(String key);
/**
* 获取给定名称属性源的值
*/
@Nullable
String getProperty(String key);
/**
* 获取给定名称的属性,不存在时则使用默认值
*/
String getProperty(String key, String defaultValue);
/**
* 获取给定名称和期望类型的属性值
*/
@Nullable
<T> T getProperty(String key, Class<T> targetType);
/**
* 获取给定名称和希望类型的属性值,不存在时则使用默认值
*/
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* 获取给定名称的属性值,不存在时则抛出异常
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* 获取给定名称和期望类型的属性值,不存在时则抛出异常
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* 解析文本中的 ${...} 属性占位符,使用对应的属性值替换
*/
String resolvePlaceholders(String text);
/**
* 解析文本中的 ${...} 属性占位符,使用对应的属性值替换,不存在对应的属性时则抛出异常
*/
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
可以看到,PropertyResolver 接口中也只有属性的获取和匹配的方法,另外还提供了解析文本中属性占位符的方法。
配置属性源
理解配置属性源
属性可能来源不同的位置,如操作系统环境变量、启动参数、配置文件等。因此Environment 中具有不同的属性源。对属性源的获取相关方法则定义在 Environment 的子接口 ConfigurableEnvironment 中,相关方法定义如下。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
/**
* 获取可以修改的 PropertySources
*/
MutablePropertySources getPropertySources();
/**
* 获取 System#getProperties() 的值,Environment 实现已经将该值作为默认的 PropertySource,建议不要直接调用该方法
*/
Map<String, Object> getSystemProperties();
/**
* 获取 System#getenv() 的值,Environment 实现已经将该值作为默认的 PropertySource,建议不要直接调用该方法
*/
Map<String, Object> getSystemEnvironment();
}
ConfigurableEnvironment 中遇到一个新的类 MutablePropertySources,这个类对所有的属性源进行管理,可以方便的添加删除替换属性源,并且可以指定属性源的添加位置,这样前面的属性源就会具有较高的优先级。
先看属性源 PropertySource 的定义,核心代码如下。
public abstract class PropertySource<T> {
/**
* 属性源名称
*/
protected final String name;
/**
* 底层的属性源
*/
protected final T source;
public PropertySource(String name, T source) {
... 省略部分代码
this.name = name;
this.source = source;
}
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
/**
* 获取当前属性源名称
*/
public String getName() {
return this.name;
}
/**
* 获取属性源存储属性的底层类
*/
public T getSource() {
return this.source;
}
/**
* 属性源是否包含给定的属性名称
*/
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
/**
* 获取属性名称关联的值
*/
@Nullable
public abstract Object getProperty(String name);
}
可以认为,ProperySource 就表示某一个属性源,每个属性源都有一个名字,具体属性源的存储方式由子类指定,根据不同来源有所不同。常见的 PropertySource 如下。
- ServletConfigPropertySource:Servlet 属性源
- ServletContextPropertySource:ServletContext 属性源
- CommandLinePropertySource:命令行属性源
- SystemEnvironmentPropertySource:系统环境变量属性源
- PropertiesPropertySource:Properties 属性源
ProperySources 是一个表示多个 PropertySource 的 Iterable,定义如下。
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* 是否存在给定名称的属性源
*/
boolean contains(String name);
/**
* 获取给定名称的属性源
*/
@Nullable
PropertySource<?> get(String name);
}
MutablePropertySources 对 PropertySources 进行实现,添加了对 PropertySource 的添加、删除、替换方法。底层使用一个 List 来保存 PropertySource,这样不同的属性源就有了优先级,排在前面的具有较高的优先级。部分常用的方法如下。
public class MutablePropertySources implements PropertySources {
// 使用 List 存储属性源
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
// 是否存在给定名称的属性源
@Override
public boolean contains(String name) {
return this.propertySourceList.contains(PropertySource.named(name));
}
// 获取给定名称的属性源
@Override
@Nullable
public PropertySource<?> get(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.get(index) : null);
}
// 添加属性源到第一个位置,添加的属性源具有最高的优先级
public void addFirst(PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.add(0, propertySource);
}
// 添加属性源到最后一个位置,添加的属性源就有最低的优先级
public void addLast(PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
// 移除给定名称的属性源
@Nullable
public PropertySource<?> remove(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.remove(index) : null);
}
// 替换给定名称的属性源为新的属性源
public void replace(String name, PropertySource<?> propertySource) {
int index = assertPresentAndGetIndex(name);
this.propertySourceList.set(index, propertySource);
}
}
@PropertySource 注解
为了支持自定义的配置文件作为属性源,Spring 在 3.1 版本中同样添加了注解 @ProertySource ,使用方式为在配置类上添加该注解,然后指定配置文件的路径。示例代码如下。
@PropertySource(name = "default", value = "classpath:/META-INF/default.properties", encoding = "UTF-8",
ignoreResourceNotFound = true, factory = PropertySourceFactory.class)
@Configuration
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
Object source = context.getEnvironment().getPropertySources().get("default").getSource();
System.out.println(source);
}
}
打印结果如下。
{
name=大鹏}
@PropertySource 各属性含义如下。
- name:属性源的名称,如果不指定则会自动生成。
- value:指定资源的路径。
- encoding :表明资源文件的编码方式,如果不指定存在中文时可能会乱码。
- ignoreResourceNotFound :找不到资源时是否忽略,默认为 false,找不到资源将抛出异常。
- factory:创建 PropertySource 的工厂,默认的实现是 DefaultPropertySourceFactory 。
Environment 的底层实现
Environment 接口的实现类
先来看 Environment 相关类图,对整体有一个了解。
PropertyResolver、Environment、ConfigurableEnvironment 在前面都有提到,剩下比较重要的类如下。
- ConfigurablePropertyResolver:PropertyResolver 的子接口,在PropertyResolver 的基础上添加了设置类型转换服务 ConfigurableConversionService 及占位符前缀、后缀等方法。
- AbstractEnvironment:Environment 的抽象实现,并且提供了自定义默认属性源的方法,下文将详细对其进行分析。
- StandardEnvironment:非 web 环境下的标准实现,默认添加了表示系统属性的 PropertiesPropertySource 和 操作系统环境变量的 SystemEnvironmentPropertySource。
- StandardServletEnvironment: web 环境下的标准实现,默认添加了 ServletConfigPropertySource 和 ServletContextPropertySource 两种属性源。
Profiles 管理实现
Environment Profiles 相关方法由 AbstractEnvironment 进行实现。部分代码如下。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
// active profiles 属性名
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
// default profiles 属性名
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
// active profiles 集合
private final Set<String> activeProfiles = new LinkedHashSet<>();
// default profiles 集合
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
// 当前 Environment 接受的 profiles
@Override
public boolean acceptsProfiles(Profiles profiles) {
Assert.notNull(profiles, "Profiles must not be null");
return profiles.matches(this::isProfileActive);
}
// 给定名称的 profile 是否激活
protected boolean isProfileActive(String profile) {
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
// 激活的 profiles 或默认的 profiles 中存在给定名称的 profile 则表示接受
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
//获取激活的 profiles
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
// 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
// 获取默认的 profiles
protected Set<String> doGetDefaultProfiles() {
synchronized (this.defaultProfiles) {
if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
// 优先获取 API 到当前 Environment 中的 profiles, 获取不到则会从属性源中获取
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.defaultProfiles;
}
}
}
可以看到,Profiles 的数据来源主要包括两块,通过 ConfigurableEnvironment API 设置的 profiles 优先,如果未设置则会从配置属性源中获取,然后进行缓存。
@Profile 注解的实现
Spring 4.0 之后对 @Profile 进行了重构,使用 @Conditional 实现,参见 Spring 条件注解 @Conditional 使用及其底层实现。@Profile 源码如下。
@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,源码如下。
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
ProfileCondition 实现较为简单,如果 Environment 接受 @Profile 的值,则允许组件进行注入。
Environment 属性获取实现
以 AbstractEnvironment#getProperty(String) 获取属性的方法为例,代码如下。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
// 属性源
private final MutablePropertySources propertySources = new MutablePropertySources();
// 属性解析器
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
@Override
@Nullable
public String getProperty(String key) {
return this.propertyResolver.getProperty(key);
}
}
属性源由 MutablePropertySources 进行保存,而属性的获取则委托为 PropertySourcesPropertyResolver,这是 Spring 中常用的一种设计模式,类实现某一个接口,而实现委托给接口的另一个实现完成。再看 PropertySourcesPropertyResolver 源码。
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
// 属性源保存
@Nullable
private final PropertySources propertySources;
// 构造方法
public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
this.propertySources = propertySources;
}
// 设置类型转换服务
@Override
public void setConversionService(ConfigurableConversionService conversionService) {
this.propertyResolver.setConversionService(conversionService);
}
//设置占位符前缀
@Override
public void setPlaceholderPrefix(String placeholderPrefix) {
this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
}
// 设置占位符后缀
@Override
public void setPlaceholderSuffix(String placeholderSuffix) {
this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
}
// 获取属性值
@Override
@Nullable
public String getProperty(String key) {
return getProperty(key, String.class, true);
}
// 获取给定类型的属性值
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 循环从属性源中获取属性值
for (PropertySource<?> propertySource : this.propertySources) {
... 省略部分代码
Object value = propertySource.getProperty(key);
if (value != null) {
// 解析属性值中的占位符
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// 类型转换
return convertValueIfNecessary(value, targetValueType);
}
}
}
... 省略部分代码
return null;
}
}
PropertySourcesPropertyResolver 属性解析器循环属性源获取属性,有必要的情况下会解析属性值中的占位符,最后还会进行类型转换。类型转换为委托给 ConversionService,而占位符则有前缀、后缀。这些信息都在 AbstractEnvironment 中进行设置。
@ProperySource 注解的实现
@ProperySource 用来标注在配置类上,因此 @ProperySource 的处理在配置类的解析过程中。具体代码位置在ConfigurationClassParser#processPropertySource
,这里简单进行分析。
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
// 先获取 @PropertySource 注解的属性值
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
// 默认使用 DefaultPropertySourceFactory 创建属性源的实例
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// 循环添加属性源
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
} catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
} else {
throw ex;
}
}
}
}
处理 @PropertySource 过程,循环将资源文件位置转换为 EncodedResource,然后创建属性源,最后将属性源添加到 Environment 中。如果同时指定的属性源的名称和多个属性源的位置,还会将多个名称相同的属性源组合为一个 PropertySource。这里看一眼 DefaultPropertySourceFactory 的实现。
public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}
默认创建的 PropertySource 是 ResourcePropertySource,这个属性源会从 properties 或 xml 中加载属性,由于使用了 EncodedResource ,因此可以加载到正确编码的内容而不会乱码。有关 Resource 的文章可见 Spring 资源管理 (Resource)
@Value 如何注入属性
Spring 中 @Autowire 和 @Value 注解的处理都使用 AutowiredAnnotationBeanPostProcessor ,它会在 bean 实例化后对使用 @Autowire、@Value、@Inject 等注解标注的属性赋值,底层实现依赖AutowireCapableBeanFactory#resolveDependency
。这块由于比较复杂,后面会再开一篇文章对其分析,感兴趣的小伙伴可先自行阅读源码。
总结
本篇首先对 Environment 的使用进行介绍,然后对其底层的实现进行分析。理解 Environment 同样有助于熟悉 Spring 的第三方框架源码,欢迎大家留言交流,感谢。