181018-SpringBoot基础篇Bean之条件注入@Condition使用姿势

文章目录
  1. I. @Conditional注解
    1. 1. 定义
    2. 2. 使用说明
    3. 3. 测试与验证
  2. II. 扩展与小结
    1. 1. 自动扫描Bean的条件加载
    2. 2. 小结
      1. a. 优势
      2. b. 更多注解
  3. III. 其他
    1. 0. 相关
      1. a. 更多博文
      2. b. 项目源码
    2. 1. 一灰灰Blog
    3. 2. 声明
    4. 3. 扫描关注

前面几篇关于Bean的基础博文中,主要集中在Bean的定义和使用,但实际的情况中有没有一些场景是不加载我定义的bean,或者只有满足某些前提条件的时候才加载我定义的Bean呢?

本篇博文将主要介绍bean的加载中,条件注解@Conditional的相关使用

I. @Conditional注解

这个注解在Spring4中引入,其主要作用就是判断条件是否满足,从而决定是否初始化并向容器注册Bean

1. 定义

@Conditional注解定义如下,其内部主要就是利用了Condition接口,来判断是否满足条件,从而决定是否需要加载Bean

1
2
3
4
5
6
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

下面是Condtion接口的定义,这个可以说是最基础的入口了,其他的所有条件注解,归根结底,都是通过实现这个接口进行扩展的

1
2
3
4
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

这个接口中,有个参数比较有意思ConditionContext,它持有不少有用的对象,可以用来获取很多系统相关的信息,来丰富条件判断,接口定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface ConditionContext {
// 获取Bean定义
BeanDefinitionRegistry getRegistry();

// 获取Bean工程,因此就可以获取容器中的所有bean
@Nullable
ConfigurableListableBeanFactory getBeanFactory();

// environment 持有所有的配置信息
Environment getEnvironment();

// 资源信息
ResourceLoader getResourceLoader();

// 类加载信息
@Nullable
ClassLoader getClassLoader();
}

2. 使用说明

通过一个小例子,简单的说一下如何使用Condition和@Conditional注解,来实现bean的条件加载

首先我们定义一个随机产生数据的类,其功能就是随机生成一些数据

1
2
3
4
5
6
7
8
9
10
11
public class RandDataComponent<T> {
private Supplier<T> rand;

public RandDataComponent(Supplier<T> rand) {
this.rand = rand;
}

public T rand() {
return rand.get();
}
}

我们目前提供两种随机数据生成的bean,但是需要根据配置来选择具体选中的方式,因此我们如下定义Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class ConditionalAutoConfig {

@Bean
@Conditional(RandIntCondition.class)
public RandDataComponent<Integer> randIntComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextInt(1024);
});
}

@Bean
@Conditional(RandBooleanCondition.class)
public RandDataComponent<Boolean> randBooleanComponent() {
return new RandDataComponent<>(() -> {
Random random = new Random();
return random.nextBoolean();
});
}
}

上面的配置,先不管@Conditional注解的内容,单看两个Bean的定义,一个是定义int随机数生成;一个是定义boolean随机生成;

但是我们的系统中,只需要一个随机数据生成器即可,我们选择根据配置conditional.rand.type的值来选择到底用哪个,配置如下

1
2
# int 表示选择随机产生int数据; 非int 表示随机产生boolean数据
conditional.rand.type=int

接下来就得看这个条件如何加上了,也就是上面配置类ConditionalAutoConfig中两个注解的内容了,两个类都是实现Condition的接口,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RandBooleanCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "boolean".equalsIgnoreCase(type);
}
}

public class RandIntCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
return "int".equalsIgnoreCase(type);
}
}

上面的实现也比较清晰,获取配置值,然后判断,并返回true/fase;返回true,则表示这个条件满足,那么这个Bean就可以被加载了;否则这个Bean就不会创建

3. 测试与验证

针对上面的配置与实现,写一个测试类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping(path = "/conditional")
public class ConditionalRest {

@Autowired
private RandDataComponent randDataComponent;

@GetMapping(path = "/show")
public String show() {
String type = environment.getProperty("conditional.rand.type");
return randDataComponent.rand() + " >>> " + type;
}
}

当配置文件的值为int时,每次访问返回的应该都是正整数,演示如下图

int随机生成

将配置的值改成boolean之后,再次测试如下图

boolean随机生成

II. 扩展与小结

上面的测试演示了通过配置文件选择注入Bean的情况,如果一个Bean是通过自动扫描加载的,是否可以直接在Bean的类上添加注解来决定是否载入呢?

1. 自动扫描Bean的条件加载

从使用来讲,和前面的没有什么区别,只是将注解放在具体的类上而言,同样给出一个示例,先定义一个bean

1
2
3
4
5
6
7
8
9
10
11
@Component
@Conditional(ScanDemoCondition.class)
public class ScanDemoBean {

@Value("${conditional.demo.load}")
private boolean load;

public boolean getLoad() {
return load;
}
}

对应的判断条件如下,当配置文件中conditional.demo.load为true时,才会加载这个配置,否则不实例化

1
2
3
4
5
6
public class ScanDemoCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return "true".equalsIgnoreCase(conditionContext.getEnvironment().getProperty("conditional.demo.load"));
}
}

测试类和前面差不多,稍微注意下的就是自动注入时,改一下必要条件,避免bean不存在时报错

1
2
3
4
5
6
7
8
9
10
11
12
@Autowired(required = false)
private ScanDemoBean scanDemoBean;

@GetMapping(path = "/scan")
public String showDemo() {
String type = environment.getProperty("conditional.demo.load");
if (scanDemoBean == null) {
return "not exists! >>>" + type;
} else {
return "load : " + scanDemoBean.getLoad() + " >>>" + type;
}
}

当配置为true时,bean应该存在,走上面的else逻辑

自动扫描条件准确

当配置为false时,不会加载bean,走if逻辑

自动扫描添加不符

2. 小结

通过@Conditional注解配合Condition接口,来决定给一个bean是否创建和注册到Spring容器中,从而实现有选择的加载bean

a. 优势

这样做的目的是什么呢?

  • 当有多个同名bean时,怎么抉择的问题
  • 解决某些bean的创建有其他依赖条件的case

b. 更多注解

上面可以控制bean的创建,但通过上面的流程,会发现有一点繁琐,有没有什么方式可以简化上面的流程呢?

只用一个注解就好,不要自己再来实现Condtion接口,Spring框架提供了一系列相关的注解,如下表

注解 说明
@ConditionalOnSingleCandidate 当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true
@ConditionalOnMissingBean 当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系
@ConditionalOnBean 与上面相反,要求bean存在
@ConditionalOnMissingClass 当给定的类名在类路径上不存在时返回true,各类型间是and的关系
@ConditionalOnClass 与上面相反,要求类存在
@ConditionalOnCloudPlatform 当所配置的CloudPlatform为激活时返回true
@ConditionalOnExpression spel表达式执行为true
@ConditionalOnJava 运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配
@ConditionalOnProperty 要求配置属性匹配条件
@ConditionalOnJndi 给定的jndi的Location 必须存在一个.否则,返回不匹配
@ConditionalOnNotWebApplication web环境不存在时
@ConditionalOnWebApplication web环境存在时
@ConditionalOnResource 要求制定的资源存在

III. 其他

0. 相关

a. 更多博文

基础篇

应用篇

b. 项目源码

1. 一灰灰Blog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到 评论