上一篇博文介绍了 @Order
注解的常见错误理解,它并不能指定bean的加载顺序,那么问题来了,如果我需要指定bean的加载顺序,那应该怎么办呢?
本文将介绍几种可行的方式来控制bean之间的加载顺序
我们的测试项目和上一篇博文公用一个项目环境,当然也可以建一个全新的测试项目,对应的配置如下:(文末有源码地址)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7</version> <relativePath/> <!-- lookup parent from update --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
这种可以说是最简单也是最常见的使用姿势,但是在使用时,需要注意循环依赖等问题
我们知道bean的注入方式之中,有一个就是通过构造方法来注入,借助这种方式,我们可以解决有优先级要求的bean之间的初始化顺序
比如我们创建两个Bean,要求CDemo2在CDemo1之前被初始化,那么我们的可用方式
@Component public class CDemo1 { private String name = "cdemo 1"; public CDemo1(CDemo2 cDemo2) { System.out.println(name); } } @Component public class CDemo2 { private String name = "cdemo 1"; public CDemo2() { System.out.println(name); } }
实测输出结果如下,和我们预期一致
虽然这种方式比较直观简单,但是有几个限制
另外一个需要注意的点是,在构造方法中,不应有复杂耗时的逻辑,会拖慢应用的启动时间
这是一个专用于解决bean的依赖问题,当一个bean需要在另一个bean初始化之后再初始化时,可以使用这个注解
使用方式也比较简单了,下面是一个简单的实例case
@DependsOn("rightDemo2") @Component public class RightDemo1 { private String name = "right demo 1"; public RightDemo1() { System.out.println(name); } } @Component public class RightDemo2 { private String name = "right demo 2"; public RightDemo2() { System.out.println(name); } }
上面的注解放在 RightDemo1
上,表示 RightDemo1
的初始化依赖于 rightDemo2
这个bean
在使用这个注解的时候,有一点需要特别注意,它能控制bean的实例化顺序,但是bean的初始化操作(如构造bean实例之后,调用 @PostConstruct
注解的初始化方法)顺序则不能保证,比如我们下面的一个实例,可以说明这个问题
@DependsOn("rightDemo2") @Component public class RightDemo1 { private String name = "right demo 1"; @Autowired private RightDemo2 rightDemo2; public RightDemo1() { System.out.println(name); } @PostConstruct public void init() { System.out.println(name + " _init"); } } @Component public class RightDemo2 { private String name = "right demo 2"; @Autowired private RightDemo1 rightDemo1; public RightDemo2() { System.out.println(name); } @PostConstruct public void init() { System.out.println(name + " _init"); } }
注意上面的代码,虽然说有循环依赖,但是通过 @Autowired
注解方式注入的,所以不会导致应用启动失败,我们先看一下输出结果
有意思的地方来了,我们通过 @DependsOn
注解来确保在创建 RightDemo1
之前,先得创建 RightDemo2
;
所以从构造方法的输出可以知道,先实例RightDemo2, 然后实例RightDemo1;
然后从初始化方法的输出可以知道,在上面这个场景中,虽然RightDemo2这个bean创建了,但是它的初始化代码在后面执行
题外话:
有兴趣的同学可以试一下把上面测试代码中的 @Autowired
的依赖注入删除,即两个bean没有相互注入依赖,再执行时,会发现输出顺序又不一样
最后再介绍一种非典型的使用方式,如非必要,请不要用这种方式来控制bean的加载顺序
先创建两个测试bean
@Component public class HDemo1 { private String name = "h demo 1"; public HDemo1() { System.out.println(name); } } @Component public class HDemo2 { private String name = "h demo 2"; public HDemo2() { System.out.println(name); } }
我们希望HDemo2在HDemo1之前被加载,借助BeanPostProcessor,我们可以按照下面的方式来实现
@Component public class DemoBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { throw new IllegalArgumentException( "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } @Override @Nullable public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { // 在bean实例化之前做某些操作 if ("HDemo1".equals(beanName)) { HDemo2 demo2 = beanFactory.getBean(HDemo2.class); } return null; } }
请将目标集中在 postProcessBeforeInstantiation
,这个方法在某个bean的实例化之前,会被调用,这就给了我们控制bean加载顺序的机会
看到这种骚操作,是不是有点蠢蠢欲动,比如我有个bean,希望在应用启动之后,其他的bean实例化之前就被加载,用这种方式是不是也可以实现呢?
下面是一个简单的实例demo,重写 DemoBeanPostProcessor
的 postProcessAfterInstantiation
方法,在application创建之后,就加载我们的FDemo这个bean
@Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { if ("application".equals(beanName)) { beanFactory.getBean(FDemo.class); } return true; } @DependsOn("HDemo") @Component public class FDemo { private String name = "F demo"; public FDemo() { System.out.println(name); } } @Component public class HDemo { private String name = "H demo"; public HDemo() { System.out.println(name); } }
从下图输出可以看出, HDemo
, FDemo
的实例化顺序放在了最前面了
在小结之前,先指出一下,一个完整的bean创建,在本文中区分了两块顺序
@PostConstruct
本文主要介绍了三种方式来控制bean的加载顺序,分别是
@DependsOn
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
打赏 如果觉得我的文章对您有帮助,请随意打赏。