SpringBoot源码解读与原理分析(三)条件装配

SpringBoot源码解读与原理分析(合集)

2.3 Spring Framework的条件装配

在实际开发中我们可能遇到以下场景:测试环境用8080端口,生产环境用9999端口;测试环境需要将某个组件注册到IOC容器,但生产环境又不需要。
为了解决在不同场景/条件/环境下满足不同组件的装配,Spring Framework提供了两种条件装配的方式:基于Profile和基于Conditional。

2.3.1 基于Profile的装配

1.Profile源码解读

If a {@code @Configuration} class is marked with {@code @Profile}, all of the {@code @Bean} methods and {@link Import @Import} annotations associated with that class will be bypassed unless one or more of the specified profiles are active.

如果一个标注了@Configuration的配置类被标注为@Profile,那么与该类关联的所有@Bean方法和@Import}注释将被绕过,除非一个或多个指定的配置文件处于活动状态。

A profile is a named logical grouping that may be activated programmatically via {@link ConfigurableEnvironment#setActiveProfiles} or declaratively by setting the {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME spring.profiles.active} property as a JVM system property, as an environment variable, or as a Servlet context parameter in {@code web.xml} for web applications.

这里描述激活Profile的三种方式:JVM启动参数、环境变量、web.xml配置

简单概括,Profile提供了一种“基于环境的配置”,根据当前项目的不同运行时环境,可以动态地注册与当前运行环境匹配的组件。

2.使用@Profile注解

(1)BartenderConfiguration类添加@Profile注解

public class Bartender {

    private String name;

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

    // gettter setter
}
@Configuration
@Profile("city")
public class BartenderConfiguration {

    @Bean
    public Bartender zhangsan() {
        return new Bartender("张三");
    }

    @Bean
    public Bartender lisi() {
        return new Bartender("李四");
    }

}

(2)编程式配置Profile

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BartenderConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }

}

执行结果(已省略一些内部组件打印):

=========分割线=========
=========分割线=========

控制台没有打印zhangsan和lisi。

因为在默认情况下,ApplicationContext中的Profile为“default”,与配置的@Profile("city")不匹配,所以BartenderConfiguration不会生效,@Bean也就不会注册到IOC容器中。

要想zhangsan和lisi注册到IOC容器中,则需要给ApplicationContext设置一下激活的Profile。

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("city");
        ctx.register(BartenderConfiguration.class);
        ctx.refresh();
        System.out.println("=========分割线=========");
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("=========分割线=========");
    }

}

执行结果(已省略一些内部组件打印):

=========分割线=========
bartenderConfiguration
zhangsan
lisi
=========分割线=========

zhangsan和lisi已注册到IOC容器。

注意:这里AnnotationConfigApplicationContext在创建对象时,没有传入配置类,则内部不会执行初始化逻辑,而是等到手动调用其refresh方法后才会初始化IOC容器(如果传入了会立即初始化IOC容器),在初始化过程中,一并处理环境配置。

(3)命令行参数配置Profile

上面使用的编程式配置Profile存在硬编码问题,如果需要切换Profile,则需要修改代码并重新编译。为此,SpringFramework还支持命令行参数配置Profile。

在IDEA中配置启动选项:


在main方法中改回原来的构造方法传入配置类的形式:

public class TavernProfileApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BartenderConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }

}

执行结果(已省略一些内部组件打印):

=========分割线=========
bartenderConfiguration
zhangsan
lisi
=========分割线=========

zhangsan和lisi成功注册到IOC容器。

3.Profile运用于实际开发

application.properties文件可以通过加profile后缀来区分不同环境下的配置文件(application-dev.properties、application-test.properties、application-prod.properties)

# application-dev.properties
server.port=8787

# application-prod.properties
server.port=8989

# application.properties
spring.profiles.active=dev #激活dev的配置

4.Profile的不足

Profile控制的是整个项目的运行环境,无法根据单个Bean的因素决定是否装配。这种情况要用第二种条件装配的方式:基于@Conditional注解。

2.3.2 基于Conditional的装配

