前段时间面试的时候被问到了Spring的源码,问的其实也不算深,但由于距离上次看Spring源码也隔了挺久的了,差不多都忘了,导致基本都没回答出来。
为了巩固同时也为了搞懂一些以前不太清楚的问题,于是有了这篇文章。全文较长,但总体而言不难理解,就是从宏观上分析一下一些常见问题:
Spring的创建过程、bean的生命周期、bean的创建过程、beanDefinition的创建过程、Spring Boot的启动过程以及一些小点。
内容参考:
- Spring详细源码分析:https://www.bilibili.com/video/BV1T54y1n7PB/?p=77
- IOC和AOP概况分析:https://www.bilibili.com/video/BV1584y1r7n6
首先要搞清的是,我们每次创建的Spring应用,主要分为两种接口,BeanFactory
和ApplicationContext
,其中后者继承了前者,拥有比前者更多的功能,比如AOP。
在 Spring 早期版本中,使用 XmlBeanFactory 是一种创建 Spring 容器的方式。
XmlBeanFactory 实现了 BeanFactory 接口,可以从 XML 配置文件中解析 Bean 的定义
并初始化 Bean 对象。在这个版本中,Spring 并没有直接集成 AOP 功能,但是可以通过
Spring AOP 模块集成第三方 AOP 框架,如 AspectJ 或 JBoss AOP。但是,随着 Spring 版本的不断升级更新,XmlBeanFactory 被废弃,并被
ApplicationContext 所取代。ApplicationContext 继承了 BeanFactory 接口,并为
用户提供了更多的功能支持,其中就包括对 AOP 的原生支持。因此,如果你要使用
Spring AOP,建议使用 ApplicationContext 作为你的 Spring 容器,而不是过时的
XmlBeanFactory。详细可参考Java Guide上面写的。
这两个接口的实现类,就相当于是一个工厂,这里就运用到了工厂模式。只不过这个工厂有很多复杂的功能,这也侧面说明工厂模式的一个好处就是集中管理对象创建的话,能够统一增加一些额外的功能。
其中Spring对于要求单例的对象是通过ConcurrentHashMap
来管理的,以保证线程安全。同时在创建单例对象时,也会用到sychronized
锁,具体参照org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton
方法。
bean的生命周期主要分为:初始化、使用、销毁三个阶段。且可以通过下列方法进行控制:
实例化:当 Spring 容器启动时,它会通过反射机制实例化所有在配置文件或注解中配置的 Bean。这是 Bean 生命周期的起始阶段。
属性赋值:实例化之后,Spring 会根据配置文件或注解中指定的属性值,为 Bean 的属性进行赋值。这是 Bean 生命周期的第二个阶段。
自定义初始化方法:当属性赋值完成后,Spring 会调用自定义初始化方法,可以通过实现 InitializingBean 接口或在配置文件或注解中指定 init-method 方法来实现。这是 Bean 生命周期的第三个阶段。
使用:初始化方法执行完毕后,Bean 就可以被应用程序使用了。在此阶段,Bean 可以接收各种请求和调用,为应用服务。
自定义销毁方法:当应用程序结束时,Spring 容器会调用 Bean 的自定义销毁方法。可以通过实现 DisposableBean 接口或在配置文件或注解中指定 destroy-method 方法来实现。这是 Bean 生命周期的最后一个阶段。
首先,通过断点Spring容器的创建,我们可以知道是在org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
这个方法中,bean被创建了出来。
一路断点,可以来到org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
方法,这个方法我们可以看到是根据beanName
来查找beanDefinition
来判断是否存在该bean的。
这里是不断遍历
beanDefinitionNames
集合去创建bean,注意到集合是List
类型,说明保留了顺序,即 这里创建bean是严格按照顺序去创建的。
另外如果该bean是抽象的,或者非单例的,或者懒加载的,都不会被初始化。这里值得注意的一点就是只有这样的bean是在beanFactory创建时就随着一起初始化了,其他类型的bean都不会这样。
最终我们会来到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
方法。这个方法控制着bean的创建和获取,即 这个方法用来获取bean,如果获取不到,就调用不同情况进行处理,并调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean
进行初始化,然后再返回bean。值得注意的是在调用createBean()
之前,会先准备好该bean构造依赖,循环依赖的报错也是在这个时候产生的。
上面说到的createBean()
是创建bean的核心方法,里面是bean创建前后的整个过程。其中创建bean的话,会来到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
这个方法,如下图:
在doCreateBean()
中可以将bean的创建过程分为三个部分:构造对象createBeanInstance()
、填充属性populateBean()
、初始化实例initializeBean()
、注册销毁registerDisposableBeanIfNecessary()
。见下图:
构造对象阶段就单纯的是创建一个对象。
填充属性阶段就是对标有
@Autowire
注解的属性进行注入,这里就用到了三级缓存,还记得之前提到的doGetBean()
吗,这里一来会先调用getSingleton()
,而这个查找会向三级缓存中逐级查找,如下图:
如果
allowEarlyReference
为true
,那么就会执行刚刚添加的singletonFactory,已完成AOP操作。同样如果在创建完对象开始属性填充时,发生了循环依赖,这时也没关系,因为会在三级缓存中查找。初始化实例阶段主要就是调用我们写好的各种钩子函数,比如实现的
BeanNameWare
接口、实现的initializingBean
接口。AOP也是这个时候执行的,这里并不用担心在三级缓存时做过一次AOP之后,这里再做一次AOP,Spring会把做过AOP的bean放入集合以判断bean时候已经AOP过。注册销毁阶段就是将实现销毁接口的bean进行注册。
关于三级缓存详细可以参考这篇文章:Spring中的循环依赖及解决。
上面
doGetBean()
这一段代码同样也解释了为什么如果是构造器循环依赖的话,则无法解决(因为至少要初始化完对象后,才会加入缓存,注意看addSingletonFactory
执行的位置),如果是prototype
的话,也无法解决(因为addSingletonFactory
的前置条件是这个bean是单例模式)。
对于Spring应用,会根据创建的ApplicationContext
的类型(ClassPathXmlApplication、FileSystemXmlApplication、XmlWebApplicationContext),去采用不同的策略读取xml文件以完成beanDefinition的创建。
首先要明确一个思路,上面说到了,Spring IOC的核心就是beanFactory。在我们创建ApplicationContext
的过程中,会调用org.springframework.context.support.AbstractApplicationContext#refresh
,这个方法完成了beanFactory的创建。
在这个方法中,首先会通过obtainFreshBeanFactory()
创建一个beanFactory。在obtainFreshBeanFactory()
中会调用到方法org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions
完成beanDefinition的创建。这里会创建一个XmlBeanDefinitionReader
来读取我们启动程序时传入的xml文件位置,解析标签并封装成BeanDefinition
类型对象,将其存储在beanFactory的beanDefinitionMap
(也是ConcurrentHashMap
类型)中。BeanDefinition
就相当于bean的信息,bean就是依靠这些信息反射创建的。
这里提到的处理方式是基于
ClassPathXmlApplication
。不同ApplicationContext
的实现类获取beanDefinition方式会有略微不同。
beanFactory创建后,refresh()
会再调用finishBeanFactoryInitialization()
,完成单例非懒加载的bean的创建,就和我们上面说到的一样,部分bean是在beanFactory创建的过程中创建的。
Spring Boot启动分为四个阶段:服务构建、环境准备、容器创建、填充容器。
详细可参考上面第二个链接。
任何Spring Boot项目都要运行SpringApplication.run()
(注意这里是SpringApplication
类的静态run()
)以启动。这个方法首先会创建一个SpringApplication
类型对象,这个对象创建过程中会完成很多必要的配置ApplicationContext的准备操作(比如获得当前系统配置信息、根据classpath下class文件判断web应用类型、打印banner图等等操作)。
然后会调用刚刚创建的SpringApplication
类型对象的run()
:
这个方法中会完成容器(ApplicationContext)的创建。其中createApplicationContext()
会初始化一个ApplicationContext,将刚刚获得到的信息进行组合,同时prepareContext()
中会初始化好beanDefinitionMap,之后会调用refreshContext()
进行beanFactory的相关操作。
值得注意的是
refreshContext()
进行beanFactory的相关操作,这里和Spring中一样,也会来到org.springframework.context.support.AbstractApplicationContext#refresh
中:
同样也是调用
obtainFreshBeanFactory()
创建一个beanFactory,但是这个方法此时不做任何操作,因为Spring Boot是用ServletWebServerApplicationContext
作为容器,beanFactory已经创建好了,此时调用obtainFreshBeanFactory()
实际上就不会做任何操作了。另外Tomcat服务器的启动是在该方法中的
onRefresh()
中完成的。关于
refresh()
方法中调用的各个方法详细介绍可以参考视频:https://www.bilibili.com/video/BV1hv4y1z7PQ。另外关于Spring Boot中beanDefinition的扫描就和上面说得那种方式不一样。Spring Boot开始时就已经有一些基本的beanDefinition,比如我们自己程序的启动类,然后它是通过调用
invokeBeanFactoryPostProcessors()
,执行各种beanFactory的后置处理器,其中最重要的就是ConfigurationClassPostProcessor
,它会扫描所有配置类,并用ConfigurationClassParser
取扫描它们。它实现了
BeanFactoryPostProcessor
接口的postProcessBeanFactory()
,可以看到这个方法会调用processConfigBeanDefinitions()
去处理,这里会调用ConfigurationClassUtils.checkConfigurationClassCandidate()
判断是否是配置类要不要进行处理,跟踪这个方法的源码就可以知道其实这里说的配置类不一定得是有@Configuration
,有@Component
、@ComponentScan
、@Import
、@ImportResource
都可以被处理。之后就会调用
ConfigurationClassParser
的parse()
方法去处理前面扫描到的配置类。这个方法最终会来到doProcessConfigurationClass()
去递归处理所有配置类(这里的配置类也不只是指有@Configuration
的才算,任何直接或间接标注了@Component
的类都算)。以上这一小段关于Spring Boot自动配置的过程,详细可参考视频:https://www.bilibili.com/video/BV1NY411P7VX
至此Spring Boot启动的主要的四个步骤全部完成。
Spring的beanFactory中有一个map集合allBeanNamesByType
,键为Class
类型,存储依赖类型,值为String[]
,存储所有单例bean和非单例bean名称(id)。
如果是通过xml文件去配置一个bean的静态或者非静态工厂方法(factory-method),那这个原理不难想清,这里主要讨论的是当利用FactoryBean
接口时,Spring是怎么处理的。
首先对于beanDefinition,实现了FactoryBean
并注入容器了的类和其他注入到容器中的类一样,只会产生一个beanDefinition,并且这个beanDefinition的类型就是该类的类型,而非其要产生的对象(getObject()
)的类型。
那么照理来说Spring只能拿到FactoryBean
类型的bean,它是怎么拿到其产生的对象的呢?
经过上面的推到我们知道bean最终都是通过doGetBean()
去获取,这里在获取到bean之后,还是会调用getObjectForBeanInstance()
,如下图:
这个方法不展开讲了,大致就是判断此次是获取bean还是bean的factory(注意到这里传进去了name和beanName)。如果是获取factoryBean,那么直接返回就好了,因为最开始beanDefinitionMap里面存储的就是该工厂bean(factoryBean)。如果是想获得该工厂产生的对象,那么视情况调用工厂方法去创建对象,因为如果确定了该对象是单例模式,Spring会将其缓存在beanFactory
的factoryBeanObjectCache
中,这样创建过一次的对象直接在这里取就行了。
注意到工厂bean产生的对象并不存储在
singletonObjects
中。
@AutoConfiguration
可以延后注册我们知道配置类总是会先于自动配置类,这里来讲讲为什么是这样。具体原理有点绕,这里分几步讲解。
首先介绍一下怎么注册自动配置类。不同于普通的配置类加注解即可,自动配置类的注册会不一样。
在Spring Boot 2.7.0之前,还没有@AutoConfiguration
的时候,我们应该在项目路径创建文件夹META-INF/spring.factories
,然后在文件中如下配置:
这里忘了格式其实也不要紧,随便看一个有自动配置功能的jar,都会有这个文件,模仿格式即可。
在Spring Boot 2.7.0之后,有@AutoConfiguration
的时候,我们应该在项目路径创建文件夹META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,然后在文件中如下配置:
这里就只需要写配置类的全限定类名就行了。
然后记得给该类加上@Configuration
/@AutoConfiguration
就行了。
另外3.0之后只能使用第二种方式声明自动配置类了。
配置类、自动配置类并不是指的是有没有
@AutoConfiguration
(毕竟这个注解是Spring Boot 2.7.0之后才有的,但是自动配置类这个概念早就有了),而是从注册方式去区别的。自动配置类多了很多功能,比如安排顺序,使得我们使用
@ConditionalOn...
这类注解时更加方便,因为这类注解是当即判断,即 加载时立即判断是否符合条件,所以对加载顺序很敏感。比如beanA和beanB都在路径下,beanA只在beanB注入容器时才注入,但如果由于顺序安排不当导致beanA先注入,此时beanB还未注入,就会导致beanA注入失败。
依靠
@AutoConfigureOrder
,我们可以更改配置类的初始化顺序,注意不同于@Order
,只能修改AOP顺序和bean注入时的自动装配顺序(详情可参考博客:Bean加载顺序之错误使用姿势辟谣,这里不展开介绍了)。此外
@AutoConfigureBefore
等等也可以调整自动配置类的加载顺序,而这些注解统一都被@AutoConfiguration
一个注解搞定了(都成了它的一个属性),另外还值得注意的就是@AutoConfiguration
的proxyBeanMethods
默认为false
,以提高Spring Boot的启动速度。
上面我们注册了自动配置类,那么Spring Boot是怎么扫描的呢?
这点其实很多博客都提到了,就是我们Spring Boot的启动类上的注解@SpringBootApplication
,这个注解中包含@EnableAutoConfiguration
,而这个注解中又包含@Import(AutoConfigurationImportSelector.class)
。
其中这里就引入类AutoConfigurationImportSelector
,这是一个ImportSelector
,它会去扫描beanDefinition,所以直接看该类的selectImports()
就可以搞清是怎么回事了,这里不展开说了,最后是用ClassLoader
的getResources()
去扫描那两个文件(因为3.0之前,spring.factories
还是兼容的,可以一起使用)。
所以当递归解析Spring Boot项目的启动类时(上面提到的ConfigurationClassParser
),当解析到启动类的@Import
时,就会找到AutoConfigurationImportSelector
,从而引入我们注册的自动配置类。
由上面的分析我们知道了自动配置类是如何被扫描到的,但只依据上面说的,还无法保证自动配置类的扫描就一定在配置类之后,但现在基于上面的知识,这一点也能被推敲出来了。
上面提到的AutoConfigurationImportSelector
,不仅是一个ImportSelector
,更主要是它实现了DeferredImportSelector
接口,这个接口继承了ImportSelector
,而这就是核心点。
还记得上面提到是用ConfigurationClassParser
的parse()
去递归扫描beanDefinition吗,这个方法中在扫描的最后会调用前面扫描中获得的DeferredImportSelector
实现类,延后执行:
其实只看
DeferredImportSelector
的注释,上面也说清楚了,这个ImportSelector
会在扫描完所有的配置类之后执行,上面的源码只是其保证该行为的直接证明。
但不知道大家是否还会有一个疑问,就是假如我们在自己启动类的同一路径下,如果声明了一个配置类,然后又在spring.factories
中注册了该类,那么这个类的行为算配置类,还是自动配置类?
这个问题的答案其实是自动配置类。翻看ConfigurationClassParser
的parse()
的源码我们会发现它其实会先判断是否是自动配置类org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter#getAutoConfigurations
,如果是的话就不会现在加载。