1.13 Environment抽象
Environment
是集成在容器中的抽象,他包含了两个方面: profiles 和 properties。
一组 bean 可以定义为一个拥有名字的 profile
,只有给定的 profile
被激活的时候,这些 bean 才会注册到容器中。不管是 xml,还是注解定义到bean 都可以被指定给 profile,可以指定多个 profile 。Environment
对象会判断哪个 profile
被激活了。
java -jar rabbitmq-tutorials.jar --spring.profiles.active=hello-world,receiver
Properties属性扮演一个非常重要的角色,可能来源于:properties文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,点对点的属性对象,Maps等。 Environment
对象给用户提供方便接口,方便撰写配置、方便解析配置。
1.13.1 bean定义profiles
bean定义profiles是核心容器内的一种机制,该机制能在不同环境中注册不同的bean。环境的意思是,为不同的用户做不同的事儿,该功能在很多场景中都非常有用,包括:
- 开发期使用内存数据源,在QA或者产品上则使用来自JNDI的相同的数据源
- 开发期使用监控组件,当部署以后则关闭监控组件,让应用更高效
- 为用户各自注册自定义bean实现
考虑一个实际应用中的场景,现在需要一个DataSource。开测试环境中,这样配置:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在让我们考虑如何将此应用程序部署到QA或生产环境中,假设应用程序的数据源将注册到生产应用程序服务器的JNDI目录。 我们的dataSource bean现在看起来像这样:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
看起来很完美,但是现在的问题变成了,如何在当前环境中如何切换这两个配置。总结一下
概括一下上面的场景:你需要在场景A中注册一组bean定义,在场景B中注册另外一组。
@Profile注解
@Profile
注解用于当一个或多个配置文件激活的时候,用来指定组件是否有资格注册。 使用上面的例子,我们可以重写dataSource
配置如下:
@Configuration
@Profile("dev")
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();
}
}
@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");
}
}
如前所述,使用@Bean方法,通常会选择使用JNDI查找:使用Spring的JndiTemplate /JndiLocatorDelegatehelper或上面显示的直接JNDIInitialContext用法,但不是JndiObjectFactoryBean 这将使你声明返回类型必须为FactoryBean类型
自定义组合注解 @Production
,该注解用于替换@Profile("production")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Profile也能注解方法,用于配置一个配置类中的指定bean:
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean
@Profile("production")
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
如果一个@Component或@Configuration类被标记为@Profile({“p1”,“p2”}), 那么除非profile 'p1' 或 'p2' 被激活,否则该类将不会注册/处理。如果是@Profile({“p1”,“!p2”}), 只要p1 激活或者p2没有激活,才会被注册。
思考
如果 @Configuration 有 @Porfile('p1', 'p2'),@bean 有 @Porfile('p3')的时候,激活的是 p1 时,此 bean会注册吗?
1.13.2 XML bean定义profile
XML中的<beans>元素有一个profile属性
<beans profile="dev"
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个文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="dev">
<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">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd强制允许将profile元素定义在文件的最后面,这有助于在XML文件中提供灵活的方式而又不引起混乱。
启用 profile
有几种方式可以启用 profile。
最直接的方式
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
setActiveProfiles()
方法提供多个配置文件的姿势:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明式的使用spring.profiles.active,值可以为逗号分隔的配置文件名列表:
-Dspring.profiles.active="profile1,profile2"
默认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();
}
}
如果没有配置文件激活,上面的dataSource就会被创建。这提供了一种默认的方式。如果有任何一个配置文件启用,default配置就不会生效。
默认配置文件的名字(default)可以通过Environment的setDefaultProfiles方法或者spring.profiles.default属性修改。
1.13.3 PropertySource abstraction
Spring的Environment抽象提供用于一系列的操作 property sources 的接口
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
StandardEnvironment
有两个 PropertySource 对象,一个表示JVM系统属性(ystem.getProperties()),一个表示系统环境变量 (System.getenv())。
StandardEnvironment 是默认的。
StandardServletEnvironment
包括servlet配置和servlet上下文参数。
查询是有优先级。默认情况下,系统属性优先于环境变量,因此如果在调用env.getProperty(“foo”)时,两个地方都设置了foo属性,系统属性值 返回优先于环境变量。
StandardServletEnvironment
的属性顺序:ServletConfig参数 > JNDI环境变量 > JVM系统属性 > JVM系统环境 > 系统环境变量
这个机制是可以配置的
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代码中,“MyPropertySource”在搜索中以最高优先级添加。 如果它包含一个foo属性,,它将会被探测并返回,优先于其他PropertySource中的fooproperty属性。
1.13.4 @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将被用作默认值。 如果未指定默认值,那么property将不能解析,,则将抛出IllegalArgumentException。
1.13.5 Placeholder resolution in statements
以前,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。 现在不再是这种情况。 因为环境抽象集成在整个容器中,所以很容易通过它来来对占位符进行解析。 这意味着你可以以任何你喜欢的方式来配置这个解析过程:可以改变是优先查找系统properties或者是有限查找环境变量,或者删除它们;增加自定义property源,使之成为更合适的。
具体来说,无论“自定义”属性定义在何处,以下语句都会工作,只要它在“Environment”中可用:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>