Spring Boot自动装配原理

Spring Boot自动装配原理

一、什么是自动装配?

自动装配通过分析类路径下的依赖、Bean的定义以及其他配置信息,自动配置Spring应用程序的配置类和Bean,以减少开发人员的配置工作,提高开发效率。

Spring Boot的自动装配实际上是从 META-INF/spring.factories 文件中获取到对应的需要进行自动装配的类,并生成相应的Bean对象,然后将它们交给Spring容器进行管理。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置。

二、Spring Boot如何实现自动装配?

通过SpringBoot项目启动类上的@SpringBootApplication注解来实现。

@SpringBootApplication注解核心源码:

java 复制代码
//自定义注解相关注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//自动装配相关注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}

@SpringBootApplication主要由 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解组成。

  • @SpringBootApplication:标注在某个类上,表示这是一个Spring Boot的配置类;
  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

@EnableAutoConfiguration 是实现自动装配的主要注解。

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
  • @AutoConfigurationPackage:将主程序类所在包及所有子包下的组件扫描到Spring容器中。
  • @Import:
        ① 导入Bean
        ② 导入配置类
        ③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
        ④ 导入 ImportBeanDefinitionRegistrar 实现类。

由源码可见自动装配的核心功能与引入的 AutoConfigurationImportSelector 类有关。

java 复制代码
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

在AutoConfigurationImportSelector 类中发现它实现了ImportSelector接口,也就实现了这个接口中的 selectImports 方法,该方法主要用于获取所有符合条件的类的全限定类名,并以字符串数组返回,这些类需要被加载到 IoC 容器中。

该方法主要通过调用**getAutoConfigurationEntry()**方法获取AutoConfigurationEntry对象,这个方法主要负责加载自动配置类的。

java 复制代码
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    }

该方法通过调用 List configurations = getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入到容器中的配置类。

具体是在该类的 getCandidateConfigurations 方法中调用了 SpringFactoriesLoader类的 loadFactoryNames 方法获取所有自动转配类名,loadSpringFactories() 方法从META-INF/spring.factories加载自动装配类。

最后按照条件装配(@Conditional)最终会按需配置。

加载 spring.factories 中的配置,但不是每次启动都会加载其中的所有配置,会有一个筛选的过程,去掉重复的配置。

总结

  1. Spring Boot项目中@SpringBootApplication注解实现自动装配,这个注解是对三个注解进行了封装:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,
    其中@EnableAutoConfiguration是实现自动化配置的核心注解。
  2. 该注解通过@Import注解导入AutoConfigurationImportSelector,这个类实现了一个导入器接口ImportSelector。在该接口中重写了一个方法selectImports。
  3. selectImports方法的返回值是一个数组,数组中存储的就是要被导入到spring容器中的类的全限定名。在AutoConfigurationImportSelector类中重写了这个方法。
  4. 该方法内部就是读取了项目的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
  5. 在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

三、Condition

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

@Conditional 有一个属性 value,其类型是 Condition 数组。组件必须匹配数组中所有的 Condition,才可以被注册。

Condition 是一个函数式接口,只有一个 matches 方法,返回 true 则表示条件匹配。matches 方法的两个参数分别是上下文信息和注解的元信息,从这两个参数中可以获取到 IOC 容器和当前组件的信息,从而判断条件是否匹配。

java 复制代码
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  1. 没有添加坐标前,发现为空,报错
  2. 有添加坐标前,发现有对象
java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

@Configuration
public class UserConfig {
    @Bean
    public User user(){
        return new User();
    }
}
@SpringBootApplication
@EnableScheduling
@Import(UserConfig.class)
@EnableAsync
public class Springbootcondition03Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Springbootcondition03Application.class, args);
        User user = context.getBean(User.class);
        System.out.println(user);

    }

}
java 复制代码
@Configuration
public class UserConfig {
    @Bean
//    @Conditional(value = ClassCondition.class)
//    public User user(){
//        return new User();
//    }
    @ConditionOnClass(value = {"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis"})
    public User user(){
        return new User();
    }

    @Bean
    @ConditionalOnProperty(name = "k1",havingValue = "v2")
    public User user2(){
        return new User();
    }
}

public class ClassCondition implements Condition {
    //Params:
    //context -- 上下文对象。用于获取环境,IOC容器,ClassLoader对象 metadata -- 注解元对象。 可以用于获取注解定义的属性值
    //Returns:
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//        boolean flag = true;
//        try {
//            Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
//        } catch (ClassNotFoundException e) {
//            flag = false;
//        }
//        return flag;


        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;

    }
}

@Target({ElementType.TYPE,ElementType.METHOD})//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented//生成文档
@Conditional(value = ClassCondition.class)
public @interface ConditionOnClass {
    String[] value();//设置此注解的属性redis.clients.jedis.Jedis
}


@SpringBootApplication
public class SpringbootCondition01Application {

    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition01Application.class, args);

        Object user = context.getBean("user");
        System.out.println(user);
    }

}

Condition -- 小结

自定义条件:

  1. 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。

    matches 方法两个参数:

    • context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
    • metadata:元数据对象,用于获取注解属性。
  2. 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解

    SpringBoot 提供的常用条件注解:

    以下注解在SpringBoot-Autoconfigure的condition包下

    • ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
    • ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
    • ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
    • ConditionalOnBean:判断环境中有对应Bean才初始化Bean
相关推荐
凸头8 小时前
Collections.synchronizedList()详解
java
开心就好20258 小时前
iOS 26 文件管理实战,多工具组合下的 App 数据访问与系统日志调试方案
后端
乘风破浪酱524368 小时前
PO、DTO、VO的区别与应用场景详解
后端
用户0273851840268 小时前
【Android】MotionLayout详解
java·程序员
Jammingpro8 小时前
【Git版本控制】Git初识、安装、仓库初始化与仓库配置(含git init、git config与配置无法取消问题)
java·git·elasticsearch
wydaicls8 小时前
AIDL 接口的定义与生成,使用
java·开发语言
云草桑8 小时前
C#入坑JAVA 使用XXLJob
java·开发语言·c#
悟能不能悟8 小时前
springboot在DTO使用service,怎么写
java·数据库·spring boot
Uluoyu8 小时前
支持Word (doc/docx) 和 PDF 转成一张垂直拼接的长PNG图片工具类
java·pdf·word
__XYZ8 小时前
RedisTemplate 实现分布式锁
java·spring boot·redis·分布式·junit