Spring进阶(十五)之@Conditional注解

目录

@Conditional注解

Condition接口

ConditionContext接口

关键性问题

类中有下面任意注解之一的就属于配置类:

Spring对配置类处理过程

ConfigurationCondition接口

@Conditional注解使用的三个步骤

案例一:阻止配置类的处理

案例2:阻止bean的注册

案例3:bean不存在的时候才注册

案例4:ConfigurationCondition使用

案例:


@Conditional注解

@Conditional注解是从spring4.0才有的,可以用在类,接口或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。

比如可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等等

@Conditional源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	
	Class[] value();

}

这个注解只有一个value参数,Condition类型的数组,Condition是一个函数式接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。

下面我们来看一下Condition接口

Condition接口

@FunctionalInterface
public interface Condition {

	
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

该函数式接口内部只有一个matches方法,用来判断条件是否成立的,2个参数:

  • context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的个人信息
  • metadata:用来获取被@Conditional标注的对象上的所有注解信息

ConditionContext接口

看看其源码,混个眼熟:

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容器中

那么什么是配置类呢???

类中有下面任意注解之一的就属于配置类:

  • 类上有@Component等四个注解
  • 类上有@Configration注解
  • 类上有@Import注解
  • 类上有@ImportResource注解
  • 类上有@ComponentScan注解
  • 类中有@Bean注解

判断一个类是不是一个配置类,是否的是下面这个方法:

org.springframework.context.annotation.ConfigurationClassUtils.isConfigurationCandidate()

Spring对配置类处理过程

源码位置:

org.springframework.context.annotation.ConfigurationClassPostProcessor#processCo nfigBeanDefinitions

大致过程如下:

  1. 通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器
  2. spring对传入的多个配置类进行解析
  3. 配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置 类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类 刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析
  4. bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时spring容器会将 这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到spring容器
  5. 经过上面第3个阶段之后,spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的 配置类
  6. Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给 第3步进行处理
  7. step3到step6,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册

从上面过程中可以了解到:

  • 可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解 析,那么这个配置上面6种注解的解析都会被跳过
  • 可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中
  • 如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的 bean都不会被注册到容器

一个配置类被spring处理有2个阶段:配置类解析阶段、bean注册阶段(将配置类作为bean被注册到 spring容器)。

如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition

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注解使用的三个步骤

  • 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
  • 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
  • 启动spring容器加载资源,此时@Conditional就会起作用了

案例一:阻止配置类的处理

在配置类上面使用@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对象输出,说明我们成功阻止了该配置类的解析。

案例2:阻止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并没有受到影响

案例3:bean不存在的时候才注册

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都去掉。

案例4:ConfigurationCondition使用

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对象上

Spring进阶(十五)之@Conditional注解_第1张图片

运行后,发现没什么问题,test<======>test打印出来了。我们把config1中的@Bean注解去掉(不注册service),再运行就会发现test<======>test没有打印出来。实现了上面的需求

你可能感兴趣的:(spring,spring,java,spring,boot)