SpringBoot 源码学习——SpringBoot 自动装配源码解析 +Spring 如何处理配置类的

发布于:2022-12-12 ⋅ 阅读:(548) ⋅ 点赞:(0)

一丶什么是 SpringBoot 自动装配

SpringBoot 通过 SPI 的机制,在我们程序员引入一些 starter 之后,扫描外部引用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器,实现引入 starter 即可开启相关功能的操作,大大简化了程序员手动配置 bean,即开即用。

二丶 SpringBoot 自动装配源码解析

1.源码解析入口

SpringApplication.run(启动类.class, args)

复制代码

这是我们最常用的 Main 方法启动 SpringBoot 服务的方式,其中启动类上需要标注 @SpringBootApplication 注解,自动装配,扫描主类下所有 Bean 的奥秘就在此

2.@SpringBootApplication 注解

@SpringBootApplication 本身没有什么神奇的地方,重要的是注解上面标注了 @SpringBootConfiguration , @EnableAutoConfiguration ,和 @ComponentScan 注解

  • @SpringBootConfiguration 平平无奇,上面标注了 @Configuration 表示标注的类是一个配置类

  • @EnableAutoConfiguration

    表示开启自动配置,即我们说的 SpringBoot 自动装配、

  • @AutoConfigurationPackage 其上方标注了 @Import(AutoConfigurationPackages.Registrar.class) ,加上 @EnableAutoConfiguration 上的 @Import(AutoConfigurationImportSelector.class) . @Import 注解的作用是导入一些 bean 到 Spring 容器中,实现此功能的是 ConfigurationClassPostProcessor ,它是一个 BeanFactoryPostProcessor 会解析配置类中的 @Bean,@Import,@ComponentScan 等注解

  • @ComponentScan ,指导 Spring 容器需要扫描哪些包下的类加入到 Spring 容器

也就是说 @SpringBootApplication 相当于

3.自动配置源码学习前言

SpringBoot 并不是 Spring 的替代品,而是利用 Spring 加上 约定大于配置 的思想,方便程序员开发的框架。所以其底层还是 Spring 那一套

本篇着重研究 SpringBoot 的自动装配原理,所以一些无关的代码不会进行详细探究,后续会单独学习整理。

4.SpringApplication#run 是如何初始化 ApplicationContext 的

既然 SpringBoot 是基于 Spring 的,那么必然是无法脱离 ApplicationContext 的,接下来我们以 SpringApplication#run 为入口看看,SpringApplication 是如何初始化一个 ApplicationContext 的

4.1 SpringApplication 的初始化

我们在启动类的 main 方法中 SpringApplication.run(启动类.class, args) 其实最终调用的是

在其构造方法中:

  • 根据当前项目判断 Web 应用类型

  • 初始化 ApplicationContextInitializer ,和 ApplicationListener

    这部分是通过读 META-INF/spring.factories 中的内容反射进行初始化,前者是用于在刷新之前初始化 Spring ConfigurableApplicationContext 的回调接口,后者是 Spring 监听器,后续会进行专门的学习。

  • 获取主类

    会 new 出一个 RuntimeException ,然后分析 StackTraceElement 找到方法名称为 main ,然后获取类名

springboot 启动源码 自动配置需要关注的部分框出

4.2 根据项目创建一个合适的 ApplicationConext

这里便是通过 web 应用的类型,反射生成 AnnotationConfigServletWebServerApplicationContext 类型的上下文,也就是说,如果当前项目中存在 Servlet ,和 ConfigurableWebApplicationContext 那么 SpringBoot 会选择 AnnotationConfigServletWebServerApplicationContext

其中 ServletWebServerApplicationContext 具备启动 Serlvet 服务器(如 Tomcat)并将 Servlet 类型的 bean 或过滤器类型的 bean 都注册到 Web 服务器的能力

AnnotationConfigServletWebServerApplicationContext 则是在 ServletWebServerApplicationContext 上增加了根据类路径扫描,注册 Component 到上下文的能力

4.3 刷新 ApplicationContext 的前置准备

在 prepareContext 方法中,SpringBoot 会把主类注册到 Spring 容器中,为什么要这么做昵,——主类上的注解 @SpringBootApplication 需要 ConfigurationClassPostProcessor 解析,才能发挥 @Import,@ComponentScan 的作用,想要 ConfigurationClassPostProcessor 处理主类的前提是主类的 BeanDefinition 需要在 Spring 容器中