Conditional,意为条件,可以使Bean的装配基于一些指定的条件。
换句话说,被标注@Conditional注解的Bean要注册到IOC容器时,必须满足@Conditional上指定的所有条件才允许注册。

1.@Conditional源码解读

The {@code @Conditional} annotation may be used in any of the following ways:

  • as a type-level annotation on any class directly or indirectly annotated with {@code @Component}, including {@link Configuration @Configuration} classes
  • as a meta-annotation, for the purpose of composing custom stereotype annotations
  • as a method-level annotation on any {@link Bean @Bean} method

@Conditional的三种使用方式:

  • 在任何直接或间接用@Component标注的类上作为类级别注,包括@Configuration类
  • 作为元注解,用于组合自定义构造型注解
  • 作为任何@Bean方法上的方法级注解

If a {@code @Configuration} class is marked with {@code @Conditional}, all of the {@code @Bean} methods, {@link Import @Import} annotations, and {@link ComponentScan @ComponentScan} annotations associated with that class will be subject to the conditions.

如果一个@Configuration配置类标注了@Conditional,那么与之相关联的@Bean方法,@Import导入,@ComponentScan注解都将适用于这些条件。

Class<? extends Condition>[] value();

@Conditional注解需要传入一个Condition接口实现类数组,说明在使用时还需要定义一个条件判断类作为匹配依据,实现Condition接口。

2.@Conditional使用

(1)创建判断Boss是否存在的条件判断类

public class Boss {
}
public class BossExistCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 使用BeanDefinition而不是Bean做判断,这是因为考虑到:
        // 当进行匹配时Boss对象可能尚未创建,使用BeanDefinition
        // 可以确保不会出现偏差
        return context.getBeanFactory().containsBeanDefinition(Boss.class.getName());
    }
    
}

(2)吧台配置类使用@Conditional,并传入BossExistCondition

public class Bar {
}
@Configuration
public class BarConfiguration {

    @Bean
    @Conditional(BossExistCondition.class)
    public Bar bbBar() {
        return new Bar();
    }

}

(3)测试
@EnableTavern的内容详见:SpringBoot源码解读与原理分析(二)组件装配
场景一:@EnableTavern只导入BarConfiguration,不导入Boss

@Documented
@Retention(RetentionPolicy.RUNTIME) //
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({BarConfiguration.class})
public @interface EnableTavern {

}

执行结果(已省略一些内部组件打印):

=========分割线=========
tavernConfiguration
com.star.springboot.conditional.BarConfiguration
=========分割线=========

Boss和bbBar均没有注册到IOC容器中。

场景二:@EnableTavern导入BarConfiguration和Boss

@Documented
@Retention(RetentionPolicy.RUNTIME) //
@Target(ElementType.TYPE) // 该注解只能标注到类上
@Import({Boss.class, BarConfiguration.class})
public @interface EnableTavern {

}

执行结果(已省略一些内部组件打印):

=========分割线=========
tavernConfiguration
com.star.springboot.ioc.Boss
com.star.springboot.conditional.BarConfiguration
bbBar
=========分割线=========
Boss和bbBar均注册到IOC容器中,说明@Conditional已经起了作用。

3.ConditionalOnXXX系列注解

SpringBoot针对@Conditional注解扩展了一系列条件注解。

  • @ConditionalOnClass & @ConditionalOnMissingClass :检查当前项目的类路径下是否包含/缺少指定类。
  • @ConditionalOnBean & @ConditionalOnMissingBean :检查当前容器中是否注册/缺少指定Bean。
  • @ConditionalOnProperty :检查当前应用的属性配置。
  • @ConditionalOnWebApplication & @ConditionalOnNotWebApplication :检查当前应用是否为Web应用。
  • @ConditionalOnExpression :根据指定的SqEL表达式确定条件是否满足。

注意,@ConditionalOnXXX注解通常都用在自动配置类中,对于普通的配置类最好避免使用,以免出现判断偏差。

SpringBoot源码解读与原理分析(合集)

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

推荐阅读更多精彩内容