前言
作为一名面向Spring开发的开发人员,在日常的工作、学习或者面试中或多或少都会遇到这样一个问题:如何控制Bean的加载顺序?
在开始回答这个问题之前我们需要先解答另一个问题:为什么需要进行Bean的加载顺序控制?
在Spring
框架的开发中,常常会面临以下情境:某个Bean
依赖于另一个Bean
,也就是说,要创建Bean B,必须首先创建Bean A。
正常情况下,Spring 容器加载 Bean 的顺序是不确定的,那么我们如果需要按顺序加载 Bean 时应如何操作?本文将详细讲述我们如何才能控制 Bean 的加载顺序。
两个误区
-
在被标注了
@Configuration
的类中,写在前面的@Bean
方法并不一定会先被注册。
这个观点在Spring的早期版本中也不成立,因为在XML配置时,Spring
并不是逐行逐个注册Bean的,而是先解析整个XML文件,然后进行依赖分析和注册。尽管在Spring Boot中省略了XML配置,直接将配置类解析成Spring内部对象,但加载方式并没有发生根本性改变。 -
使用
@Order
注解并不能控制所有Bean
的加载顺序。
实际上,将@Order
注解应用于普通方法或类上是无效的。@Order
注解能够影响加载顺序的Bean
有一定的限制。
最开始@Order
注解用于切面的优先级指定;在 4.0 之后对它的功能进行了增强,支持集合的注入时,指定集合中bean
的顺序,并且特别指出了,它对于但实例的bean
之间的顺序,没有任何影响。
1、使用@Order注解
@Order
注解是Spring
框架中的一个注解,位于spring-core
包下。它的作用是 定义Spring IOC
容器中Bean的执行顺序的优先级,间接性地决定它们在容器中被加载的先后顺序。
@Order
注解可以用于类、方法和字段上。
当用于类上时,它指定了该类在容器中的加载顺序。当用于方法或字段上时,它定义了与其他Bean对象之间的执行顺序关系。
具体的优先级数值越小
,优先级越高
。
1.1 使用示例
我们准备两个类,Demo1和Demo2,分别在类上添加@Order注解:
@Component@Order(1)
public class Demo1 {
@Bean
public UserService serviceA(){
System.out.println("serviceA 执行");
return new UserService();
}}
两个类各自创建一个UserService的bean
@Component
@Order(2)
public class Demo2 {
@Bean
public UserService serviceB(){
System.out.println("serviceB 执行");
return new UserService();
}
}
运行下面的代码,可以看到,order数字更小的demo1这个类的bean比demo2的bean要先创建出来。
1.2 @order失效问题解决办法
1、在@Configuration
里面通过@Bean是方式创建bean,在上面加@Order
控制顺序是没有效果的。
2、控制顺序方式:
-
实现
Ordered
接口 -
实现
PriorityOrdered
接口 -
在类上面加
@Order
-
在类上面加
@Priority
-
可以通过
RegistrationBean
方式创建bean,用setOrder添加顺序 -
filter
可以通过FilterRegistrationBean
创建filter的bean,指定顺序
解决方式1
将需要控制bean
的创建顺序的两个类放到不同的包路径下。
再次运行测试代码,当demo2的order数字更小的情况下,就能看到预期的demo2创建的bean优先输出的效果了。
解决方式2
重新修改类的命名,比如将demo2的类修改为ADemo,让ADemo这个类在同一个包路径下置于Demo2之前。
这样修改后,再次运行测试代码观察效果,此时ADemo就优先Demo1了。
1.3实现Ordered接口
如果您想使用Spring
提供的Ordered
接口来定义类的执行顺序,只需自定义的类实现该接口,并重写getOrder方法。在getOrder方法中,返回的数值越小,优先级就越高。以下是一个示例,展示了如何自定义两个由Spring
管理的类,并实现了Ordered
接口:
@Componentpublic
class Listener1 implements Ordered {
@Bean
public UserService userService1(){
System.out.println("userService1 bean");
return new UserService();
}
@Override
public int getOrder() {
return 100;
}
}
@Componentpublic
class Listener2 implements Ordered {
@Bean
public UserService userService2(){
System.out.println("userService2 bean");
return new UserService();
}
@Override
public int getOrder() {
return 10;
}
}
2 使用@DependsOn注解
@DependsOn
注解可以用来控制bean
的创建顺序,该注解用于声明当前bean
依赖于另外一个bean
。所依赖的bean
会被容器确保在当前bean
实例化之前被实例化。
示例:
@Configurationpublic
class BeanOrderConfiguration {
@Bean
@DependsOn("beanB")
public BeanA beanA() {
System.out.println("bean A init");
return new BeanA();
}
@Bean
public BeanB beanB() {
System.out.println("bean B init");
return new BeanB();
}
@Bean
@DependsOn({"beanD", "beanE"})
public BeanC beanC() {
System.out.println("bean C init");
return new BeanC();
}
@Bean
@DependsOn("beanE")
public BeanD beanD() {
System.out.println("bean D init");
return new BeanD();
}
@Bean
public BeanE beanE() {
System.out.println("bean E init");
return new BeanE();
}
}
以上代码bean
的加载顺序为:
bean B init
bean A init
bean E init
bean D init
bean C init
使用@DependsOn
注解有以下几种情况:
直接或者间接标注在带有@Component
注解的类上。
直接或者间接标注在带有@Bean
注解的方法上。
需要注意的是,@DependsOn
注解在类级别上仅在使用组件扫描(component-scanning)方式时有效。如果带有@DependsOn
注解的类是通过XML方式进行配置,那么该注解会被忽略。在这种情况下,可以通过XML配置中的来实现依赖控制。
3 参数注入
在使用@Bean
标注的方法时,如果您传入了参数,Spring Boot
会自动在Spring
上下文中查找具有相同类型的引用,并首先初始化该类的实例。这一特性也可以用来控制Bean
的加载顺序。
示例:
@Beanpublic
BeanA beanA(BeanB demoB) {
System.out.println("bean A init");
return new BeanA();
}
@Beanpublic
BeanB beanB() {
System.out.println("bean B init");
return new BeanB();
}
以上结果,beanB先于beanA被初始化加载。
需要注意的是,Springboot
会按类型去寻找。如果这个类型有多个实例被注册到Spring
上下文,那你就需要加上@Qualifier("Bean的名称")
来指定。