1.@Conditional是做什么的?
2.@Conditional多个条件是什么逻辑关系?
3.条件判断在什么时候执行?
4.ConfigurationCondition和Condition有什么区别?什么时候使用ConfigurationCondition?
5.多个Condition执行的顺序是什么样的?可以配置优先级么?
6.介绍一下@Conditional常见的一些用法么?
Conditional接口
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
//Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。
Class<? extends Condition>[] value();
}
Condition接口
@FunctionalInterface
public interface Condition {
//context : 条件上下文
// metadata:用来获取被@Conditional标注的对象上的所有注解信息
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionContext 接口
public interface ConditionContext {
/**
* 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回当前spring容器的环境配置信息对象
*/
Environment getEnvironment();
/**
* 返回资源加载器
*/
ResourceLoader getResourceLoader();
/**
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();
}
Conditional是一个条件判断,那什么时候执行?
Spring对配置类的处理主要分为2个阶段:
- 配置类解析阶段:会得到一批配置类的信息,和一些需要注册的bean
- bean注册阶段:将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中
什么是配置类 (ConfigurationClassUtils#isConfigurationCandidate)
- 类上有@Compontent注解
- 类上有@Configuration注解
- 类上有@CompontentScan注解
- 类上有@Import注解
- 类上有@ImportResource注解
- 类中有@Bean标注的方法
如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition
ConfigurationCondition接口
public interface ConfigurationCondition extends Condition {
/**
* 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示阶段的枚举:2个值
*/
enum ConfigurationPhase {
/**
* 配置类解析阶段,如果条件为false,配置类将不会被解析
*/
PARSE_CONFIGURATION,
/**
* bean注册阶段,如果为false,bean将不会被注册
*/
REGISTER_BEAN
}
}
案例 :阻止配置类的处理
//Condition 实现类
public class MyCondition1 implements Condition {
//为false的时候,spring不处理这个配置类
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
配置类:
@Conditional(MyCondition1.class)
@Configuration
public class ConditionConfig {
@Bean
public String name() {
return "ss";
}
}
测试方法:
@Test
public void testCondition() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
Map<String, String> serviceMap = context.getBeansOfType(String.class);
serviceMap.forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
不会有name这个bean出现,当去掉@Conditional注解:
name->ss
案例:阻止bean的注册
上面的案例中,我们只修改配置类就行:
@Configuration
public class ConditionConfig {
//注意,类上的注解@Conditional去掉了。
@Conditional(MyCondition1.class)
@Bean
public String name1() {
return "name1";
}
@Bean
public String name2() {
return "name2";
}
}
运行结果:
name2->name2
Condition指定优先级
@Condtional中value指定多个Condtion的时候,默认情况下会按顺序执行
指定Condition的顺序:
自定义的Condition可以实现PriorityOrdered接口或者继承Ordered接口,或者使用@Order注解,通过这些来指定这些Condition的优先级。
排序规则:先按PriorityOrdered排序,然后按照order的值进行排序;也就是:PriorityOrdered asc,order值 asc
小伙伴可以自行测试一下,就不写案例了。
ConfigurationCondition使用
先来个案例,认识下ConfigurationCondition。
需求:有个Service的bean,和String类型的bean。当容器中有Service的Bean的时候,才能注册String类型的bean
public class Service {
}
@Configuration
public class ServiceConfig {
@Bean
public Service service() {
return new Service();
}
}
@Configuration
public class StringConfig {
@Conditional(MyCondition.class)
@Bean
public String name() {
return "BeanConfig2:name";
}
}
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取spring容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判断容器中是否存在Service类型的bean
boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
return existsService;
}
}
@Configuration
@Import({ServiceConfig.class, StringConfig.class})
public class MainConfig {
}
测试:
@Test
public void testCondition2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
context.getBeansOfType(String.class).forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
运行:发现没有String类型的bean.
原因:Condition对配置类是有两个过程:解析和注册。而在Condition实现类的时候,解析过程,还没有发现Service类型的bean,返回false,就不会注册这个String类型的bean了。所以我们考虑使用ConfigurationCondition。
ConfigurationCondition源码:
public interface ConfigurationCondition extends Condition {
//在什么阶段执行
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
//解析阶段
PARSE_CONFIGURATION,
//注册阶段
REGISTER_BEAN
}
}
我们使用ConfigurationCondition 接口,定义一个实现类:
public class MyConfigurationCondition1 implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取spring容器
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//判断容器中是否存在Service类型的bean
boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
return existsService;
}
}
修改StringConfig 类:
@Configuration
public class StringConfig {
@Conditional(MyConfigurationCondition1.class)
@Bean
public String name() {
return "BeanConfig2:name";
}
}
测试方法不变,运行,就可以得到我们想要的结果。
@Conditional注解是被这个类处理的:ConfigurationClassPostProcessor,大家还是要多撸一下这个类的源码