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);

    }
}

结果:

相关推荐
智慧老师27 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm29 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
V+zmm101341 小时前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
Oneforlove_twoforjob1 小时前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
xmh-sxh-13141 小时前
常用的缓存技术都有哪些
java
迷糊的『迷』2 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
J不A秃V头A2 小时前
IntelliJ IDEA中设置激活的profile
java·intellij-idea
DARLING Zero two♡3 小时前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode
小池先生3 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端