Q:什么是 profile?
A:可以理解为我们在Spring容器中所定义的Bean的逻辑组名称。只有当这些 Profile 被激活的时候,才会将 Profile 中所对应的 Bean 注册到 Spring 容器中。
Q:为什么要使用 profile bean?
A: 因为在不同的环境中某个 bean 会有所不同。我们就必须得有一种方法来配置 bean,使其在每种环境下都会选择最为合适的配置。
其中一种方法:在单独的配置类(或 XML 文件)中配置每个 bean,然后在构建阶段(可能会使用 Maven 的 profiles)确定要将哪一个配置编译到可部署的应用中。然而,这种方法的问题在于要为每种环境重新构建应用。比如:发开阶段 -> QA 阶段时,需要重新构建。QA 阶段 -> 开发阶段,需要重新构建。
而 Spring 所提供的解决方案(profile bean):不需要重新构建。他不是在构建阶段做出决策,而是等到运行时再来确定要将哪一个配置编译到可部署的应用中。这样的好处就是:同一个部署单元(可能会是 WAR 文件)能够适用于所有的环境,没有必要进行重新构建。
如:在开发中,通常会出现在开发的时候使用一个开发数据库,测试的时候使用一个测试的数据库,而实际部署的时候需要一个数据库。环境的更改,每次都需要修改配置文件。但是使用 profile 就不需要了。
在 Spring 3.1 中,Spring 引入了 bean profile 的功能。
Q:怎么使用 profile?
A:首先要将所有不同的 bean 定义整理到一个或多个 profile 之中,再将应用部署到每个环境时,要确保对应的 profile 处于激活的状态。
Q:怎么配置 profile bean?
在 JavaConfig 中配置 profile
方式一:单个创建
package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.activation.DataSource;
/**
* 在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile。
* 在配置类中,嵌入式数据库的 DataSource 可能会配置成如下所示。
*
* 注意:@Profile 注解应用在类级别上,它会告诉 Spring 这个配置类中的 bean 只有在 dev profile 激活时才会创建。
* 如果 dev profile 没有激活的话,那么带有 @Bean 注解的方法都会被忽略掉。
*/
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
@Bean
public DataSource dataSource(){
return (DataSource) new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
可能还需要一个适用于生产环境的配置:
package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.activation.DataSource;
/**
* 只有 prod profile 激活时,才会创建对应的 bean。
*/
@Configuration
@Profile("prod")
public class ProductionProfileConfig {
@Bean
public DataSource dataSource(){
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
在 Spring 3.1 中,只能在类级别上使用 @Profile 注解。不过,从 Spring 3.2 开始,你也可以在方法级别上使用 @Profile 注解,与 @Bean 注解一同使用。这样的话,就能将这两个 bean 的声明放到同一个配置类之中。
方式二:放在同一个配置类之中
package com.myapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.activation.DataSource;
/**
* @Profile 注解基于激活的 profile 实现 bean 的装配
*
* 注意:尽管每个 DataSource bean 都被声明在一个 profile 中,并且只有当规定的 profile 激活时,
* 相应的 bean 才会被创建,但是可能会有其他的 bean 并没有声明在一个给定的 profile 范围内。
* 没有指定 profile 的 bean 始终都会被创建,与激活哪个 profile 没有关系。
*/
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev") //为 dev profile 装配的 bean
public DataSource embeddedDataSource(){
return (DataSource) new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod") //为 prod profile 装配的 bean
public DataSource jndiDataSource(){
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
在 XML 中配置 profile
方式一:单个配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"
profile="dev">
<!-- 通过<beans>元素的 profile 属性,在 XML 配置中配置 profile bean -->
<!--
定义了适用于开发阶段的嵌入式数据库 DataSource bean,
也可以将 profile 设置成 prod,创建适用于生产环境的从 JNDI 获取的 DataSource bean
-->
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
</beans>
方式二:重复使用元素来指定多个 profile
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<!--
这里的两个 bean ,类型都是 javax.sql.DataSource,并且ID 都是 dataSource
但是运行时,只会创建一个 bean,这取决于处于激活状态的是哪个 profile
-->
<!-- dev profile 的 bean -->
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
</beans>
<!-- prod profile 的 bean -->
<beans profile="prod">
<jee:jndi-lookup jndi-name="jdbc/myDatabase" id="dataSource" resource-ref="true"
proxy-interface="javax.sql.DataSource"/>
</beans>
</beans>
Q:怎么激活 profile?
Spring 在确定哪个 profile 处于激活状态时,需要依赖两个独立的属性:spring.profile.active 和 spring.profile.default。Spring 会先查找 active 再找 default,如果两个均没有设置的话,那就没有激活的 profile,因此只会创建哪些没有定义的在 profile 中的 bean。
Q:有哪些方式是来设置这两个属性呢?
A:
- 作为 DispatcherServlet 的初始化参数;
- 作为 Web 应用的上下文参数;
- 作为 JNDI 条目;
- 作为环境变量;
- 作为 JVM 的系统属性;
- 在集成测试上,使用 @ActiveProfiles 注解设置。
在 Web 应用中,设置 spring.profiles.default 的 web.xml 文件如下所示
这里采用的方法:使用 DispatcherServlet 的初始化参数将 spring.profiles.default 设置为开发环境的 profile,我会在 Servlet 上下文中进行设置(为了兼顾到 ContextLoaderListener)。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- bean 配置所在的 xml -->
<param-value>classpath:datesource.xml</param-value>
</context-param>
<!-- 为上下文设置默认的 profile:为了兼顾到 ContextLoaderListener -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<!-- 需要导入 spring-web -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 需要导入 spring-webmvc -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 为 servlet 设置默认的 profile -->
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
然后,当应用程序部署到 QA、生产或其他环境之中时,负责部署的人根据情况使用系统属性、环境变量或 JNDI 设置 spring.profiles.active 即可。当设置了 active 以后,default 的值就无所谓了。
Q:怎么同时激活多个 profile?
A:通过列出多个 profile 名称,并以逗号分隔来实现。
当运行继承测试时,通常会希望采用与生产环境(或者是生产环境的部分子集)相同的配置进行测试。
如果配置中的 bean 定义在了 profile 中,那么在运行测试时,就需要一种方式来启用合适的 profile。
Q:如何使用 profile 进行测试?
Spring 提供了@ActiveProfiles 注解,我们可以使用它来指定运行测试时需要激活哪个 profile。在集成测试时,通常想要激活的是开发环境的 profile。
/**
* 使用 @ActiveProfiles 激活 dev profile
* <p>
* SpringJUnit4ClassRunner 需要导入 spring-test 包
*/
@RunWith(SpringJUnit4ClassRunner.class)
// 设置配置文件
@ContextConfiguration("classpath:datesource.xml")
//@ContextConfiguration(classes = {PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
}
在条件化创建 bean 方面,Spring 的 profile 机制是一种很棒的方法,这里的条件要基于哪个 profile 处于激活状态来判断。
Spring 4.0 中提供了一种更为通用的机制来实现条件化的 bean 定义,在这种机制中,条件完全由你来确定。那就是 @Conditional 注解定义条件化的 bean
上一篇:自动装配、JavaConfig、XML 三种方案之间,怎么导入和混合配置?
下一篇:高级装配 —— 条件化的 bean