SpringBoot自动配置

一.Condition接口

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

当我们为spring容器添加了redis坐标后,我们就可以通过getBean()方法获取到redisTemplate对象,如果没有添加坐标则会报错,那么spring容器是怎么知道我们要配置那个类呢?

二.@conditional注解

其实spring容器是通过@conditional注解来判断我们是否添加了Redis坐标。

@conditional注解只有一个属性,就是一个Condition类型的数组,在Condition接口中只有一个matches方法,用于判断是否注入相关类,当matches返回值为true时,spring会进行注入,所以我们使用 @conditional注解时要给它一个属性值作为判断是否注入相关类的条件

所以我们用一个案例说明:

案例:

需求1: 在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:

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

我们自定义一个配置类(UserConfig)和一个Condition的实现类(ClassCondition):

java 复制代码
public class ClassCondition  implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        /**
         *
         * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
         * @param metadata 注解元对象。 可以用于获取注解定义的属性值
         * @return
         */
        //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;
        }

        return flag;
    }

}
java 复制代码
public class UserConfig {
    //@Conditional中的ClassCondition.class的matches方法,返回true执行以下代码,否则反之
    @Bean
    @Conditional(value= ClassCondition.class)
    public User user(){
        return new User();
    }

}

@conditional注解的属性是我们自定义的Condition的实现类,在这个实现类中,我们重写了matches方法,用于自定义判断条件,当这个条件成立时,即我们导入了jedis坐标,spring会为我们自动注入相关类。

当我们没有注入jedis坐标时,matches返回值为false,所以就无法创建User对象:

java 复制代码
@SpringBootApplication
public class Springboot815ConditionZidongpeizhiApplication {

    public static void main(String[] args) {
        //启动SpringBoot的应用,返回Spring的IOC容器
        ConfigurableApplicationContext context =  SpringApplication.run(Springboot815ConditionZidongpeizhiApplication.class, args);
        /********************案例1********************/
        Object user = context.getBean("user");
        System.out.println(user);
    }
}

当我们注入jedis坐标后,我们成功获取到了User对象:

那当我们的判断条件为添加多个坐标时才创建对象,要是一个一个重写太麻烦,所以我们使用动态装配。

需求二:

在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:

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

实现步骤:

不使用@Conditional(ClassCondition.class)注解 自定义注解@ConditionOnClass,因为他和之前@Conditional注解功能一直,所以直接复制 编写ClassCondition中的matches方法:

//1.自定义注解(ConditionOnClass):

java 复制代码
import java.lang.annotation.*;
//自定义注解(仿照conditional注解)
@Target({ElementType.TYPE, ElementType.METHOD})//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented//生成文档
@Conditional(value=ClassCondition.class)

public @interface ConditionOnClass {
    String[] value();//设置此注解的属性redis.clients.jedis.Jedis

}

//2.配置类

用一个map存所有的标签名,然后通过遍历来一个个判断他们的坐标是否导入了,所有坐标都添加了才会创建对象。

java 复制代码
public class ClassCondition implements Condition {
    /**
     *
     * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
     * @param metadata 注解元对象。 可以用于获取注解定义的属性值
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        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;
        }
        return flag;


    }
}

此处的value为:"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis",只有这两个坐标均添加,才会创建user对象:

情况1:没有添加fastjson坐标

user对象无法创建:

当两个坐标都添加后:

三.@Enable注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现Bean的动态加载

@Import注解

@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:

我们先创建一个import_demo工程,将他里面的对象导入另一个工程(demo):

//1.创建两个实体类(User和Student):

java 复制代码
public class User {
}
java 复制代码
public class Student {
}

//2.创建配置类

java 复制代码
@Configuration
public class UserConfig {
    @Bean
    public User user() {
        return new User();
    }
    @Bean
    public Student student() {
        return new Student();
    }


}

//3.ImportSelector 实现类:

java 复制代码
public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
        return new String[]{"com.apesource.domain.User", "com.apesource.domain.Student"};

    }

//4.ImportBeanDefinitionRegistrar实现类

java 复制代码
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //AnnotationMetadata注解
        //BeanDefinitionRegistry向spring容器中注入

        //1.获取user的definition对象
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();

        //2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
        registry.registerBeanDefinition("user", beanDefinition);

    }
}

demo工程:

//1.导入 import_demo坐标

① 导入Bean

java 复制代码
@SpringBootApplication
//@ComponentScan("com.apesource.import_demo02.config")
@Import(User.class)//导入javaBean
public class SpringbootImportDemo01Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);
        /**
         * @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包
         *  当前引导类所在包com.apesource.springbootenable03
         *  注入user类所在包com.apesource.springbootenable_other04.config
         *  因此扫描不到,所以容器中没有user
         *  解决方案:
         *          1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
         *          2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
         *          3.可以对Import注解进行封装。
         *
         */
//
        Student student = context.getBean(Student.class);
        System.out.println(student);

        //获取Bean
        User user = context.getBean(User.class);
        System.out.println(user);

    }
}

② 导入配置类

java 复制代码
@SpringBootApplication
@Import(UserConfig.class)
public class SpringbootImportDemo01Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);
        /**
         * @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包
         *  当前引导类所在包com.apesource.springbootenable03
         *  注入user类所在包com.apesource.springbootenable_other04.config
         *  因此扫描不到,所以容器中没有user
         *  解决方案:
         *          1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
         *          2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
         *          3.可以对Import注解进行封装。
         *
         */
//
        Student student = context.getBean(Student.class);
        System.out.println(student);

        //获取Bean
        User user = context.getBean(User.class);
        System.out.println(user);

    }
}

③ 导入 ImportSelector 实现类。

java 复制代码
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringbootImportDemo01Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);
        /**
         * @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包
         *  当前引导类所在包com.apesource.springbootenable03
         *  注入user类所在包com.apesource.springbootenable_other04.config
         *  因此扫描不到,所以容器中没有user
         *  解决方案:
         *          1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
         *          2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
         *          3.可以对Import注解进行封装。
         *
         */
//
        Student student = context.getBean(Student.class);
        System.out.println(student);

        //获取Bean
        User user = context.getBean(User.class);
        System.out.println(user);

    }
}

④ 导入 ImportBeanDefinitionRegistrar 实现类。

java 复制代码
@SpringBootApplication
@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootImportDemo01Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);
        /**
         * @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包
         *  当前引导类所在包com.apesource.springbootenable03
         *  注入user类所在包com.apesource.springbootenable_other04.config
         *  因此扫描不到,所以容器中没有user
         *  解决方案:
         *          1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
         *          2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
         *          3.可以对Import注解进行封装。
         *
         */
//
        Student student = context.getBean(Student.class);
        System.out.println(student);

        //获取Bean
        User user = context.getBean(User.class);
        System.out.println(user);

    }
}

结果:

相关推荐
皮皮林5519 分钟前
SpringBoot 集成 Hera,让日志查看从 “找罪证” 变 “查答案”!
spring boot
num_killer14 分钟前
小白的Langchain学习
java·python·学习·langchain
期待のcode1 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐1 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲1 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红1 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥1 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v1 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地2 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209252 小时前
Guava Cache 原理与实战
java·后端·spring