文章目录
- 前言
- 简介
- 创建 springboot-condition 模块
- 开始操作
- 获取容器中的 Bean
- @Conditional() 注解
- matches() 条件判断
- 导入 Jedis 坐标后
- 导入通过注解属性值 value 指定坐标
- 思考
- spring-boot-autoconfigure
- condition 包
- ConditionalOnClass
- ConditionalOnBean
- ConditionalOnProperty
- ConditionalOnMissingBean
- data 包
- 演示
- 总结
- 自定义条件
- SpringBoot 提供的常用条件注解
前言
SpringBoot 的自动配置分为 Condition、切换内置web服务器、Enable 注解、Import 注解、EnableAutoConfiguration 注解。
本章分享的是 Condition 的内容。
简介
Condition 是在 Spring 4.0 增加的条件判断功能,通过这个功能可以实现选择性的创建 Bean 操作。
创建 springboot-condition 模块
使用 IDEA 快速创建一个 SpringBoot 模块,不引入任何依赖。
开始操作
进入 SpringBoot 启动类,点击进入 run() 可以看到这个方法是有返回值的,返回值为 ConfigurableApplicationContext,这个返回值就是 IOC 容器。
获取 run() 的返回值。
// 启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
获取容器中的 Bean
在 pom.xml 中导入 Redis 的起步依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
在启动类中编写获取 redisTemplate 代码。
// 获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
打印输出获取到的 redisTemplate。
接下来创建 domain -> User 类
package com.xh.springbootcondition.domain;
public class User {
}
创建config -> UserConfig 类,类中添加返回值为 User 的方法,方法顶部添加 @Bean。
package com.xh.springbootcondition.config;
import com.xh.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
public User user(){
return new User();
}
}
在启动类中获取容器中的 user 对象并打印输出。
// 获取Bean,user
Object user = context.getBean("user");
System.out.println(user);
@Conditional() 注解
Spring 在创建 Bean 时,我们可以在 UserConfig 中的 user 方法上添加条件注解 @Conditional()。
@Conditional() 注解点进去可以看到这个注解需要一个 Class(因为是一个数组,所以可以导很多 Class ),这些 Class 必须都是 Condition 或者 Condition 的子类。
而 Condition 就是核心的条件接口,点击进入 Condition 可以看到接口中只有一个 matches() 方法,返回值为 boolean 类型。
所以我们要使用 @Conditional() 注解就需要在注解中传入一个 Condition 条件接口的实现类,并且实现类要复写matches() ,返回 true 或者 false。
如果返回的是 true,那么 User 对应的 Bean 将会被 Spring 容器创建,如果返回的是 false,那么容器则不会创建 User 对应的 Bean 。
创建 condition -> ClassCondition 类,实现 Condition 接口,复写 matches() 。
package com.xh.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 {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
在 user 方法顶部的 @Conditional() 注解中放入创建好的 ClassCondition.class
@Conditional(ClassCondition.class)
由于我们的 ClassCondition 实现类中复写的 matches() 返回的是 false,那么 Spring 容器就不会创建 User 所对应的 Bean,测试运行输出会提示找不到 user。
如果把返回条件改为 true,那么 User 对应的 Bean 就会被创建。
matches() 条件判断
我们可以在 复写的 matches() 中加入条件判断:
导入 Jedis 坐标后
在 pom.xml 中导入 Jedis 坐标
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
判断是否导入 Jedis 坐标(仅用于测试用例,不推荐用来实际操作)
// 1.需求:导入 Jedis 坐标后,创建 Bean
// 思路:判断 redis.clients.jedis.Jedis.class 是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
e.printStackTrace();
}
return flag;
运行启动类,成功输出 user。
如果把 pom.xml 文件中的 Jedis 坐标注释掉,那么Spring 容器则不会创建 User 对应的 Bean,运行启动类就获取不到 user 对象。
导入通过注解属性值 value 指定坐标
上一种方法操作空间有限,接下来我们去自定义一个注解来实现动态导入指定值判断是否创建Bean。
创建 condition -> ConditionOnClass 定义一个注解,添加一些相对应的注解并定义 value 属性。
package com.xh.springbootcondition.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
/**
* @Target 表示当前注解可以加到哪个范围上
* ElementType.TYPE 类上
* ElementType.METHOD 方法上
* --------------------------------------
* @Retention 表示注解生效的时机,为 RUNRIME
* --------------------------------------
* @Documented 表示生成 javadoc 的文档
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
String[] value();
}
修改 UserConfig 类中 user(),使用自定义的注解。
package com.xh.springbootcondition.config;
import com.xh.springbootcondition.condition.ConditionOnClass;
import com.xh.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
@ConditionOnClass("redis.clients.jedis.Jedis")
public User user(){
return new User();
}
}
修改 ClassCondition 中 matches() 实现动态获取注解属性值。
修改前我们需要了解 matches() 中的两个参数:
ConditionContext context 和 AnnotatedTypeMetadata metadata。
package com.xh.springbootcondition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class ClassCondition implements Condition {
/**
* 参数说明
* @param context 上下文对象。用于获取环境,IOC 容器,ClassLoader 对象
* @param metadata 注解元对象。可以获取注解定义的属性值。
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/*
// 1.需求:导入 Jedis 坐标后,创建 Bean
// 思路:判断 redis.clients.jedis.Jedis.class 是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
e.printStackTrace();
}
return flag;
*/
// 2.需求:导入通过注解属性值 value 指定坐标后创建 Bean
// 获取注解属性值 value[]
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
// System.out.println(map);
String[] value = (String[]) map.get("value");
boolean flag = true;
try {
for (String className : value) {
Class<?> cls = Class.forName(className);
}
} catch (ClassNotFoundException e) {
flag = false;
e.printStackTrace();
}
return flag;
}
}
运行启动类,输入获取的 user。
可以测试一个其他的坐标看看能不能创建出 User。
在 pom.xml 中添加 fastjson 坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
修改 userConfig,将之前编写的 user 方法注释掉,重新编写一个方法,@ConditionOnClass 的 value 值放入 com.alibaba.fastjson.JSON 路径。
@Configuration
public class UserConfig {
// @Bean
@Conditional(ClassCondition.class)
// @ConditionOnClass("redis.clients.jedis.Jedis")
// public User user(){
// return new User();
// }
@Bean
@ConditionOnClass("com.alibaba.fastjson.JSON")
public User user(){
return new User();
}
}
运行启动类,正确输出:
思考
读到这里的朋友可以回过头思考一下:根据我们之前在启动类中编写的获取 redisTemplate 的代码:
// 启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
SpringBoot 是如何知道要创建 RedisTemplate 的?
其实 SpringBoot 就是使用 Condition 去判断我们当前环境中有没有导入 Redis 的 start,如果导入了,那么就会创建 RedisTemplate。
spring-boot-autoconfigure
在 SpringBoot 中提供了非常多的 ConditionOn* 的类,我们可以在 External Libraries 中找到 spring-boot-autoconfigure 这个 SpringBoot 的自动配置 jar 包,其中有一个 condition 包,可以看到这个包里有很多 ConditionOn*。
condition 包
ConditionalOnClass
进入 ConditionalOnClass 中可以看到,他里面定义了一个注解,注解上面有一个 @Conditional,value 值为 OnClassCondition.class,这个操作就是在判断是否有对应的字节码文件。
我们可以再看一看其他的,比如:
ConditionalOnBean
这个注解代表了:当你的环境中有 OnBeanCondition 这个 Bean 的时候,才会去创建 ConditionalOnBean 这个注解修饰的 Bean。
ConditionalOnProperty
当你的配置文件中配置了指定的属性,才会创建某一个 Bean。
ConditionalOnMissingBean
当你的 IOC 容器中没有指定 Bean 的时候,才会去初始化指定的 Bean。
data 包
data 包中有一个 redis 的包,redis 有一个配置类为 RedisAutoConfiguration,如图可以看到这个配置类在初始化 RedisTemplate,并且有一些初始化的条件:
@ConditionalOnClass(RedisOperations.class)
在当前环境中需要有 RedisOperations 字节码存在,才能去创建下面方法的 Bean。(因为这个注解是加在类上的,所以下面所有的方法都要满足这个条件才可以创建 Bean)
@ConditionalOnMissingBean(name = “redisTemplate”)
可以看到在 redisTemplate 方法上添加了一个这样的注解,他代表如果当前环境中不存在 redisTemplate,才会创建 RedisTemplate,如果存在,那就证明我们自己定义了 RedisTemplate,则不会创建。
演示
修改 UserConfig,添加一个 user2 方法,并且在方法上添加 @Bean 和 @ConditionalOnProperty(name = “csdn”, havingValue = “xinghe”) 注解。
上面讲到 @ConditionalOnProperty() 是当配置文件中某一个属性和对应的值存在,才会去加载 Bean,这个注解表示:当我们的配置文件中有一个键为 ”csdn“,值为 ”xinghe“ 的时候,才会去加载对应的 Bean。
@Configuration
public class UserConfig {
// @Bean
@Conditional(ClassCondition.class)
// @ConditionOnClass("redis.clients.jedis.Jedis")
// public User user(){
// return new User();
// }
@Bean
@ConditionOnClass("com.alibaba.fastjson.JSON")
public User user(){
return new User();
}
@Bean
@ConditionalOnProperty(name = "csdn", havingValue = "xinghe")
public User user2(){
return new User();
}
}
在启动类中添加获取 user2 的代码。
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
// 启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
// 获取Bean,user
Object user = context.getBean("user");
System.out.println(user);
// 获取Bean,user2
Object user2 = context.getBean("user2");
System.out.println(user2);
}
}
因为我们没有在启动类中配置相应的属性,所以运行输出没有找到 user2。
接下来我们在 application.properties 中配置相应的属性:
csdn=xinghe
再次运行,成功输出:
总结
本章介绍了自定义的条件以及 SpringBoot 提供的常用条件注解。
自定义条件
- 定义条件类:自定义类实现 Condition 接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean 值。
matches 方法两个参数:
- context:上下文对象,可以获取属性值、类加载器、BeanFactory 等。
- metadata:元数据对象,用于获取注解属性。
- 判断条件:在初始化 Bean 时,使用 @Conditional(条件类.class) 注解。
SpringBoot 提供的常用条件注解
- ConditionalOnProperty:判断配置文件是否有对应的属性和值才初始化 Bean。
- ConditionOnClass:判断环境中是否有对应的字节码文件才初始化 Bean。
- ConditionOnMissingBean:判断环境中没有对应 Bean 才初始化 Bean。