手写一个SpringBoot ConditionOn*

SpringBoot自带的Conditional

打开任意*AutoConfiguration文件,一般都有下面的条件注解,在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.condition包下条件注解如下:

  • @ConditionalOnBean:当前容器有指定Bean的条件下。
  • @ConditionalOnClass:当前类路径下有指定的类的条件下。
  • @ConditionalOnExpression:基于SpEL表达式作为判断条件。
  • @ConditionalOnJava:基于JVM版本作为判断条件。
  • @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
  • @ConditionalOnMissingBean:当容器里没有指定Bean的情况下。
  • @ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
  • @ConditionalOnNotWebApplication:当前项目不是WEB项目的条件下。
  • @ConditionalOnProperty:指定属性是否有指定的值。
  • @ConditionalOnResource:类路径是否有指定的值。
  • @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但 是指定首选的Bean。
  • @ConditionalOnWebApplication:当前项目是WEB项目的条件下。

这些注解都组合了@Conditional元注解,只是使用了不同的条件(Conditional),Spring 条件注解(@Conditional)我们介绍过根据不同条件创建不同Bean。
虽然平时使用的多,但是没怎么了解过是怎么实现的,其实就是实现org.springframework.context.annotation.Condition的不同子类做不同的扩展即可。

手写一个

注解

首先写一个ConditionOnOS注解

/**
 * @author imyzt
 * @date 2021/01/02
 * @description 是否是对应OS
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnOSCondition.class)
public @interface ConditionOnOS {


    /**
     * 系统类型
     */
    OSType osType();


    public enum OSType {
        MAC,
        LINUX,
        WINDOWS,
        ;
    }
}

处理程序

然后写一个处理程序

@Order
public class OnOSCondition implements ConfigurationCondition {


    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }


    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {


        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnOS.class.getName());
        ConditionOnOS.OSType osType = (ConditionOnOS.OSType) attributes.get("osType");


        boolean contains = System.getProperty("os.name").toUpperCase().contains(osType.name());


        return contains;
    }
}

测试程序

最后写一个启动程序

package top.imyzt.learning.condition;


import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


/**
 * @author imyzt
 * @date 2021/01/02
 * @description 手写一个ConditionOn*
 */
@ComponentScan("top.imyzt.learning.condition")
@Configuration
public class ConditionOnTest {


    public static void main(String[] args) {


        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();


        annotationConfigApplicationContext.register(ConditionOnTest.class);


        annotationConfigApplicationContext.refresh();


        HelloWorld helloWorld;
        try {
            helloWorld = (HelloWorld) annotationConfigApplicationContext.getBean("helloWorld");
        } catch (NoSuchBeanDefinitionException e) {
            System.out.println("helloWorld bean is not exists!");
            return;
        }


        helloWorld.say();


    }


    @Bean
    @ConditionOnOS(osType = ConditionOnOS.OSType.MAC)
    public HelloWorld helloWorld() {
        return new HelloWorld(ConditionOnOS.OSType.MAC.name());
    }


    public class HelloWorld{
        private String name;


        public HelloWorld(String name) {
            this.name = name;
        }


        public void say() {
            System.out.println(this.name + " --- say hello!");
        }
    }
}

测试

正向

我的电脑是Mac,所以我执行后的结果是

…省略
15:42:08.868 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'conditionOnTest'
15:42:08.873 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloWorld'
MAC --- say hello!

反向

当将注解的参数修改为 ConditionOnOS.OSType.LINUX 后,结果如下

15:42:59.405 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'conditionOnTest'
helloWorld bean is not exists!

讲解

其中自主要的是在注解ConditionOnOS上面的@Conditional(OnOSCondition.class)元注解,它指定了判断的处理程序是OnOSCondition.class。
其次是OnOSCondition通过实现ConfigurationCondition来重写判断逻辑。

getConfigurationPhase()

getConfigurationPhase()用于提供给Spring告知其构建阶段,有两个:

  • PARSE_CONFIGURATION: 一个是Condition应评估@Configuration类,如果此时条件不匹配,@Configuration 则不会添加该类。
  • REGISTER_BEAN: 该条件不会阻止@Configuration添加类,在评估条件时,所有@Configurations都将被解析。

ConfigurationPhase的作用并不是根据条件来判断是否加载这个配置类,实际ConfigurationPhase控制的是过滤的时机,是在创建Configuration类的时候过滤还是在创建bean的时候过滤(也可用条件注解的生效阶段来描述)。
此处@https://blog.csdn.net/xcy1193068639/article/details/81589489

enum ConfigurationPhase {


   /**
    * The {@link Condition} should be evaluated as a {@code @Configuration}
    * class is being parsed.
    * <p>If the condition does not match at this point, the {@code @Configuration}
    * class will not be added.
    */
   PARSE_CONFIGURATION,


   /**
    * The {@link Condition} should be evaluated when adding a regular
    * (non {@code @Configuration}) bean. The condition will not prevent
    * {@code @Configuration} classes from being added.
    * <p>At the time that the condition is evaluated, all {@code @Configuration}
    * classes will have been parsed.
    */
   REGISTER_BEAN
}

matches()

matches()方法用于重写匹配逻辑,此方法根据自己的业务逻辑重写即可。

后文

本篇文章的源代码位置:https://github.com/imyzt/learning-technology-code/tree/master/source-code/xiaomage-springboot-teach/spring-boot-condition