品牌型号:联想ThinkPad X1
系统:Windows10家庭版
软件版本:Spring 5.3.7
我们知道Spring Bean的三种配置方式,但这只解决了怎么声明Bean,我们并不知道Bean到底是怎么进容器的。在开发时项目里Bean一多,牵扯到几十个@Configuration类互相依赖的时候,经常会碰到一个诡异问题,明明两个Bean都配置好了,却因为注入顺序不对,报出循环依赖或者NoSuchBeanDefinitionException。那么Spring Boot启动时到底是按什么顺序把Bean一个个塞进容器的呢?下面就给大家介绍一下Spring Bean是怎么注入容器的,Spring Boot Bean注入顺序的相关内容。
一、Spring Bean是怎么注入容器的
很多刚接触Spring的小伙伴想知道Bean是怎么注入容器的,我们可以拆成两步,先发现要注册哪些类,然后把这些类实例化放到容器里。不管是XML里的bean标签,还是用注解@Component扫描到的类,或者是我们通过@Configuration配置类中的@Bean方法,Spring都会把它们解析成一个个BeanDefinition对象。

这一步仅仅是将信息注册至BeanDefinitionRegistry,此时还没有真正创建对象。真正创建对象的过程发生在AbstractApplicationContext的refresh方法里,调用finishBeanFactoryInitialization方法后,往下走就是调用getBean方法触发实例化。

默认情况下如果是单例Bean,会在容器启动阶段就提前实例化完的,会先反射调用构造方法创建对象,然后处理依赖注入,像我们用@Autowired标注的字段或者构造参数就会在这一步被填充,接着执行Aware接口的回调,走BeanPostProcessor的前置处理,然后调用@PostConstruct或者InitializingBean的afterPropertiesSet方法,最后是BeanPostProcessor的后置处理,这时候这个Bean才算真正可以使用,能被拿去注入到别的Bean里。

如果Bean之间有依赖关系,比如A依赖B,Spring会先去实例化B,B造好了再回头继续造A,如果A和B相互依赖,单例场景下Spring靠三级缓存能解决字段注入的循环依赖,但是构造器注入的循环依赖它是解决不了的,启动时直接报错。
二、Spring Boot Bean注入顺序
不少开发者会误以为Spring Boot中Bean是按代码书写的先后顺序来注入的,这其实是个误解,Spring Boot主要看依赖关系,谁被依赖,谁就先实例化。例如A依赖了B,不管A和B在配置类里谁先写谁后写,Spring都会先去把B造出来,再回过头来造A,一旦碰到某个Bean依赖了还没创建的Bean,就会递归先去创建那个依赖。

但是如果几个Bean之间没有依赖关系,就按照配置类扫描到的顺序,我们如果想手动干预顺序,可以使用@DependsOn和0rder。
@DependsOn是显式声明“我依赖某个Bean,你必须先把它造出来”,哪怕代码里看不出直接的字段依赖关系,也能强制指定先后顺序,常用在一些没有直接引用但确实要求初始化先后的场景,比如某个Bean的静态方法要用到另一个Bean提前注册的东西。

@0rder就不一样了,如果这会同类型多个Bean存在,我们需要按顺序将这些Bean注入到一个List时,可以用@0rder来决定排序,并不直接控制Bean本身的创建时机。

在Spring Boot中有个容易被很多开发者忽略的点,自动配置类之间也是有顺序的,靠@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigure0rder这几个注解控制,像数据源要先于JPA配置初始化,就是靠这套机制保证的,我们平时业务开发接触不多。
以上就是Spring Bean是怎么注入容器的,Spring Boot Bean注入顺序的全部内容了。Bean注入容器前,会先去登记BeanDefinition然后按依赖关系逐个实例化,关于Bean注入的顺序,是依赖关系在背后起作用,而@DependsOn和@0rder只是补丁,而且解决的还是两类不同的问题。我们后面如果遇到启动顺序诡异、循环依赖报错这种情况,别急着调整代码顺序,看看是不是依赖关系本身没理清楚。