SpringBoot 原理分析

SpringBoot自动配置

Condition

Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作。

思考:比如 SpringBoot 是如何知道要创建哪个 Bean 的?比如 SpringBoot 是如何知道要创建 RedisTemplate 的?

背景(结合上面的思考)

  1. 创建一个 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

  1. 导入 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 才会被创建注册到容器中。

SpringBoot监听机制

SpringBoot启动流程分析

相关推荐
丘山子28 分钟前
一些鲜为人知的 IP 地址怪异写法
前端·后端·tcp/ip
CopyLower1 小时前
在 Spring Boot 中实现 WebSockets
spring boot·后端·iphone
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑2 小时前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
晨集2 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城2 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
追逐时光者3 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵3 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫3 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
嘻嘻嘻嘻嘻嘻ys3 小时前
《Spring Boot 3 + Java 17:响应式云原生架构深度实践与范式革新》
前端·后端