Spring Boot 中的自动化配置 - 条件注解

定义

Spring4 中提供了更加通用的条件注解,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的。

有的小伙伴可能没用过条件注解,但是开发环境、生产环境切换的 Profile 多多少少都有用过吧?实际上这就是条件注解的一个特例。

实践

抛开 Spring Boot,我们来单纯的看看在 Spring 中条件注解的用法。

首先我们来创建一个普通的 Maven 项目,然后引入 spring-context,如下:

  1. <dependencies>

  2. <dependency>

  3. <groupId>org.springframework</groupId>

  4. <artifactId>spring-context</artifactId>

  5. <version>5.1.5.RELEASE</version>

  6. </dependency>

  7. </dependencies>

然后定义一个 Food 接口:

  1. public interface Food {

  2. String showName();

  3. }

Food 接口有一个 showName 方法和两个实现类:

  1. public class Rice implements Food {

  2. public String showName() {

  3. return "米饭";

  4. }

  5. }

  6. public class Noodles implements Food {

  7. public String showName() {

  8. return "面条";

  9. }

  10. }

分别是 Rice 和 Noodles 两个类,两个类实现了 showName 方法,然后分别返回不同值。

接下来再分别创建 Rice 和 Noodles 的条件类,如下:

  1. public class NoodlesCondition implements Condition {

  2. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

  3. return context.getEnvironment().getProperty("people").equals("北方人");

  4. }

  5. }

  6. public class RiceCondition implements Condition {

  7. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

  8. return context.getEnvironment().getProperty("people").equals("南方人");

  9. }

  10. }

在 matches 方法中做条件属性判断,当系统属性中的 people 属性值为 '北方人' 的时候,NoodlesCondition 的条件得到满足,当系统中 people 属性值为 '南方人' 的时候,RiceCondition 的条件得到满足,换句话说,哪个条件得到满足,一会就会创建哪个 Bean 。

接下来我们来配置 Rice 和 Noodles :

  1. @Configuration

  2. public class JavaConfig {

  3. @Bean("food")

  4. @Conditional(RiceCondition.class)

  5. Food rice() {

  6. return new Rice();

  7. }

  8. @Bean("food")

  9. @Conditional(NoodlesCondition.class)

  10. Food noodles() {

  11. return new Noodles();

  12. }

  13. }

这个配置类,大家重点注意两个地方:

  • 两个 Bean 的名字都为 food,这不是巧合,而是有意取的。两个 Bean 的返回值都为其父类对象 Food。

  • 每个 Bean 上都多了 @Conditional 注解,当 @Conditional 注解中配置的条件类的 matches 方法返回值为 true 时,对应的 Bean 就会生效。

配置完成后,我们就可以在 main 方法中进行测试了:

  1. public class Main {

  2. public static void main(String[] args) {

  3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

  4. ctx.getEnvironment().getSystemProperties().put("people", "南方人");

  5. ctx.register(JavaConfig.class);

  6. ctx.refresh();

  7. Food food = (Food) ctx.getBean("food");

  8. System.out.println(food.showName());

  9. }

  10. }

首先我们创建一个 AnnotationConfigApplicationContext 实例用来加载 Java 配置类,然后我们添加一个 property 到 environment 中,添加完成后,再去注册我们的配置类,然后刷新容器。容器刷新完成后,我们就可以从容器中去获取 food 的实例了,这个实例会根据 people 属性的不同,而创建出来不同的 Food 实例。

这个就是 Spring 中的条件注解。

进化

条件注解还有一个进化版,那就是 Profile。我们一般利用 Profile 来实现在开发环境和生产环境之间进行快速切换。其实 Profile 就是利用条件注解来实现的。

还是刚才的例子,我们用 Profile 来稍微改造一下:

首先 Food、Rice 以及 Noodles 的定义不用变,条件注解这次我们不需要了,我们直接在 Bean 定义时添加 @Profile 注解,如下:

  1. @Configuration

  2. public class JavaConfig {

  3. @Bean("food")

  4. @Profile("南方人")

  5. Food rice() {

  6. return new Rice();

  7. }

  8. @Bean("food")

  9. @Profile("北方人")

  10. Food noodles() {

  11. return new Noodles();

  12. }

  13. }

这次不需要条件注解了,取而代之的是 @Profile 。然后在 Main 方法中,按照如下方式加载 Bean:

  1. public class Main {

  2. public static void main(String[] args) {

  3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

  4. ctx.getEnvironment().setActiveProfiles("南方人");

  5. ctx.register(JavaConfig.class);

  6. ctx.refresh();

  7. Food food = (Food) ctx.getBean("food");

  8. System.out.println(food.showName());

  9. }

  10. }

效果和上面的案例一样。

这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?

我们来看一下 @Profile 的定义:

  1. @Target({ElementType.TYPE, ElementType.METHOD})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Conditional(ProfileCondition.class)

  5. public @interface Profile {

  6. String[] value();

  7. }

可以看到,它也是通过条件注解来实现的。条件类是 ProfileCondition ,我们来看看:

  1. class ProfileCondition implements Condition {

  2. @Override

  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

  4. MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());

  5. if (attrs != null) {

  6. for (Object value : attrs.get("value")) {

  7. if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {

  8. return true;

  9. }

  10. }

  11. return false;

  12. }

  13. return true;

  14. }

  15. }

看到这里就明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。

@Profile 虽然方便,但是不够灵活,因为具体的判断逻辑不是我们自己实现的。而 @Conditional 则比较灵活。

结语

两个例子向大家展示了条件注解在 Spring 中的使用,它的一个核心思想就是当满足某种条件的时候,某个 Bean 才会生效,而正是这一特性,支撑起了 Spring Boot 的自动化配置。

转载于:https://my.oschina.net/u/3635618/blog/3097676

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值