手写Spring Boot@ConditionalOnxxx条件装配

  • 以下例子代码可在github或者在gitee下载
    github:代码链接
    gitee:代码链接

在Spring Boot框架中,可以看到不少@ConditionalOnxxx注解,如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean等注解,@Conditional条件装配于@Profile注解类似,但@Conditional条件装配更关注运行时的动态选择注册Spring Bean,而@profile则偏向于“静态激活和配置”Spring Bean,不过在Spring4.0开始,@Profile的实现方式也是基于实现Condition来实现的,文末会提及。

  • 观察@ConditionalOnClass、@ConditionalOnBean注解不难发现,均被注解@Conditional修饰,@Conditional注解属性值是OnClassCondition、OnBeanCondition类,其实现Condition类的matches方法。故手写一个Spring Boot@ConditionalOnxxx条件装配,也可以参考这种格式,即定义条件装配注解,用@Conditional修饰,写一个实现Condition类实现其matches匹配方法即可,条件装配的逻辑即写在matches方法里面。
  • ConditionalOnClass注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    Class[] value() default {};

    String[] name() default {};

}
  • ConditionalOnBean注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

    Class[] value() default {};

    String[] type() default {};

    Class[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class[] parameterizedContainer() default {};

}
  • 条件装配核心接口Condition接口,接口matches方法即为匹配的意思,返回true的话说明该条件成立,反之不成立。matches方法两个入参,第一个为ConditionContext包含Spring应用上下文相关,第二个参数是AnnotatedTypeMetadata,AnnotationMetadata为其子接口,可以获取了注解相关信息。
@FunctionalInterface
public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return {@code true} if the condition matches and the component can be registered,
     * or {@code false} to veto the annotated component's registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
  • 本文代码项目目录结构如图所示,在spring-boot项目的spring-boot-conditional模块:


    项目结构.png
  • 代码总共有4个类和一个yaml配置文件,通过配置文件language的值为Chinese或者English值来条件化装配注册不同的language bean。
  • @ConditionalOnSystemProperty注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {

    /**
     * 语言选择
     */
    String language();
}
  • OnSystemPropertyCondition类,实现Condition接口的matches方法,逻辑就是根据yaml文件设定的language,返回true或者false来判断@ConditionalOnSystemProperty注解修饰的方法条件成不成立,进而条件装配匹配要注册的Spring Bean。
public class OnSystemPropertyCondition implements Condition {

    @Value("${language}")
    private String language;

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取ConditionalOnSystemProperty所有的属性方法值
        Map annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
        assert annotationAttributes != null;
        //获取ConditionalOnSystemProperty#language()方法值
        String annotationLanguage = (String) annotationAttributes.get("language");
        //配置文件动态选择bean
        if (language.equals(annotationLanguage)) {
            return true;
        }
        return false;
    }
}
  • ConditionalMessageConfiguration配置类
@Configuration
@Slf4j
@ConditionalOnBean
public class ConditionalMessageConfiguration {

    @ConditionalOnSystemProperty(language = "Chinese")
    @Bean("language")
    public String chineseMessage() {
        log.info("初始化language bean,使用中文:你好,世界");
        return "你好,世界";
    }

    @ConditionalOnSystemProperty(language = "English")
    @Bean("language")
    public String englishMessage() {
        log.info("初始化language bean,使用英文:hello,world");
        return "hello,world";
    }
}
  • application.yaml文件
server:
  port: 8085
language:
  Chinese
  • 最后启动项目,观察结果


    启动项目.png
  • 前面提到,Spring4.0开始@Profile的实现方式也是基于实现Condition的方式来实现的,可看@Profile注解
  • @Profile注解,被@Conditional注解修饰,实现类是ProfileCondition类。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

    /**
     * The set of profiles for which the annotated component should be registered.
     */
    String[] value();

}
  • ProfileCondition类,即实现Condition接口的matches方法,通过AnnotatedTypeMetadata获取注解@Profile的方法属性value,然后遍历value数组的值,通过匹配系统启动时环境参数来判断是否匹配true或者false,条件成立则注册相关Spring Bean。
class ProfileCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

}

你可能感兴趣的:(手写Spring Boot@ConditionalOnxxx条件装配)