更新时间:2022-09-03 18:04:02
在容器中如果存在同一类型的多个组件,也可以使用@Profile注解标识要获取的是哪一个bean,这在不同的环境使用不同的变量的情景特别有用。例如,开发环境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下,可以使用这个注解来切换要连接的数据库。
步骤如下:
注:2、4、5步其实是带参构造方法的步骤,相当于把带参构造方法拆开,在其中插入一条语句设置容器环境,这些我们可以在Spring的源码中可以看出,比如下面的代码。
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { this(); register(annotatedClasses); refresh(); }
接下来,我们再来看下@Profile注解的源码,如下所示。
package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Profiles; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { String[] value(); }
注意:@Profile不仅可以标注在方法上,也可以标注在配置类上。如果标注在配置类上,只有在指定的环境时,整个配置类里面的所有配置才会生效。如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中
接下来,我们就一起来搭建使用@Profile注解实现开发、测试和生产环境的配置和切换的环境。这里,我们以不同的数据源为例。首先,我们在pom.xml文件中添加c3p0和MySQL驱动的依赖,如下所示。
<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency>
添加完项目依赖之后,我们在项目中新建ProfileConfig配置类,并在ProfileConfig配置类中模拟开发、测试、生产环境的数据源,如下所示。
package io.mykit.spring.plugins.register.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /** * @author binghe * @version 1.0.0 * @description 测试多数据源 */ @Configuration public class ProfileConfig { @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Bean("testDataSource") public DataSource dataSourceTest() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Bean("prodDataDource") public DataSource dataSourceProd() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_prod"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } }
这个类相对来说比较简单,其中使用 @Bean("devDataSource")注解标注的是开发环境使用的数据源;使用 @Bean("testDataSource")注解标注的是测试环境使用的数据源;使用@Bean("prodDataDource")注解标注的是生产环境使用的数据源。
接下来,我们创建ProfileTest类,并在ProfileTest类中新建一个testProfile01()方法来进行测试,如下所示。
package io.mykit.spring.test; import io.mykit.spring.plugins.register.config.ProfileConfig; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import javax.sql.DataSource; import java.util.stream.Stream; /** * @author binghe * @version 1.0.0 * @description 测试类 */ public class ProfileTest { @Test public void testProfile01(){ //创建IOC容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProfileConfig.class); String[] names = context.getBeanNamesForType(DataSource.class); Stream.of(names).forEach(System.out::println); } }
运行ProfileTest类的testProfile01()方法,输出的结果信息如下所示。
devDataSource testDataSource prodDataDource
可以看到三种不同的数据源成功注册到了IOC容器中,说明我们的环境搭建成功了。
我们成功搭建环境后,接下来,就是要实现根据不同的环境来向IOC容器中注册相应的bean。也就是说,我们要实现在开发环境注册开发环境下使用的数据源;在测试环境注册测试环境下使用的数据源;在生产环境注册生产环境下使用的数据源。此时,@Profile注解就显示出其强大的特性了。
我们在ProfileConfig类中为每个数据源添加@Profile注解标识,如下所示。
@Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Profile("test") @Bean("testDataSource") public DataSource dataSourceTest() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; } @Profile("prod") @Bean("prodDataDource") public DataSource dataSourceProd() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_prod"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; }
我们使用@Profile("dev")注解来标识在开发环境下注册devDataSource;使用@Profile("test")注解来标识在测试环境下注册testDataSource;使用@Profile("prod")注解来标识在生产环境下注册prodDataDource。
此时,我们运行ProfileTest类的testProfile01()方法,发现命令行并未输出结果信息。说明我们为不同的数据源添加@Profile注解后,默认是不会向IOC容器中注册bean的,需要我们根据环境显示指定向IOC容器中注册相应的bean。
换句话说:通过@Profile注解加了环境标识的bean,只有这个环境被激活的时候,相应的bean才会被注册到IOC容器中。
如果我们需要一个默认的环境怎么办呢?
此时,我们可以通过@Profile("default")注解来标识一个默认的环境,例如,我们将devDataSource环境标识为默认环境,如下所示。
@Profile("default") @Bean("devDataSource") public DataSource dataSourceDev() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test_dev"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); return dataSource; }
此时,我们运行ProfileTest类的testProfile01()方法,输出的结果信息如下所示。
devDataSource
可以看到,我们在devDataSource数据源上使用@Profile("default")注解将其设置为默认的数据源,运行测试方法时命令行会输出devDataSource。
接下来,我们将devDataSource数据源的@Profile("default")注解还原成@Profile("dev")注解,标识它为一个开发环境下注册的数据源。
那么,我们如何根据不同的环境来注册相应的bean呢?
第一种方式就是根据命令行参数来确定环境,我们在运行程序的时候可以添加相应的命令行参数,例如,我们现在的环境是测试环境,那可以在运行程序的时候添加如下命令行参数。
-Dspring.profiles.active=test
第二种方式就是通过AnnotationConfigApplicationContext类的无参构造方法来实现。我们在程序中调用AnnotationConfigApplicationContext的无参构造方法来生成IOC容器,在容器进行初始化之前,我们就为IOC容器设置相应的环境,然后再为IOC容器设置主配置类。例如,我们将IOC容器设置为生产环境,如下所示。
@Test public void testProfile02(){ //创建IOC容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getEnvironment().setActiveProfiles("prod"); context.register(ProfileConfig.class); context.refresh(); String[] names = context.getBeanNamesForType(DataSource.class); Stream.of(names).forEach(System.out::println); }
此时,我们运行testProfile02()方法,输出的结果信息如下所示。
prodDataDource
可以看到,命令行输出了prodDataDource,说明我们成功将IOC环境设置为了生产环境。
@Profile不仅可以标注在方法上,也可以标注在配置类上。如果标注在配置类上,只有在指定的环境时,整个配置类里面的所有配置才会生效。例如,我们在ProfileConfig类上标注@Profile("dev")注解,如下所示。
@Profile("dev") @Configuration public class ProfileConfig { /*********代码省略*********/ }
接下来,我们运行testProfile02()方法,发现命令行中未输出任何信息。
这是因为我们在testProfile02()方法中指定了当前的环境为生产环境,而ProfileConfig类上标注的注解为@Profile("dev"),说明ProfileConfig类中的所有配置只有在开发环境下才会生效。所以,此时没有任何数据源注册到IOC容器中,命令行不会打印任何信息。