SpringBoot自动装配源码剖析

SpringBoot自动装配源码剖析

前言

我们现在最常使用的框架肯定非SpringBoot莫属了,相较于Spring框架我们都知道其简化了很多配置,使其使用起来更加便捷,不需要程序员过度的关心配置。那么你知道SpringBoot是如何完成这样的工作的吗?本文就带大家来具体剖析一下工作原理。

首先我们要先弄清楚 什么是SpringBoot的自动装配,它是干什么的,又完成了什么样的工作?

用大白话来说:其实就是当我们引入starter之后可以自动 的将这个组件注入到Spring容器中供我们使用

流程图

为了方便大家理解,首先我们先通过一个流程图来简要的叙述一下SpringBoot自动装配的流程,接下来我们再从代码层面去具体分析一下它的工作原理。

源码剖析

@SpringBootApplication

这里就是我们SpringBoot的入口了,我们通过@SpringBootApplication 来标明我们的程序入口,以及完成自动装配的操作,我们点进去看一眼

很明显,@SpringBootApplication 是一个复合注解 ,它其中包含了许多的注解,但是别被这么多的注解吓得腿软了伙计,这里主要的注解其实只有三个:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan ,接下来我们逐一来讲一下它们都完成的什么事

@SpringBootConfiguration

我们点进去这个注解可以看到,这个注解也是一个复合注解,但是最主要的注解是这个@Configuration ,这个注解我们再熟悉不过了,它的作用是将使用该注解的类标明该类为配置类,并交给容器来管理,那么去除多余的注解,这个注解的主要作用就是:将被修饰的类交给容器来管理。

@ComponentScan

这个注解我们不需要点到注解内部,这个注解在我们工作中使用的可以说很频繁了,它的作用就是扫描包,并将包下的类及子类均交给容器来管理。其中有一些属性我们或许没用过,别担心老伙计,我这就给你讲一下这些属性都是干什么的。

其中只使用了一个属性:excludeFilters ,这个属性的含义为排除掉满足@Filter 条件的类,在@Filter 注解中,type 在该处的值为一个枚举,Custom表明该过滤规则为自定义规则,classes 指明了该过滤规则的类

综上所述,该注解的含义为将满足Filter除外所有所需的类加载到容器中。

@EnableAutoConfiguration

我们点到该注解内部后发现,该注解也是一个复合注解,其主要的注解为:@AutoConfigurationPackage@Import 两个注解,其中 @Import 注解为SpringBoot自动装配的 **核心注解,**我们一个一个来分析。

@AutoConfigurationPackage

我们进入到该注解的内部可以发现,里面也有一个 @Import 注解,同时该注解也为 @AutoConfigurationPackage 的核心注解。

在SpringBoot中,该注解会向Spring容器内部注册一个类型为 AutoConfigurationPackages.BasePackages 的Bean,这个Bean里保存了SpringBoot启动类的路径,后续会配合前面的@ComponentScan 注解来扫描并注入由@Component@Controller@Service@Repository@Configuration 注解所修饰的类。

我们再来看一些在 @Import 注解中声明的类 AutoConfigurationPackages.Registrar

进入到这个类内部可以发现,**Registrar为AutoConfigurationPackages的一个内部类,**其内部有两个方法:registerBeanDefinitionsdetermineImports。

其中比较重要的是 **registerBeanDefinitions 方法,**在Spring容器启动的过程中也会调用这个方法。可以看到该方法调用了 AutoConfigurationPackages.register() 方法,我们可以点到该方法内部来分析它都做了什么。

该方法内部调用了 **BeanDefinitionRegistry.registerBeanDefinition() 方法,**这个方法会将包路径封装成一个 **BasePackagesBeanDefinition,**然后将其注册到注册表中。

综上所述,@AutoConfigurationPackage 完成的就是 将包路径注册到注册表中供其他地方使用。

@Import({AutoConfigurationImportSelector.class})

这个注解的重点在于后面的 **AutoConfigurationImportSelector 这个类,**那么我们进入到这个类内部看一下都有什么。

我们可以看到这个类实现了很多类,其中比较重要的是实现的第一个类 DeferredImportSelector,我们再进入这个类内部进行分析。

可以看到该类继承了 ImportSelector 这个类,那我们就继续往里面走。

我们可以清晰的看到这个接口里只有两个方法:selectImports 方法和 getExclusionFilter 方法,其中比较重要的是 selectImports 这个方法,那我们就去其实现类下面看看这个方法究竟完成了什么事。

或许有小伙伴会问:这么多实现类每个实现的都不一样,我怎么知道看哪个。你这就心急了不是,虽然它有很多实现类,但是我们看名字可以看到有一个 **AutoConfigurationImportSelector,**顾名思义,这个开头就是自动装配,嘿!就是它没错了,我们快去看看里面完成了啥。

在这个方法里我们可以看到它调用了 getAutoConfigurationEntry() 这个方法,那我们就去看看这个方法里完成了什么样的事情。

我们来逐一分析一下它都干了什么事儿

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1.判断是否启用了自动装配,默认为true,在yml中配置项为spring.boot.enableautoconfiguration
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        // 2.获取注解中的排除项
        // 获取注解属性
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 3.获取所有需要自动装配的配置类
        // 读取所有预配置类,即读取spring.factories
        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);
        // 4.根据排除规则进行过滤
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回自动装配的对象
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

分析getAutoConfigurationEntry方法

isEnabled

在这个方法里我们可以看到,其 defaultValue 值为 true,也就是默认为 true,同时我们也可以在 ymlproperties 中设置该项的值

getAttributes

可以看到这个方法是想要获取注解的属性

getCandidateConfigurations

其中调用了 loadFactoryNames 的方法,我们进入到这个方法内部来分析。

这个方法最后调用了 loadSpringFactories 方法,我们再进到这个方法进一步去分析。

在这里我们清晰的看到在这里去读取了 META-INF 下的 spring.factories 文件,然后将所有读取到的预配置类返回,spring.factories文件内容如下:

其内容是以key-value的形式进行存储的,用于存储预加载类的全限定名。

removeDuplicates

这个方法就很好理解了,就是去重再返回

getExclusions

这里就是获取注解中声明的排除项

checkExcludedClasses

这里做的工作就是去二次校验是否有遗漏的配置类没被排除中,如果有则补上,再配合 removeAll 方法将被排除的类从预加载类中删除

最后几步就是根据排除规则去过滤需要排除掉的类,最后返回一个自动装配的对象,最终完成自动装配的工作。

注意事项

**Q:**所有的自动装配的类都会被加载到这个地方吗,有没有其他的配置?

**A:**并不是所有的配置都会被加载到此处,而是 所有的starter下的 META-INF/spring.factories 都会被加载

**Q:**spring.factories中有众多的配置类,随着后续依赖的增加配置也会越来越多,那么每次启动都要加载这些类吗?

**A:**并不是每次都会加载,因为在此处还经历了一个筛选的过程,即:@ConditionalOnXXX ,只有满足了该注解中的所有条件,最终才会被加载,如下:

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi :在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

总结

SpringBoot的自动装配就是 将我们引入的starter中的组件自动加载到Spring容器进行管理的过程。

SpringBoot自动装配的过程是:通过 @EnableAutoConfiguration 注解来开启SpringBoot的自动装配,然后将我们要自动装配的类加载到 spring.factories 中,最后通过 SpringFactoriesLoader 来加载 spring.factories 中的配置类来实现自动装配,并通过 @ConditionalOnXXX 注解来按需加载。

相关推荐
用户9083246027321 小时前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840822 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解2 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解2 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记2 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者3 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840823 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解3 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者4 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺4 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端