目录
@Conditional注解
Condition接口
ConditionContext接口
关键性问题
类中有下面任意注解之一的就属于配置类:
Spring对配置类处理过程
ConfigurationCondition接口
@Conditional注解使用的三个步骤
案例一:阻止配置类的处理
案例2:阻止bean的注册
案例3:bean不存在的时候才注册
案例4:ConfigurationCondition使用
案例:
@Conditional注解是从spring4.0才有的,可以用在类,接口或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
比如可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等等
@Conditional源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class extends Condition>[] value();
}
这个注解只有一个value参数,Condition类型的数组,Condition是一个函数式接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。
下面我们来看一下Condition接口
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
该函数式接口内部只有一个matches方法,用来判断条件是否成立的,2个参数:
看看其源码,混个眼熟:
public interface ConditionContext {
//返回bean定义注册器,可以通过其获取bean的各种配置信息
BeanDefinitionRegistry getRegistry();
//返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
//返回当前spring容器的环境配置信息对象
Environment getEnvironment();
//返回资源加载器
ResourceLoader getResourceLoader();
//返回类加载器
@Nullable
ClassLoader getClassLoader();
}
spring对配置类的处理主要分为两个阶段:
配置类解析阶段
该阶段会得到一些配置类的信息,和一些需要注册的bean的信息
bean注册阶段
将配置类解析阶段得到的 配置类 和 需要注册的bean 注册到spring容器中
那么什么是配置类呢???
判断一个类是不是一个配置类,是否的是下面这个方法:
org.springframework.context.annotation.ConfigurationClassUtils.isConfigurationCandidate()
源码位置:
org.springframework.context.annotation.ConfigurationClassPostProcessor#processCo nfigBeanDefinitions
大致过程如下:
从上面过程中可以了解到:
一个配置类被spring处理有2个阶段:配置类解析阶段、bean注册阶段(将配置类作为bean被注册到 spring容器)。
如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition
源码:
public interface ConfigurationCondition extends Condition {
/**
* 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示阶段的枚举:2个值
*/
enum ConfigurationPhase {
/**
* 配置类解析阶段,如果条件为false,配置类将不会被解析
*/
PARSE_CONFIGURATION,
/**
* bean注册阶段,如果为false,bean将不会被注册
*/
REGISTER_BEAN
}
}
ConfigurationCondition接口相对于Condition接口多了一个getConfigurationPhase方法,用来指定条 件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤。
在配置类上面使用@Conditional,这个注解的value数组中指定的Condition只要有一个为false,spring都会跳过处理这个配置类。
public class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
@Conditional(MyCondition1.class)
@Configuration
public class MainConfig1 {
@Bean
public String test(){
return "test";
}
}
class ConditionalTestApplicationTests {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"==>"+context.getBean(beanName));
}
}
}
运行后并无bean对象输出,说明我们成功阻止了该配置类的解析。
public class MyCondition2 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
@Configuration
public class MainConfig2 {
@Conditional(MyCondition2.class)
@Bean
public String test1(){
return "test1";
}
@Bean
public String test2(){
return "test2";
}
}
class ConditionalTestApplicationTests {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"==>"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig2==>com.example.Dome2.MainConfig2$$EnhancerBySpringCGLIB$$96268b20@4d154ccd
test2==>test2
说明并没有阻止配置类的解析,而是阻止了bean的注册。@Conditional加在test1方法上,只有当@Conditional指定的Condition类型返回值为true时,才能创建test1对象。且test2并没有受到影响
public interface IService {
}
public class Service1 implements IService{
}
public class Service2 implements IService{
}
public class OnMissingBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map beansOfType = beanFactory.getBeansOfType(IService.class);
return beansOfType.isEmpty();
}
}
@Configuration
public class MainConfig3 {
@Conditional(OnMissingBeanCondition.class)
@Bean
public Service1 service1(){
return new Service1();
}
@Conditional(OnMissingBeanCondition.class)
@Bean
public Service2 service2(){
return new Service2();
}
}
class ConditionalTestApplicationTests {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"==>"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig3==>com.example.Dome3.MainConfig3@b9b00e0
service1==>com.example.Dome3.Service1@506ae4d4
发现service1和service2中只有一个能成功注入到容器。要想把两个都注册到容器,需要把两个@Conditioal都去掉。
ConfigurationCondition使用的比较少,很多地方对这个基本上也不会去介绍,Condition接口基本上可 以满足99%的需求了,但是springboot中却大量用到了ConfigurationCondition这个接口。
来一个普通的类:Service1
public class Service1 {
}
来一个配置类,通过配置类注册上面这个Service
@Configuration
public class config1 {
@Bean
public Service1 service1(){
return new Service1();
}
}
再来一个配置类:config2
@Configuration
public class config2 {
@Bean
public String test(){
System.out.println("test<======>test");
return "test";
}
}
来一个总的配置类
@Configuration
@Import({config1.class,config2.class})
public class MainConfig {
}
class ConditionalTestApplicationTests {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"==>"+context.getBean(beanName));
}
}
}
运行输出没有任何问题,但现在我们还有一个需求:当容器中有Service1这种类型的bean的时候,config2才生效
再来个来个自定义的Condition
public class MyCondition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map beansOfType = beanFactory.getBeansOfType(Service1.class);
return !beansOfType.isEmpty();
}
}
将该自定义的Condition加在config2中的bean对象上
运行后,发现没什么问题,test<======>test打印出来了。我们把config1中的@Bean注解去掉(不注册service),再运行就会发现test<======>test没有打印出来。实现了上面的需求