原文地址:http://docs.spring.io/spring/docs/5.0.0.M4/spring-framework-reference/htmlsingle/#beans-property-source-abstraction
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");
}
问题是,在当前环境中如何切换这两个配置。随着时间推移,Spring用户设计了很多种方式完成此切换,通常使用系统环境变量和XML<import/>
绑定,<import/>
元素包含一个${placeholder}
符号,使用环境变量来设置${placeholder}
符号所代表的值,从而达到切换正确配置文件的目的。bean定义profiles是核心容器功能,提供针对子问题的解决方案。
概括一下上面的场景:环境决定bean定义,最后发现,我们需要在某些上下文环境中使用某些bean,在其他环境中则不用这些bean。你也许会说,你需要在场景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类型
@Profile
可以用作meta-annotation
,用于创建自定义组合注解。 下面的例子定义了一个自定义@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");
}
}
如果@Configuration
类标有@Profile
,类中@Bean
和@Import
注解相关的类都将被忽略,除非该profile被激活。 如果一个@Component
或@Configuration
类被标记为@Profile({“p1”,“p2”})
,那么除非profile'p1'
和/或’p2’ 已被激活。否则该类将不会注册/处理。 如果给定的配置文件以NOT运算符(!)
为前缀,如果配置文件为 not active,则注册的元素将被注册。 例如,给定@Profile({“p1”,“!p2”})
,如果配置文件’p1’激活或配置文件’p2’没有激活,则会注册。
XML bean 定义 profiles
XML配置是作为<beans>
的一个属性, 我们上面的示例配置能够用下面的两个XML文件来重写, 如下:
<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>
避免上面两个文件分开也是有可能的, 通过在相同的文件中嵌套<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="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>
激活profile
既然我们已经更新了配置, 我们仍然需要告知Spring哪一个profile 是激活的。 如果我们现在就启动我们的示例应用, 我们将会看到NoSuchBeanDefinitionException
抛出, 因为容器不能找到名为dataSource
的Spring bean。
可以采用一些方式激活profile, 但是最直接的方式是依靠Environment
API用编程方式来配置,通过ApplicationContext
来获取Environment
:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
另外, profiles 可能通过spring.profiles.active
属性来声明式的激活, 此属性可能在系统环境变量, JVM系统属性, web.xml
定义的 servlet上下文参数中被指定, 或着甚至可以作为JNDI的一个元组(entry), 在集成测试中。 在spring-test
模块中可以通过@ActiveProfiles
注解来激活profiles。
注意 profiles 并不是“或着-或着”的命题, 一次性激活多个profiles也是有可能的, 编程式地,简单的向setActiveProfiles()
方法中提供多个profile名称, 它接受String...
参数:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明式地配置,spring.profiles.active
可以接受以逗号分隔的profile名称列表:
-Dspring.profiles.active="profile1,profile2"
默认profile
默认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
将会被创建; 这可以被看作是为一个或多个beans提供一个默认定义 的一种方式。如果有任一bean被激活, 默认profile将不会生效。
默认profile的名称可以被改变, 在Environment
中使用setDefaultProfiles()
, 或着声明式的使用spring.profiles.default
属性。