美文网首页
手写Spring Boot@ConditionalOnxxx条件

手写Spring Boot@ConditionalOnxxx条件

作者: dayue_ | 来源:发表于2021-05-01 18:56 被阅读0次

    在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<? extends Annotation>[] 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<String, Object> 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<String, Object> 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条件

          本文链接:https://www.haomeiwen.com/subject/ptxarltx.html