@Conditional介绍

前言

上一篇文章介绍了SpringBoot的Endpoint,这里再介绍下@Conditional。
SpringBoot的AutoConfig内部大量使用了@Conditional,会根据运行环境来动态注入Bean。这里介绍一些@Conditional的使用和原理,并自定义@Conditional来自定义功能。
Conditional

@Conditional是SpringFramework的功能,SpringBoot在它的基础上定义了@ConditionalOnClass,@ConditionalOnProperty的一系列的注解来实现更丰富的内容。
观察@ConditionalOnClass会发现它注解了@Conditional(OnClassCondition.class)。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

OnClassCondition则继承了SpringBootCondition,实现了Condition接口。

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

查看SpringFramework的源码会发现加载使用这些注解的入口在ConfigurationClassPostProcessor中,这个实现了BeanFactoryPostProcessor接口,前面介绍过,会嵌入到Spring的加载过程。
这个类主要是从ApplicationContext中取出Configuration注解的类并解析其中的注解,包括 @Conditional,@Import和 @Bean等。
解析 @Conditional 逻辑在ConfigurationClassParser类中,这里面用到了 ConditionEvaluator 这个类。

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    ......
}

ConditionEvaluator中的shouldSkip方法则使用了 @Conditional中设置的Condition类。

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }
    if (phase == null) {
        if (metadata instanceof AnnotationMetadata &&
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }
    List<Condition> conditions = new ArrayList<Condition>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }
    AnnotationAwareOrderComparator.sort(conditions);
    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        if (requiredPhase == null || requiredPhase == phase) {
            if (!condition.matches(this.context, metadata)) {
                return true;
            }
        }
    }
    return false;
}
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
        Object values = (attributes != null ? attributes.get("value") : null);
        return (List<String[]>) (values != null ? values : Collections.emptyList());
}

自定义Conditional

所以自定义Conditional就是通过自定义注解和Condition的实现类。完整的代码在Github上了

定义@ConditionalOnMyProperties

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMyPropertiesCondition.class)
public @interface ConditionalOnMyProperties {
    String name();
}

定义OnMyPropertiesCondition,这里继承了SpringBootCondition重用了部分功能,然后再getMatchOutcome实现了自定义的功能。

public class OnMyPropertiesCondition extends SpringBootCondition {
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Object propertiesName = metadata.getAnnotationAttributes(ConditionalOnMyProperties.class.getName()).get("name");
        if (propertiesName != null) {
            String value = context.getEnvironment().getProperty(propertiesName.toString());
            if (value != null) {
                return new ConditionOutcome(true, "get properties");
            }
        }
        return new ConditionOutcome(false, "none get properties");
    }
}

ConditionalOnMyProperties使用类,还要加上Configuration注解才能生效。

@Configuration
@ConditionalOnMyProperties(name = "message")
public static class ConditionClass {
    @Bean
    public HelloWorld helloWorld() {
        return new HelloWorld();
    }
}
private static class HelloWorld {
    public void print() {
        System.out.println("hello world");
    }
}

入口类,这里运行两次SpringApplication,传入的参数不同,第一次取Bean会抛出Bean不存在的异常,第二次就会正常输出。

@Configuration
@EnableAutoConfiguration
public class CustomizeConditional {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(CustomizeConditional.class);
        springApplication.setWebEnvironment(false);
        ConfigurableApplicationContext noneMessageConfigurableApplicationContext = springApplication.run("--logging.level.root=ERROR","--endpoints.enabled=false");
        try {
            noneMessageConfigurableApplicationContext.getBean(HelloWorld.class).print();
        } catch (Exception e) {
            e.printStackTrace();
        }
        ConfigurableApplicationContext configurableApplicationContext = springApplication.run("--message=haha", "--logging.level.root=ERROR");
        configurableApplicationContext.getBean(HelloWorld.class).print();
    }
}

结语

ConfigurationClassPostProcessor实现了很多的扩展功能,很多额外的注解包括@Bean,@Import等都是由这个类解析的。

作者:wcong
链接:https://www.jianshu.com/p/1d0fb7cd8a26
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

扫描二维码关注公众号,回复: 2666948 查看本文章

猜你喜欢

转载自blog.csdn.net/u011649691/article/details/81124129
今日推荐