这里的 BeanDefinitionRegistry 即是 AnnotationConfigServletWebServerApplicationContext 中持有的 DefaultListableBeanFactory

如果是 CharSequence 类型,会尝试使用 Class.forName 解析成类,然后尝试使用解析 Resouce,解析的 Package 的方式处理。

这里使用 AnnotatedBeanDefinitionReader 注册我们的主类

简单来说就是会将主类的信息包装成 AnnotatedGenericBeanDefinition ,其中会解析 @Scope , @Lazy , @Primary , @DependsOn 等注解设置到 AnnotatedGenericBeanDefinition 中,然后调用 BeanDefinitionCustomizer#customize 允许我们自定义处理 BeanDefinition。

4.4 刷新 ApplicationContext

这里就是调用 AnnotationConfigServletWebServerApplicationContext#refresh 方法,来到 AbstractApplicationContext 的 refresh 方法中,执行流程如下

这里我们需要注意 调用 BeanFactoryPostProcessor ,因为这里将调用到 ConfigurationClassPostProcessor ,接下来我们将分析其源码,看看它究竟做了什么

4.4.1 解析配置类中的相关注解

这一步发生在 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 中

4.4.1.1 遍历所有的 BeanDefinition 进行筛选

可以看到如果需要处理,会放入到集合中,那么什么样的类才需要进一步处理昵,首先这个类的 BeanDefinition 需要存在于 Spring 容器中

  • 具备 @Configuration 注解,会被标记为了 full 模式

  • 具备 @Component , @ComponentScan , @Import , @ImportResource 其中任何一个注解,会被标记为 lite 模式

  • 具备一个方法标注了 @Bean 注解,会被标记为 lite 模式

full 和 lite 的区别后面会将

获取候选者后会根据其 @Order 注解中的顺序进行排序,SpringBoot 项目通常这时候只有主类

4.4.1.2 使用 ConfigurationClassParser 解析候选者

在这里 SpringBoot 的启动类,会被解析,

  1. 首先是进行条件注解解析,如果不符合条件那么什么都不做。这里的条件注解指的是 @Conditional 及其复合注解 @ConditionOnClass , @ConditionOnBean 等

  2. 进行解析

这里循环当前类和其父类调用 doProcessConfigurationClass 进行解析,需要注意的是:如果父类上的 Condition 注解不满足,但是子类满足,但是子类是一个配置类,父类中的 @Bean 等注解,还是会进行解析

  1. 如果标注了 @Component 及其复合注解那么 解析内部类

    ConfigurationClassParser 会把当前配置类中的内部类也当作配置类解析,也就是说如果 A 是一个配置类候选者,内部类没有 @Component,@Configuration 也会当做配置进行解析

  2. 解析 @PropertySources 和 @PropertySource

    ConfigurationClassParser 会将 @PropertySources 指定的配置,加入到 Environment 中

  3. 解析 @ComponentScans 和 @ComponentScan

    这一步会确认条件注解中的内容满足,然后使用 ComponentScanAnnotationParser ,获取指定的路径,如果没有指定任何路径那么使用当前配置所在的路径,这也是为什么 SpringBoot 主类上没有指定扫描路径,但是默认加载主类所在包下所有类。扫描包路径下的所有类,使用指定的 TypeFilter 进行过滤(检查是否具备 @Component 注解)且条件注解满足才会注册对应的 BeanDefinition 到容器中,这里 SpringBoot 指定了 AutoConfigurationExcludeFilter ,其作用是排除掉扫描到的自动装配类,因为自动装配类由 @Import(AutoConfigurationImportSelector.class) 导入的 AutoConfigurationImportSelector 来处理

扫到的类,还会当前配置类进行解析,如果是一个配置类即满足

  • 具备 @Configuration 注解,会被标记为了 full 模式

  • 具备 @Component , @ComponentScan , @Import , @ImportResource 其中任何一个注解,会被标记为 lite 模式

  • 具备一个方法标注了 @Bean 注解,会被标记为 lite 模式

