0
点赞
收藏
分享

微信扫一扫

14-SpringBoot自动配置-Condition

14-SpringBoot自动配置-Condition

前言

我们在前面的篇章基本上已经熟悉了怎么使用SpringBoot,但是呢。对于SpringBoot的一些高级的特性 或者 底层原理还是不是很清楚。

例如SpringBoot是如何根据启动坐标去创建需要的 bean。

有时候我们可能需要通过一些条件,再去创建 bean。对于这种场景,我们该怎么做呢?

这个时候我们就要介绍以下 Condition 的使用了。

SpringBoot自动配置-Condition

Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载相应的Bean

@Conditional要配和Condition的实现类(ClassCondition)进行使用

案例1 - 通过读取启动坐标的配置,判断是否创建 bean 对象

需求

在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 导入 Jedis 坐标后,加载该 Bean,没导入,则不加载。

步骤

1.搭建演示项目


14-SpringBoot自动配置-Condition_mybatis

初始化的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- springboot父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<!-- 项目坐标 -->
<groupId>com.lijw</groupId>
<artifactId>springboot-condition</artifactId>
<version>0.0.1-SNAPSHOT</version>

<name>springboot-condition</name>

<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>

<!-- 项目依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

好了,现在我们搭建好了项目,也配置了 redis 的启动依赖,我们下面来逐步实验一下。

2. 获取 SpringBoot 的 IOC 容器

首先我们要知道 SpringBoot IOC 容器怎么获取,如下:


14-SpringBoot自动配置-Condition_spring boot_02

package com.lijw.springbootcondition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

public static void main(String[] args) {
// 启动SpringBoot应用就可以获取 IOC 容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 通过IOC容器获取Bean
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
}

}

在上面,我们通过SpringBoot的应用启动获取到了 IOC 容器,并通过 IOC 容器获取到了 redisTemplate 的 Bean。那么这个 Bean 能否控制产生或者不产生的呢?

这时候我们就要来介绍 Condition 接口,通过这个接口 我们将可以控制一个 Bean 是否能够自动生成。

我们来回顾一下需求:

在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 导入 Jedis 坐标后,加载该 Bean,没导入,则不加载。

下面我们首先来写一个 User 类,然后再写要给 UserConfig 类来控制 User 类是否创建 Bean

3. 创建 User 类 以及 尝试获取 Bean


14-SpringBoot自动配置-Condition_mybatis_03

package com.lijw.springbootcondition.domain;

public class User {
}

尝试获取 Bean 如下:


14-SpringBoot自动配置-Condition_spring_04

package com.lijw.springbootcondition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

public static void main(String[] args) {
// 启动SpringBoot应用就可以获取 IOC 容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 通过IOC容器获取Bean
Object user = context.getBean("user");
System.out.println(user);
}

}

可以发现 User 类目前还不能获取 Bean,因为我们没有将 User 类定义为组件,所以需要写一个获取 User 类对象的 方法。

4.创建 UserConfig 的配置文件,用来加载配置 创建 User 类的 bean


14-SpringBoot自动配置-Condition_java_05

package com.lijw.springbootcondition.config;

import com.lijw.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

@Bean
public User user(){
return new User();
}
}

此时有了这个返回 @Bean 的方法,我们应该就可以获取 user 的 bean 对象了,如下:


14-SpringBoot自动配置-Condition_spring boot_06

好了,既然已经可以获取到 user 类的 bean,那么我们就可以在这个 bean 方法上来控制是否生成 bean 对象。

5. 使用 @Conditional 以及 实现 Condition 接口来控制是否生成 bean 对象

5.1 使用 @Conditional


14-SpringBoot自动配置-Condition_redis_07

package com.lijw.springbootcondition.config;

import com.lijw.springbootcondition.condition.ClassCondition;
import com.lijw.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

@Bean
@Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
public User user(){
return new User();
}
}

5.2 编写 ClassCondition 实现 Condition 接口,定义是否创建 bean


14-SpringBoot自动配置-Condition_java_08

package com.lijw.springbootcondition.condition;


import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ClassCondition implements Condition {

/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return Boolean: false不创建bean, true创建bean
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}

此时,ClassCondition 重写的 matches 方法默认返回为 false,所以我们下面使用 IOC 获取 user bean 对象会失败,如下:


14-SpringBoot自动配置-Condition_spring boot_09

如果设置为 true,那么则可以获取到 bean 对象:


14-SpringBoot自动配置-Condition_java_10

那么到这里,我们基本上就可以知道大概如何实现控制是否生成bean的需求了。

5.2 编写 ClassCondition 判断是否引入了 redis依赖,如果引入则允许创建 user,反之不允许


14-SpringBoot自动配置-Condition_java_11

package com.lijw.springbootcondition.condition;


import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ClassCondition implements Condition {

/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return Boolean: false不创建bean, true创建bean
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

Boolean flag = true;

try {
// 获取redisTemplate的依赖文件是否存在
Class<?> cls = Class.forName("org.springframework.data.redis.core.RedisTemplate");
} catch (ClassNotFoundException e) {
// 如果异常不存在,则设置为false,不允许创建bean
flag = false;
}

return flag;
}
}

此时,如果我们注释了redis依赖,来看看是否能创建 user 的 bean,如下:


14-SpringBoot自动配置-Condition_mybatis_1214-SpringBoot自动配置-Condition_spring_13

获取 bean 失败了,说明我们的方法是正确的。

下面我们再配置依赖,看看能否获取成功,如下:


14-SpringBoot自动配置-Condition_mybatis_1414-SpringBoot自动配置-Condition_java_15

