SpringBoot自动配置
Condition
Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作。
思考:比如 SpringBoot 是如何知道要创建哪个 Bean 的?比如 SpringBoot 是如何知道要创建 RedisTemplate 的?
背景(结合上面的思考)
- 创建一个 SpringBoot项目,不整合框架
java
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
//获取 bean
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
}
}
此时会报错 No bean named 'redisTemplate' available
- 导入 redis 的依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
再次启动发现没有报错,说明我们找到了 bean
实例一
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
1.导入 Jedis 坐标厚,加载该 Bean,没导入,则不加载
1. 创建一个 User 实体类
java
public class User {
}
2. 创建一个配置类 UserConfig
java
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
3. 在启动类中获取user 的 bean
java
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
此时可以获取到容器中定义的 bean>user
4. 在创建 bean 的时候使用注解@Conditional
首先我们要知道@Conditional 这个注解的作用是什么
@Conditional
注解是 Spring Framework 提供的一个条件注解,用于根据指定条件决定是否创建某个 Bean 或者应用某个配置。具体来说,@Conditional
注解的作用是根据指定的条件判断是否满足,若条件满足则才会创建相应的 Bean 或者应用相应的配置。 使用@Conditional
注解可以实现一些条件化的 Bean 创建或者配置,例如基于环境、系统属性、配置值等进行条件判断。这样可以根据不同的条件,灵活地控制 Spring 应用中的 Bean 创建和配置。
1️⃣ 创建条件类实现条件接口
java
package cn.hxy.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;
}
}
2️⃣ 使用条件类
java
@Configuration
public class UserConfig {
@Bean
@Conditional(ClassCondition.class)
public User user() {
return new User();
}
}
java
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
此时当我们启动时,会发现找不到 user 的 bean,因为此时的 condition 的条件对象返回的是 false
5. 根据是否导入Jedis 判断是否让条件类返回 true
在条件类中判断 Jedis 是否导入坐标
java
package cn.hxy.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) {
boolean flag = true;
try {
//根据路径去查询文件类型,如果不存在就会报错,然后异常被捕获,执行 catch 中的代码
Class<?> aClass = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
如果导入了,此时条件类返回为 true,那么@conditional 注解满足条件,执行对应的 bean 创建注入,在启动时打印user成功
如果没有导入对应的 Jedis 坐标,那么 ClassCondition 中的判断结果为 false,则 bean 不会被创建注入,在启动时打印 user 找不到 bean
实例二
将类的判断定义为动态的.判断哪个字节码文件存在可以动态指定.
1. 定义一个注解 ConditionOnClass
将我们之前的 conditional 注解放在自定义的注解上
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ClassOnCondition {
String[] value();
}
2. 在要创建 bean 上使用我们自定义的注解判断
java
package cn.hxy.springbootcondition.config;
import cn.hxy.springbootcondition.condition.ClassOnCondition;
import cn.hxy.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
@ClassOnCondition("redis.clients.jedis.Jedis")
public User user() {
return new User();
}
}
3. 修改我们的ClassCondition 条件类
java
package cn.hxy.springbootcondition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
/**
* context 上下文对象,用于获取环境、ioc 容器、classLoader 对象
* metadata 注解元对象,可以用于获取注解定义的属性值
*/
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取注解属性值 value
Map<String, Object> map = metadata.getAnnotationAttributes(ClassOnCondition.class.getName());
System.out.println(map);
//可以指定多个属性值进行判断
String[] value = (String[]) map.get("value");
boolean flag = true;
try {
for (String s : value) {
//根据路径去查询文件类型,如果不存在就会报错,然后异常被捕获,执行 catch 中的代码
Class<?> aClass = Class.forName(s);
}
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
4. 修改我们的 pom 导入新的坐标测试
导入 fastjson 坐标进行测试
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.9.graal</version>
</dependency>
在创建 user 的 bean时使用自定义注解传入坐标
java
@Configuration
public class UserConfig {
@Bean
@ClassOnCondition("com.alibaba.fastjson.JSON")
public User user() {
return new User();
}
}
此时如果 fastjson 的坐标导入了,那么我们自定义注解中使用的自定义条件类判断的结果为 true,那么创建 user 的 bean。 否则不创建 user 的 bean,打印 user 失败,找不到 bean
那么回到我们的思考:SpringBoot 是如何知道要创建哪个 Bean 的?其实就是通过我们的 Condition来判断,判断当前环境中有没有导入我们的redis,如果导入了,就帮我们创建 RedisTemplate
其实在我们的 Springboot 中已经帮我们定义好了很多 condition 的注解,我们只需要去按照不同功能进行使用即可
java
@Bean
@ConditionalOnBean(DataSource.class)
public User user1() {
return new User();
}
例如,当我们要创建一个 user1
的 bean 时,只有当容器中存在 DataSource
类型的 Bean 时,user1
的 Bean 才会被创建注册到容器中。