@Profile注解 -【Spring底层原理】-爱代码爱编程
目录
一、注解用法
在我们开发开发测试部署当中,有不同的环境,比如有:开发环境、测试环境、上产环境,不同的环境有不同的组件,这听着怎么那么像springboot中的多环境配置呢?今天,咱们来看看在spring中是如何实现的。
为了多环境开发,Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能,比如数据源组件的配置,不同开发环境连接的数据源可能会不同,就可以使用@Profile注解进行配置,根据环境动态切换数据源组件。
@Profile:指定组件在哪个环境下才能被注册到容器中,不指定则任何环境都能注册这个组件,加了环境标识的bean,只有这个环境被激活的时候才能注册到容器
二、实例分析
就以数据源配置为例,不同环境数据源的配置往往不同,如何使用@Profile注解在不同环境下进行数据源的注册呢,通过实例来进行分析。
【1】@Profile环境搭建
// 启动测试类
@Test
public void TestMain() {
// 创建IOC容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String string : namesForType) {
System.out.println(string);
}
// applicationContext.close();
}
// 配置类
@Configuration
public class AppConfig {
// 测试环境
// @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("806188");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
}
// 开发环境
// @Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("806188");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
}
// 生产环境
// @Profile("pro")
@Bean("proDataSource")
public DataSource dataSourcePro() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("806188");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
}
}
可以看到,在配置类中,有不同的数据源配置,分别是测试环境、开发环境、生产环境,我们可以通过@Profile注解来指定注入哪种环境,当不指定则任何环境都能注册这个组件,也就是上面代码,运行测试类,输出结果如下,三个数据源组价都进行了注入:
将数据源使用@Profile注解进行标识,也就是将上面代码三个@Profile注解打开,此时因为没有激活注册环境,所以这个三个数据源都不能被注入。下面来进行激活。
【2】激活注册环境
- default默认环境:
@Profile("default")
- 使用命令行动态参数:
-Dspring.profiles.active=dev
- 使用代码手动激活指定环境:要使用无参构造方法
- 配置在配置类上:只有在指定环境的时候,整个配置类的所有配置才能生效
- default默认环境,使用
@Profile("default")
,标识默认当前环境
// 测试环境
@Profile("default")
// @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("806188");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
}
- 使用命令行动态参数,编辑运行配置的VM options,参数为:
-Dspring.profiles.active=dev
,通过该配置来标注是何种环境
运行启动类,可以看到devDataSource
被注入:
- 使用代码手动激活指定环境,使用这种方法不能让有参构造器代码执行,因为执行有参构造器加载配置类的时候,执行refresh()方法容器就启动刷新了,就将配置写死了,所以这里要用无参构造器,手动激活指定环境。修改启动类:
@Test
public void TestMain() {
// 创建IOC容器
// AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("pro");
// 注册主配置类
applicationContext.register(AppConfig.class);
// 启动刷新容器
applicationContext.refresh();
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String string : namesForType) {
System.out.println(string);
}
}
修改后,手动设置需要激活的环境,运行启动类,输出结果如下:
- 在配置类上加@Profile注解,则只有在指定环境的时候,整个配置类的所有配置才能生效,如下:
@Profile("test")
@Configuration
public class AppConfig {...}
运行启动类,此时启动类是手动代码配置了pro环境,因为配置类上是@Profile("test")
,只有在test环境下该配置类才会生效,所以没有输出:
三、源码追踪
查看@Profile注解源码,我们可以看到,@Profile实际上是一个@Conditional注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}
@Conditional
注解在前面的文章讲解过,可以进行参考:@Conditional注解 -【Spring底层原理】,这里简单分析一下
@Conditinal
是一个条件注解,参数是一个class,这个class都要实现Condition
接口,重写matches()
方法。例如,上面示例代码中的ProfileCondition.class
class ProfileCondition implements Condition {
ProfileCondition() {
}
// Spring从ConditionContext中拿到激活的Profile和注解上的字符串进行比对,以决定是否实例化这个bean
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
Iterator var4 = ((List)attrs.get("value")).iterator();
Object value;
do {
if (!var4.hasNext()) {
return false;
}
value = var4.next();
} while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value))));
return true;
} else {
return true;
}
}
}
mathces()
方法的返回值是一个布尔值,返回true时,spring就会创建这个bean,返回false时就不会创建mathes()
方法上有两个参数,分别是ConditionContext
和AnnotatedTypeMetadata
,这连个参数中包含了大量的信息,ConditionContext
中有Environment
,ClassLoader
等信息,AnnotatedTypeMetadata
可以获得注解的信息- Spring从
ConditionContext
中拿到激活的Profile和注解上的字符串进行比对,以决定是否实例化这个bean
四、总结
@Profile注解是用来指定组件在哪个环境下才能被注册到容器中的,只有这个环境被激活的时候才能注册到容器,激活总结为以下几种方式:
- default默认环境:
@Profile("default")
- 使用命令行动态参数:
-Dspring.profiles.active=dev
- 使用代码手动激活指定环境:要使用无参构造方法
- 配置在配置类上:只有在指定环境的时候,整个配置类的所有配置才能生效