可参考官网:Environment Abstraction
核心概念
Environment 对象对两个关键方面进行建模:profiles 和 属性(properties)。
Profile
简单来说:profile 机制在 IOC 容器中提供了一种机制:允许在不同的环境中注册不同的bean。即对于一个bean,如果你想在情况A中注册它的一种类型,而在情况B中注册另一种类型,这时你就可以使用 profile
使用 @Profile
:
考虑一下实际应用,我们需要一个 DataSource。在 开发环境 中,我们希望IOC容器中它的类型是 DataSourceImpl1,而在 生产环境 中,我们希望IOC容器中它的类型是 DataSourceImpl2。
我们可以定义如下两个配置类:
@Configuration
@Profile("development")
public class DevelopmentDataConfig {
@Bean
public DataSource dataSource1() {
return new DataSourceImpl1(); // 伪代码
}
}
@Configuration
@Profile("production")
public class ProductionDataConfig {
@Bean
public DataSource dataSource2() {
return new DataSourceImpl2(); // 伪代码
}
}
@Profile 注解可以在 @Bean 方法上使用
激活一个 Profile
激活一个 profile 可以通过几种方式进行,但最直接的是 以编程方式 对环境API进行激活,该API可以通过 ApplicationContext 获得。下面的例子显示了如何做到这一点:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development"); // 激活 "development" 对应的 profile(可以指定多个)
ctx.register(AppConfig.class, DevelopmentDataConfig.class, ProductionDataConfig.class);
ctx.refresh();
此外,你还可以通过 spring.profiles.active 属性以声明方式激活配置文件,该属性可以通过 系统环境变量、JVM 系统属性、web.xml 中的 servlet 上下文参数 (这些参数其实都会以 “属性源PropertySource” 的形式保存在 Environment 中,包括 SpringBoot 应用中 yml 中配置的参数) 来指定
如,通过 JVM系统属性指定:
-Dspring.profiles.active="development" #可以指定多个profile
这样一来,IOC 容器在启动时,就会注入 development 环境所需要的 DataSourceImpl1 bean对象
默认 Profile
如果没有活动的 profile,那么 Spring 底层会启用默认的 profile(“default”)
@Configuration
@Profile("default") // 系统默认会启用"default"对应的profile
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new DataSourceImpl3();
}
}
以上配置表明:即使你没有激活任何 profile ,DataSourceImpl3 也会被注入到容器中
如果任何 profile 被启用,默认的profile就不应用。
你可以通过在环境中使用 setDefaultProfiles() 来改变默认配置文件的名称,或者通过声明性地使用 spring.profiles.default 属性。
属性(properties)
在 Spring 中,使用 PropertySource 对象对属性进行抽象。PropertySource 是对 “任何键值对源” 的简单抽象,提供了统一存储外部化配置数据的功能,例如上面配置的JVM系统属性就会被封装为 PropertySource 对象(可参考其 javadoc)
Environment 对象中保存了一系列 PropertySource 集合,可以使用 Environment 对 PropertySource 进行搜索获取:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean flag = env.containsProperty("my-property");
System.out.println(flag); // 当前环境中是否定义了 my-property 属性?
Spring 的 StandardEnvironment 对象保存了两个 PropertySource 对象—— 一个表示 JVM 系统属性集合(System.getProperties()),另一个表示系统环境变量集合(System.getenv())。
这些默认属性源存在于 StandardEnvironment 中,用于独立的应用程序中。
而其子类 StandardServletEnvironment 中填充了其他默认属性源,包括 Servlet config、Servlet context 参数和 JndiPropertySource(如果 JNDI 可用)。
执行的搜索是分层次的。默认情况下,系统属性(system properties)优先于环境变量(environment variables)。
因此,如果在调用 env.getProperty(“my-property”) 时,my-property 属性恰好在两个地方都被设置了,那么系统属性值 “胜出” 并被返回。请注意,属性值不会合并,而是被前面的条目完全覆盖。
我们可以定义自己的 PropertySource 加入到当前 Environment 中,并且可以指定其位置(搜索优先级)
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource()); // 加入到最前面(优先级最高)
源码解析
先看 Environment 的体系架构:
我们可以从 AbstractEnvironment 类入手:
当一个组件Xxx(接口)体系很复杂时,从它具体的落地实现类开始分析是十分复杂的,可以考虑从它的抽象实现类:AbstractXxx 入手(内部一定定义了某些重要方法的抽象实现,和一些重要的成员属性)
AbstractEnvironment 中几个比较重要的成员属性:
private final Set<String> activeProfiles = new LinkedHashSet<>(); // 保存激活的Profile
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles()); // 保存默认的 Profile
private final MutablePropertySources propertySources; // 存放一系列 PropertySource
private final ConfigurablePropertyResolver propertyResolver; // 属性解析器(如解析占位符${})
其中 MutablePropertySources 对象维护着一个 PropertySource 列表,外部可以通过 get、contains、addFirst 等方法对其进行访问
对外暴露的与 profile 相关的 API 方法(可自行查看方法内部逻辑):
getDefaultProfiles(); // 获取默认的 Profile
setDefaultProfiles(); // 设置默认的 Profile
getActiveProfiles(); // 获取激活的 Profile
setActiveProfiles(); // 设置激活的 Profile
所以说:Environment 对象对两个关键方面进行建模:Profile 和 PropertySource(属性源)
当我们使用 SpringBoot 进行传统的 Web 开发时,应用环境为 SERVLET ,默认使用的 Environment 实现为:ApplicationServletEnvironment
源码位置:run() —> prepareEnvironment() —> getOrCreateEnvironment()
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment();
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
就是直接使用无参构造创建其对象:new ApplicationServletEnvironment(),分析这个动作都发生了什么
ApplicationServletEnvironment 的无参构造中会调父类的无参构造(默认有super.())… 一直会调用到 AbstractEnvironment 的无参构造:
public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources; // 新 new 的 MutablePropertySources
this.propertyResolver = createPropertyResolver(propertySources);
customizePropertySources(propertySources); // 模板方法,提供给子类实现(子类向propertySources里面添加一些PropertySource)
}
套路:在分析父类预留给子类实现的方法时,要定位到此方法 “最后一次的重写位置”
子类 StandardServletEnvironment # customizePropertySources 的实现:(customizePropertySources 方法最后的重写位置(StandardServletEnvironment 一系中))
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 向 propertySources 中加入了几个 PropertySource :"servletConfigInitParams"、"servletContextInitParams"、"jndiProperties"(根据情况)
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
// 回调父类 StandardEnvironment # customizePropertySources (父类 StandardEnvironment再向propertySources中加入几个PropertySource)
super.customizePropertySources(propertySources);
}
StandardEnvironment # customizePropertySources
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 向 propertySources 中加入了几个 PropertySource :"systemProperties"、"systemEnvironment"
propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
所以在 ApplicationServletEnvironment 对象创建完成之后,它所持有的 propertySources 中至少保存了 “servletContextInitParams”、“servletConfigInitParams”、“systemEnvironment”、“systemProperties” 四个 PropertySource
(待…)