1. 前言
本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址
Environment环境接口是集成在容器中的抽象,它对应用程序环境的两个关键方面建模:Profile和属性。
Profile是一个命名的、逻辑的 bean 定义的分组,仅当给定的配置文件处于活动状态时才向容器注册。Bean 可以分配给Profile,无论是在 XML 中定义还是使用注解。Environment与Profile相关的对象的作用是确定哪些Profile(如果有)当前是活动的,以及默认情况下哪些Profile(如果有)应该是活动的。
属性在几乎所有应用程序中都扮演着重要的角色,并且可能来自各种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、ad-hoc Properties对象、Map对象等。Environment与属性相关的对象的作用是为用户提供方便的服务接口,用于配置属性源并从中解析属性。
2. Bean 定义Profile
Bean 定义Profile在核心容器中提供了一种机制,允许在不同环境中注册不同的 Bean。“环境”这个词对不同的用户可能有不同的含义,此功能可以帮助解决许多用例,包括:
- 在开发中使用内存数据源,而不是在 QA测试 或生产中从 JNDI 查找相同的数据源。
- 仅在将应用程序部署到性能环境中时才注册监控基础设施。
- 为客户 A 和客户 B 部署注册 bean 的自定义实现。
比如我们开发环境下,注入的DataSource数据源如下:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
那么在测试或者生产环境中部署程序时,就需要考虑切换数据源配置,如何根据当前环境进行不同的数据源Bean切换呢?Bean 定义配置文件是核心容器的一个特性,它为这个问题提供了解决方案。
2.1 使用@Profile
@Profile注解由spring-context模块提供。只有当@Profile指定的环境配置被激活时,才会将@Profile中所对应的Bean注册到Spring容器中。
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({
ProfileCondition.class})
public @interface Profile {
String[] value();
}
比如上述的dataSource数据源配置,可以使用@Profile定义在development环境下才加载当前Bean对象。
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
在生产环境下,指定@Profile的值为production。
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
当然也可以指定多个值:
@Profile({
"p1", "!p2"})
也可以结合@Bean注解直接使用
@Configuration
public class AppConfig {
// 该standaloneDataSource方法仅在development配置环境中可用。
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
// 该jndiDataSource方法仅在production配置环境中可用。
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
可以用@Profile作为元注解来创建自定义组合注解。以下示例定义了一个自定义 @Production注解,您可以将其用作 @Profile(“production”)的替代品 :
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
2.2 XML Bean 定义Profile
XML中对应的是<beans>
元素的profile属性,我们前面的示例配置可以改写为两个 XML 文件,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
2.3 激活Profile
在之前我们配置了各个环境的Profile,然后我们需要指定当前应用程序的环境,Spring提供了多种方式激活。
但最直接的是针对Environment API 以编程方式进行,该API 可通过 ApplicationContext,以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
当然也可以一次性激活多个:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
还可以通过spring.profiles.active属性声明性地激活配置文件,这些属性可以通过系统环境变量、JVM 系统属性、servlet 上下文参数、web.xml指定,甚至可以作为JNDI 中的条目(请参阅PropertySource抽象)。在集成测试中,可以使用模块中的@ActiveProfiles注解来声明活动配置文件spring-test(请参阅环境配置文件的上下文配置)。
2.4 默认Profile
用过spring boot的应该知道,在项目启动时,如果没有指定Profile,则会使用default作为默认的Profile。
对于以下示例,如果没有Profile处于活动状态,dataSource则会创建 。可以将其视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何Profile,则默认Profile不适用。
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
还可以更改默认Profile的名称,方法是使用Environment中setDefaultProfiles()方法,或者使用spring.profiles.default属性配置。
3. PropertySource
Spring 的Environment 提供了对属性源PropertySource的配置和搜索操作。
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
上述代码中,从Spring 的Environment 中查询当前环境是否定义了 my-property 属性,Environment对一组对象执行搜索PropertySource,PropertySource是对任何键值对属性的简单抽象,可以理解PropertySource为应用程序环境配置的属性对象。
比如Environment 的实现类StandardEnvironment ,配置了两个 PropertySource 对象,
- systemProperties: JVM 系统属性
- systemEnvironment:系统环境变量
public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
public StandardEnvironment() {
}
protected StandardEnvironment(MutablePropertySources propertySources) {
super(propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}
}
而StandardServletEnvironment 填充了额外的默认属性源,包括 servlet 配置和 servlet 上下文参数。
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
/** Servlet context init parameters property source name: {@value}. */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** Servlet config init parameters property source name: {@value}. */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
/** JNDI property source name: {@value}. */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
}
在查询属性时是分层的。默认情况下,系统属性优先于环境变量。如果my-property在调用期间碰巧在两个地方都设置了该属性,则系统属性值会优先查询到并返回。请注意,属性值不会合并,而是被前面的条目完全覆盖。
对于StandardServletEnvironment,完整的层次结构如下,最高优先级的条目位于顶部:
- ServletConfig 参数(如果适用例如,在DispatcherServlet上下文的情况下)
- ServletContext 参数(web.xml 上下文参数条目)
- JNDI 环境变量(java:comp/env/条目)
- JVM 系统属性(-D 命令行执行参数)
- JVM系统环境(操作系统环境变量)
整个机制是可配置的。请实现并实例化自定义的PropertySource,并将其添加到当前Environment. 以下示例显示了如何执行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
MyPropertySource已最高优先级添加。如果它包含一个my-property属性,则会优先查询并返回该属性。
4. 使用@PropertySource注解
@PropertySource 注解提供了一种方便且声明性的机制来添加PropertySource 到 Spring 的Environment中。
比如我们首先创建一个app.properties配置文件,配置一个属性(testbean.name=myTestBean),然后在@Configuration类中使用@PropertySource引入这个配置文件,然后就可以在环境中获取这个属性。
@Configuration
@PropertySource("classpath:/com/myco/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都根据针对环境注册的一组属性源进行解析,如以下示例所示:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
假设my.placeholder存在于已注册的属性源之一(例如,系统属性或环境变量)中,占位符将解析为相应的值。如果没有,则将default/path其用作默认值。如果未指定默认值且无法解析属性,则抛出 IllegalArgumentException异常。
@PropertySource注解是可重复配置多个的。但是,所有@PropertySource都需要在同一级别声明,可以直接在配置类上声明,也可以作为同一自定义注解中的元注解。不推荐混合直接注解和元注解,因为直接注解有效地覆盖了元注解。
5. 语句中的占位符解析
在之前,xml标签中的占位符的值只能根据 JVM 系统属性或环境变量进行解析。因为Environment是集成在整个容器中的,所以很容易通过它来路由占位符的解析。
这意味着可以按照自己喜欢的任何方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级或完全删除它们。您还可以根据需要将自己的属性源添加到组合中。
具体来说,无论属性在哪里定义,以下语句都有效,只要它在可用Environment的中:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: