Environment接口提供两个关于应用环境的数据模型:profiles和properties。
profile是一个有名字的在激活时要被注册的bean定义的组。
既可以用xml定义profile也可以用注解定义。
Environment对象用于确定当前激活的profile,或者默认profile。
Properties在几乎所有的应用中扮演非常重要的角色,而且可以来自不同的地方:
properties文件、JVM系统属性、系统环境变量、JNDI、servlet容器参数等。
和Environment
对象之间的关系是提供用户便捷的属性源和属性解析接口。
用于bean定义的profile:
使用@Profile
,作用在类级别:
@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();
}
}
和:
@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表达式:
@Profile(“production & us-east”)
如果要多个运算符一起用,就必须使用圆括号:
@Profile(“production & (us-east | eu-central)”)
其它操作符也是支持的:
!
: 逻辑“not”&
: 逻辑“and”|
: 逻辑“or”
可以将@Profile作为元注解定制自己的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
注意:默认不对被注解类的@Bean方法和@Import注解生效,除非一个或多个对应的profile被激活。
@Profile注解也可以用于bean工厂方法:
@Configuration
public class AppConfig {
@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();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
注意:如果bean工厂方法有重载的,则所有重载工厂方法的@Profile注解都要保持一致,
如果不一致就会取第一个声明生效,而且也不能用于选择重载方法。
应该使用不同的bean工厂方法名生成同一个name属性的bean,就像上面的例子。
XML配置里的profile:
<beans>元素的profile属性:
<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>
也可以在同一个xml配置文件里面嵌套<beans>元素:
<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="development">
<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>
xml配置不支持profile表达式,但是可以通过嵌套实现逻辑and:
<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="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
production
and us-east
同时激活才会暴露dataSource bean。
激活profile:
可以通过ApplicationContext的Environment API编程式激活profile:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
或者在property里面添加spring.profiles.active属性。
可以同时激活多个profile:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
或环境变量中通过逗号分隔:
-Dspring.profiles.active="profile1,profile2"
默认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被激活就创建dataSource,当有profile被激活时就不创建dataSource。
可以通过Environment API 的setDefaultProfiles()方法设置默认profile名称,
或者声明spring.profiles.default属性。
PropertySource抽象:
Environment对象在一组可配置的层级的PropertySource
对象中搜索属性值,PropertySource
是对于键值对的简单抽象。
Spring的StandardEnvironment
由两个PropertySource对象配置成,一个表示JVM系统属性,另一个表示系统环境变量。
搜索操作具有层级关系,属性变量有优先级,默认系统属性级别大于环境变量,比如my-property属性同时出现在系统属性和环境变量中,则调用env.getProperty(“my-property”)就会返回系统属性中的值。
在StandardServletEnvironment中,完整的优先级顺序是:(1为最高)
- ServletConfig parameters (例如:
DispatcherServlet
context) - ServletContext parameters (web.xml context-param entries)
- JNDI environment variables (
java:comp/env/
entries) - JVM system properties (
-D
command-line arguments) - JVM system environment (操作系统环境变量)
上面的整个机制都是可以配置的,比如添加自己的PropertySource
:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
上面的代码片段中,MyPropertySource被设置成了最高优先级。MutablePropertySources
API暴露了一些精确操作PropertySource
的方法。
使用@PropertySource注解:
方便快捷地向Environment添加PropertySource对象的方法,参数是某个含有键值对信息的文件:
@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
只要在app.properties文件里添加testbean.name=beanName就可给testBean命名。
任何${…}占位符都会在读取Environment之前被解析:
@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作为值,冒号后面是默认值。
如果不提供默认值,当my.placeholder无法解析时就会抛出IllegalArgumentException异常。
@PropertySource可以重复声明,只是需要在同一等级,只能选直接使用还是用定制的注解。混用不同等级的注解时,直接使用@PropertySource会覆盖定制的注解。
语句中的占位符的解析:
历史上,占位符只能解析系统属性和环境变量。
现在只要是在Environment中存在的值都可以解析,无关属性在什么地方:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>