SpringBoot 通过 SPI 的机制,在我们程序员引入一些 starter 之后,扫描外部引用 jar 包中的 META-INF/spring.factories
文件,将文件中配置的类型信息加载到 Spring 容器,实现引入 starter 即可开启相关功能的操作,大大简化了程序员手动配置 bean,即开即用。
SpringApplication.run(启动类.class, args)
复制代码
这是我们最常用的 Main 方法启动 SpringBoot 服务的方式,其中启动类上需要标注 @SpringBootApplication
注解,自动装配,扫描主类下所有 Bean 的奥秘就在此
@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
相当于
SpringBoot 并不是 Spring 的替代品,而是利用 Spring 加上 约定大于配置
的思想,方便程序员开发的框架。所以其底层还是 Spring 那一套
本篇着重研究 SpringBoot 的自动装配原理,所以一些无关的代码不会进行详细探究,后续会单独学习整理。
既然 SpringBoot 是基于 Spring 的,那么必然是无法脱离 ApplicationContext 的,接下来我们以 SpringApplication#run
为入口看看,SpringApplication 是如何初始化一个 ApplicationContext 的
我们在启动类的 main 方法中 SpringApplication.run(启动类.class, args)
其实最终调用的是
在其构造方法中:
根据当前项目判断 Web 应用类型
初始化 ApplicationContextInitializer
,和 ApplicationListener
这部分是通过读 META-INF/spring.factories
中的内容反射进行初始化,前者是用于在刷新之前初始化 Spring ConfigurableApplicationContext 的回调接口,后者是 Spring 监听器,后续会进行专门的学习。
获取主类
会 new 出一个 RuntimeException
,然后分析 StackTraceElement
找到方法名称为 main
,然后获取类名
这里便是通过 web 应用的类型,反射生成 AnnotationConfigServletWebServerApplicationContext
类型的上下文,也就是说,如果当前项目中存在 Servlet
,和 ConfigurableWebApplicationContext
那么 SpringBoot 会选择 AnnotationConfigServletWebServerApplicationContext
其中 ServletWebServerApplicationContext
具备启动 Serlvet 服务器(如 Tomcat)并将 Servlet 类型的 bean 或过滤器类型的 bean 都注册到 Web 服务器的能力
AnnotationConfigServletWebServerApplicationContext
则是在 ServletWebServerApplicationContext
上增加了根据类路径扫描,注册 Component 到上下文的能力
在 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。
这里就是调用 AnnotationConfigServletWebServerApplicationContext#refresh
方法,来到 AbstractApplicationContext
的 refresh
方法中,执行流程如下
这里我们需要注意 调用 BeanFactoryPostProcessor ,因为这里将调用到 ConfigurationClassPostProcessor
,接下来我们将分析其源码,看看它究竟做了什么
这一步发生在 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
中
可以看到如果需要处理,会放入到集合中,那么什么样的类才需要进一步处理昵,首先这个类的 BeanDefinition 需要存在于 Spring 容器中
具备 @Configuration
注解,会被标记为了 full
模式
具备 @Component
, @ComponentScan
, @Import
, @ImportResource
其中任何一个注解,会被标记为 lite
模式
具备一个方法标注了 @Bean
注解,会被标记为 lite
模式
full
和 lite
的区别后面会将
获取候选者后会根据其 @Order
注解中的顺序进行排序,SpringBoot 项目通常这时候只有主类
ConfigurationClassParser
解析候选者在这里 SpringBoot 的启动类,会被解析,
首先是进行条件注解解析,如果不符合条件那么什么都不做。这里的条件注解指的是 @Conditional
及其复合注解 @ConditionOnClass
, @ConditionOnBean
等
进行解析
这里循环当前类和其父类调用 doProcessConfigurationClass
进行解析,需要注意的是:如果父类上的 Condition 注解不满足,但是子类满足,但是子类是一个配置类,父类中的 @Bean 等注解,还是会进行解析
如果标注了 @Component
及其复合注解那么 解析内部类
ConfigurationClassParser
会把当前配置类中的内部类也当作配置类解析,也就是说如果 A 是一个配置类候选者,内部类没有 @Component,@Configuration 也会当做配置进行解析
解析 @PropertySources
和 @PropertySource
ConfigurationClassParser
会将 @PropertySources
指定的配置,加入到 Environment
中
解析 @ComponentScans
和 @ComponentScan
这一步会确认条件注解中的内容满足,然后使用 ComponentScanAnnotationParser
,获取指定的路径,如果没有指定任何路径那么使用当前配置所在的路径,这也是为什么 SpringBoot 主类上没有指定扫描路径,但是默认加载主类所在包下所有类。扫描包路径下的所有类,使用指定的 TypeFilter
进行过滤(检查是否具备 @Component 注解)且条件注解满足才会注册对应的 BeanDefinition 到容器中,这里 SpringBoot 指定了 AutoConfigurationExcludeFilter
,其作用是排除掉扫描到的自动装配类,因为自动装配类由 @Import(AutoConfigurationImportSelector.class)
导入的 AutoConfigurationImportSelector
来处理
扫到的类,还会当前配置类进行解析,如果是一个配置类即满足
具备 @Configuration
注解,会被标记为了 full
模式
具备 @Component
, @ComponentScan
, @Import
, @ImportResource
其中任何一个注解,会被标记为 lite
模式
具备一个方法标注了 @Bean
注解,会被标记为 lite
模式
任何一个条件 那么会再次处理,有点递归的意思
处理 @@Import
注解
获取类上面的 @Import
注解内容
导入的类是 ImportSelector
类型
反射实例化 ImportSelector
如果此 ImportSelector
实现了 BeanClassLoaderAware
, BeanFactoryAware
, EnvironmentAware
, EnvironmentAware
, ResourceLoaderAware
会回调对应的方法
调用当前 ImportSelector
的 selectImports
,然后递归执行处理 @Import
注解的方法,也就是说可以导入一个具备 @Import
的类,如果没有``@Import`那么当中配置类解析
导入的类是 ImportBeanDefinitionRegistrar
类型
反射实例化 ImportBeanDefinitionRegistrar
,然后加入到 importBeanDefinitionRegistrars
集合中后续会回调其 registerBeanDefinitions
既不是 ImportBeanDefinitionRegistrar
也不是 ImportSelector
,将导入的类当做配置类处理,后续会判断条件注解是否满足,然后解析导入的类,并且解析其父类
这一步便会解析到 @Import(AutoConfigurationImportSelector.class)进行自动装配,具体操作后续讲解
处理 @ImportResource
注解
获取注解中指定的路径资源,和指定的 BeanDefinitionReader
类型,然后包装到 importedResources
集合中,后续回调 BeanDefinitionReader#loadBeanDefinitions
(默认使用 XmlBeanDefinitionReader
),也就是说我们可以使用 @ImportResource
导入一些定义在 xml 中的 bean
处理标注 @Bean
注解的方法
会扫描标注 @Bean
的方法,存到 beanMethods
集合中,后续解析方法上的条件注解,如果满足条件,将包装成 ConfigurationClassBeanDefinition
,其中 bean 名称和别名来自 @Bean 中 name 指定,并且指定其 factoryMethodName
,后续实例化 bean 的时候将反射调用标注的方法生成 bean,然后解析 @Lazy
, @Primary
, @DependsOn
等注解,还会解析 @Bean 注解中标注的是否依赖注入候选者,初始化方法,销毁方法,以及 @Scope
注解,然后注册到 BeanDefinitionRegistry 中
处理接口中标注 @Bean 的默认方法
获取当前类实现的全部的接口,且非抽象的方法,然后进行 6.处理标注
@Bean 注解的方法
在 ConfigurationClassPostProcessor#postProcessBeanFactory
方法中,会对 full
模式的配置进行增强,full 模式指标注 @Configuration 注解的类,调用其 enhanceConfigurationClasses
方法,拦截 @Bean
方法,以确保正确处理 @Bean
语义。Spring 将使用 CGLIB 对原配置进行增强,获取增强后的类,替换调用原 BeanDefinition 记录的类,后续将使用此加强类,这也做的目的在于,调用配置类标注了 @Bean 方法的时候,不会真正调用其中的逻辑,而是直接取 BeanFactory#getBean
中取,保证 @Bean 标注的方法,产生 bean 的生命周期完整
上面关于 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
将这些自动配置类进一步解析