掘金 后端 ( ) • 2024-04-19 10:07

theme: channing-cyan

SpringBoot自动配置

Condition

Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作。
思考:比如 SpringBoot 是如何知道要创建哪个 Bean 的?比如 SpringBoot 是如何知道要创建 RedisTemplate 的?

背景(结合上面的思考)

  1. 创建一个 SpringBoot项目,不整合框架
@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

  1. 导入 redis 的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

再次启动发现没有报错,说明我们找到了 bean


实例一

在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
1.导入 Jedis 坐标厚,加载该 Bean,没导入,则不加载

1. 创建一个 User 实体类

public class User {

}

2. 创建一个配置类 UserConfig

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

3. 在启动类中获取user 的 bean

@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️⃣ 创建条件类实现条件接口

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️⃣ 使用条件类

@Configuration
public class UserConfig {
    @Bean
    @Conditional(ClassCondition.class)
    public User user() {
        return new User();
    }
}
@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 是否导入坐标

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 注解放在自定义的注解上

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ClassOnCondition {
    String[] value();
}

2. 在要创建 bean 上使用我们自定义的注解判断

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 条件类

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 坐标进行测试

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.9.graal</version>
</dependency>

在创建 user 的 bean时使用自定义注解传入坐标

@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 的注解,我们只需要去按照不同功能进行使用即可

@Bean
@ConditionalOnBean(DataSource.class)
public User user1() {
    return new User();
}

例如,当我们要创建一个 user1 的 bean 时,只有当容器中存在 DataSource 类型的 Bean 时,user1的 Bean 才会被创建注册到容器中。

SpringBoot监听机制

SpringBoot启动流程分析