手写Spring Boot@ConditionalOnxxx条件装配

在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;
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容