任何一个条件 那么会再次处理,有点递归的意思

  1. 处理 @@Import 注解

    获取类上面的 @Import 注解内容

  • 导入的类是 ImportSelector 类型

    反射实例化 ImportSelector

    如果此 ImportSelector 实现了 BeanClassLoaderAware , BeanFactoryAware , EnvironmentAware , EnvironmentAware , ResourceLoaderAware 会回调对应的方法

    调用当前 ImportSelector 的 selectImports ,然后递归执行处理 @Import 注解的方法,也就是说可以导入一个具备 @Import 的类,如果没有``@Import`那么当中配置类解析

  • 导入的类是 ImportBeanDefinitionRegistrar 类型

    反射实例化 ImportBeanDefinitionRegistrar ,然后加入到 importBeanDefinitionRegistrars 集合中后续会回调其 registerBeanDefinitions

  • 既不是 ImportBeanDefinitionRegistrar 也不是 ImportSelector ,将导入的类当做配置类处理,后续会判断条件注解是否满足,然后解析导入的类,并且解析其父类

这一步便会解析到 @Import(AutoConfigurationImportSelector.class)进行自动装配,具体操作后续讲解

  1. 处理 @ImportResource 注解

    获取注解中指定的路径资源,和指定的 BeanDefinitionReader 类型,然后包装到 importedResources 集合中,后续回调 BeanDefinitionReader#loadBeanDefinitions (默认使用 XmlBeanDefinitionReader ),也就是说我们可以使用 @ImportResource 导入一些定义在 xml 中的 bean

  2. 处理标注 @Bean 注解的方法

    会扫描标注 @Bean 的方法,存到 beanMethods 集合中,后续解析方法上的条件注解,如果满足条件,将包装成 ConfigurationClassBeanDefinition ,其中 bean 名称和别名来自 @Bean 中 name 指定,并且指定其 factoryMethodName ,后续实例化 bean 的时候将反射调用标注的方法生成 bean,然后解析 @Lazy , @Primary , @DependsOn 等注解,还会解析 @Bean 注解中标注的是否依赖注入候选者,初始化方法,销毁方法,以及 @Scope 注解,然后注册到 BeanDefinitionRegistry 中

  3. 处理接口中标注 @Bean 的默认方法

    获取当前类实现的全部的接口,且非抽象的方法,然后进行 6.处理标注 @Bean 注解的方法

4.4.2 增强配置类

在 ConfigurationClassPostProcessor#postProcessBeanFactory 方法中,会对 full 模式的配置进行增强,full 模式指标注 @Configuration 注解的类,调用其 enhanceConfigurationClasses 方法,拦截 @Bean 方法,以确保正确处理 @Bean 语义。Spring 将使用 CGLIB 对原配置进行增强,获取增强后的类,替换调用原 BeanDefinition 记录的类,后续将使用此加强类,这也做的目的在于,调用配置类标注了 @Bean 方法的时候,不会真正调用其中的逻辑,而是直接取 BeanFactory#getBean 中取,保证 @Bean 标注的方法,产生 bean 的生命周期完整

4.5 自动装配源码解析

上面关于 ConfigurationClassPostProcessor 类的源码解析,我们明白了 Spring 是如何解析一个配置类的,其中和 SpringBoot 自动装配关系最密切的是对 @Import 注解,SpringBoot 启动上标注的 @SpringBootApplication 包含了 @Import(AutoConfigurationImportSelector.class) ,下面我们将解析 AutoConfigurationImportSelector 到底做了什么来实现自动装配

首先我们可以通过 spring.boot.enableautoconfiguration 来设置是否开启自动配置,那怕再配置类上面标注了 @EnableAutoConfiguration 也可以进行关闭。

然后会先读取 spring-autoconfigure-metadata.properties ,此文件存储的是”待自动装配候选类“过滤的计算规则,会根据里面的规则逐一对候选类进行计算看是否需要被自动装配进容器,并不是全部加载

然后是读取 META-INF/spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的自动配置类,如

  • 首先使用 classLoader 读 META-INF/spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的内容,然后进行去重

  • 然后获取自动装配注解标注的 exclude 和 excludeName 表示不需要进行自动装配的类,并排除掉这些类

  • 然后获取 META-INF/spring.factories 中 org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 对应的内容,实例化成 AutoConfigurationImportFilter 调用其 match 方法,判断这些自动装配类是否需要被过滤掉,这是 springboot 留给我们的一个扩展点,如果需要读取缓存中的内容进行对自动配置类的过滤,我们可以自己实现一个 AutoConfigurationImportFilter 放在 META-INF/spring.factories 中,如 org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=com.a.xx 即可进行自定义过滤

  • 紧接着会发送一个 AutoConfigurationImportEvent 事件

    关于 SpringBoot 的事件会在下一篇中讲解

  • 最后会把需要自动装配的类全限定类名返回,接着就到了 ConfigurationClassPostProcessor 中,它会继续使用 ConfigurationClassParser 将这些自动配置类进一步解析

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到