成功获取 bean 了。

6.小结

在上面我们已经通过一个类依赖是否存在,来判断是否创建 bean 对象。但是我们这个类依赖 目前是直接 硬编码写死在代码里头的!

这样肯定不灵活,如果我们想要改其他类,就要每次到代码里面去修改,能不能写到一个注解上,然后我们在注解上配置需要依赖的类呢?

下面我们来看看怎么做。

案例2 - 通过动态指定启动坐标的配置,判断是否创建 bean 对象

需求

在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。

步骤

1.创建自定义条件注解类


14-SpringBoot自动配置-Condition_spring_16

创建 @ConditionOnClass 继承 @Conditional,可以自定义属性


14-SpringBoot自动配置-Condition_redis_17

package com.lijw.springbootcondition.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class) // 继承 @Conditional 条件注解
public @interface ConditionOnClass {
String[] value(); // 定义数组,方便后续可以写入多个依赖类
}

2.在创建 Bean 方法换上使用自定义的条件注解类,并指定希望设置的依赖类


14-SpringBoot自动配置-Condition_mybatis_18

@Bean
//@Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
@ConditionOnClass("org.springframework.data.redis.core.RedisTemplate")
public User user(){
return new User();
}

在这里我们已经可以将依赖类作为参数写到注解中,那么注解如何将参数传递到条件处理类中呢?


14-SpringBoot自动配置-Condition_mybatis_19

3.认识 ClassCondition 类下 matches(ConditionContext context, AnnotatedTypeMetadata metadata) 的两个参数

public class ClassCondition implements Condition {

/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return Boolean: false不创建bean, true创建bean
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)

也就是我们可以通过 metadata 这个参数获取注解设置的参数。

4.通过 metadata 获取注解类的参数


14-SpringBoot自动配置-Condition_mybatis_20

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
System.out.println(map);

可以确认已经可以获取 value 属性的 Sting数组了。

下面我们继续来遍历一下 value 属性的字符串数组:


14-SpringBoot自动配置-Condition_spring boot_21

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
// 获取value属性的String[],并且遍历
String[] value = (String[]) map.get("value");
for (String clazzName : value) {
System.out.println(clazzName);
}

5.遍历校验 metadata 获取注解类的参数


14-SpringBoot自动配置-Condition_spring_22

package com.lijw.springbootcondition.condition;


import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

import java.util.List;
import java.util.Map;

public class ClassCondition implements Condition {

/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return Boolean: false不创建bean, true创建bean
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());

// 设置返回的flag
Boolean flag = true;

try {
// 获取value属性的String[],并且遍历
String[] value = (String[]) map.get("value");
for (String clazzName : value) {
System.out.println(clazzName);
// 尝试获取依赖文件,根据异常判断是否存在
Class<?> cls = Class.forName(clazzName);
}
} catch (ClassNotFoundException e) {
// 如果异常不存在,则设置为false,不允许创建bean
flag = false;
}

return flag;
}
}

6.测试验证

首先当然是验证设置了依赖的情况,如下:


14-SpringBoot自动配置-Condition_mybatis_23

可以看到成功获取 bean

下面我们来注释依赖,确认异常的情况:


14-SpringBoot自动配置-Condition_spring boot_24

成功了,当依赖不存在,则无法获取 bean

而且目前我们的依赖可以动态在注解类上填写。


14-SpringBoot自动配置-Condition_spring_25

SpringBoot 源码中提供的 Condition 条件注解类

在上面我们虽然自己写了一个条件注解类,但是 SpringBoot 其实已经提供了大量的条件注解类了,我们查看源码如下:


14-SpringBoot自动配置-Condition_spring boot_2614-SpringBoot自动配置-Condition_spring boot_2714-SpringBoot自动配置-Condition_spring boot_28

可以看到源码其实也是这些写的。

SpringBoot 提供的常用条件注解:

ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean

ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean

ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

其中源码之后 ​​ConditionalOnProperty​​ 这个注解我们可能经常会用到,通过配置文件的属性来判断是否创建 Bean


14-SpringBoot自动配置-Condition_redis_29

下面我们来演示一下。

演示使用 ConditionalOnProperty 注解

1.编写创建 user bean 的方法,需要设置 ConditionalOnProperty 定义需要的属性值


14-SpringBoot自动配置-Condition_redis_30

image-20220224204200775

package com.lijw.springbootcondition.config;

import com.lijw.springbootcondition.condition.ClassCondition;
import com.lijw.springbootcondition.condition.ConditionOnClass;
import com.lijw.springbootcondition.domain.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

@Bean
//@Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
@ConditionOnClass("org.springframework.data.redis.core.RedisTemplate")
public User user(){
return new User();
}

@Bean
@ConditionalOnProperty(name = "test", havingValue = "condition") // 配置文件存在 test=condition 则创建bean对象
public User user2(){
return new User();
}
}

2.编写获取 user2 的 bean 对象

14-SpringBoot自动配置-Condition_java_31

package com.lijw.springbootcondition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

public static void main(String[] args) {
// 启动SpringBoot应用就可以获取 IOC 容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 通过IOC容器获取Bean
Object user = context.getBean("user2");
System.out.println(user);

}

}

3.首先没有配置属性值,验证是否能创建 user2 的 bean 对象

14-SpringBoot自动配置-Condition_redis_32

此时没有自动创建 user2 的 bean 对象。

4.配置需要的属性值,验证是否能创建 user2 的 bean 对象

14-SpringBoot自动配置-Condition_spring_33

在配置文件设置了属性值之后,成功自动创建 user2 的 bean


举报

相关推荐

0 条评论