如何控制bean的加载顺序?

news/2024/12/26 8:16:57/文章来源:https://www.cnblogs.com/seven97-top/p/18625259

写在前面

springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题。在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功能。

在一般业务场景,可能是不需要关心一个bean是如何被注册进spring容器的,只需要把需要注册进容器的bean声明为@Component即可,因为spring会自动扫描到这个Bean完成初始化并加载到spring上下文容器。

但是,如果加载Bean的过程中部分Bean和Bean之间存在依赖关系,也就是说Bean A的加载需要等待Bean B加载完成之后才能进行;或者你正在开发某个中间件需要完成自动装配时,你会声明自己的Configuration类,但是可能你面对的是好几个有互相依赖的Bean,如果不加以控制,这时候可能会报找不到依赖的错误。

而Spring框架在没有明确指定加载顺序的情况下是无法按照业务逻辑预期的顺序进行Bean加载,所以需要Spring框架提供能让开发人员显示地指定Bean加载顺序的能力。

几个误区

在正式说如何控制加载顺序之前,先说2个误区:

  • 在标注了@Configuration的类中,写在前面的@Bean一定会被先注册吗?

这个不存在的,spring在xml的时代,也不存在写在前面一定会被先加载的逻辑。因为xml不是渐进的加载,而是全部parse好,再进行依赖分析和注册。到了springboot中,只是省去了xml被parse成spring内部对象的这一过程,但是加载方式并没有大的改变。

  • 利用@Order这个标注就一定能进行加载顺序的控制吗?

严格的说,不是所有的Bean都可以通过@Order这个标注进行顺序的控制。因为把@Order这个标注加在普通的方法上或者类上是没有影响的,

@Order能控制哪些bean的加载顺序呢?官方解释:

{@code @Order} defines the sort order for an annotated component. Since Spring 4.0, annotation-based ordering is supported for many kinds of components in Spring, even for collection injection where the order values of the target components are taken into account (either from their target class or from their {@code @Bean} method). While such order values may influence priorities at injection points, please be aware that they do not influence singleton startup order which is an orthogonal concern determined by dependency relationships and {@code @DependsOn} declarations (influencing a runtime-determined dependency graph).

最开始@Order注解用于切面的优先级指定;在 4.0 之后对它的功能进行了增强,支持集合的注入时,指定集合中 bean 的顺序,并且特别指出了,它对于单实例的 bean 之间的顺序,没有任何影响。目前用的比较多的有以下3点:

  • 控制AOP的类的加载顺序,也就是被@Aspect标注的类
  • 控制ApplicationListener实现类的加载顺序
  • 控制CommandLineRunner实现类的加载顺序

使用详情请看后文

如何控制

@Conditional 条件注解家族

  • @ConditionalOnClass:当类路径下存在指定的类时,配置类才会生效。
@Configuration
// 当类路径下存在指定的类时,配置类才会生效。
@ConditionalOnClass(name = "com.example.SomeClass")
public class MyConfiguration {// ...
}
  • @ConditionalOnMissingClass:当类路径下不存在指定的类时,配置类才会生效。
  • @ConditionalOnBean:当容器中存在指定的Bean时,配置类才会生效。
  • @ConditionalOnMissingBean:当容器中不存在指定的Bean时,配置类才会生效。

@DependsOn

@DependsOn注解可以用来控制bean的创建顺序,该注解用于声明当前bean依赖于另外一个bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化。

@DependsOn的使用:

  • 直接或者间接标注在带有@Component注解的类上面;
  • 直接或者间接标注在带有@Bean注解的方法上面;
  • 使用@DependsOn注解到类层面仅仅在使用 component-scanning 方式时才有效,如果带有@DependsOn注解的类通过XML方式使用,该注解会被忽略,<bean depends-on="..."/>这种方式会生效。

示例:

@Configuration
public class BeanOrderConfiguration {@Bean@DependsOn("beanB")public BeanA beanA(){System.out.println("bean A init");return new BeanA();}@Beanpublic 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();}@Beanpublic 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

参数注入

@Bean标注的方法上,如果传入了参数,springboot会自动会为这个参数在spring上下文里寻找这个类型的引用。并先初始化这个类的实例。

利用此特性,我们也可以控制bean的加载顺序。

示例:

@Bean
public BeanA beanA(BeanB demoB){System.out.println("bean A init");return new BeanA();
}@Bean
public BeanB beanB(){System.out.println("bean B init");return new BeanB();
}

以上结果,beanB先于beanA被初始化加载。

需要注意的是,springboot会按类型去寻找。如果这个类型有多个实例被注册到spring上下文,那就需要加上@Qualifier("Bean的名称")来指定

利用bean的生命周期中的扩展点

在spring体系中,从容器到Bean实例化&初始化都是有生命周期的,并且提供了很多的扩展点,允许在这些步骤时进行逻辑的扩展。

这些可扩展点的加载顺序由spring自己控制,大多数是无法进行干预的。可以利用这一点,扩展spring的扩展点。在相应的扩展点加入自己的业务初始化代码。从来达到顺序的控制。

具体关于spring容器中大部分的可扩展点的分析,之前已经写了一篇文章详细介绍了:Spring&SpringBoot中所有的扩展点

实现Ordered/PriorityOrdered接口/注解

在Spring中提供了如下的方法来进行Bean加载顺序的控制:

  • 实现Ordered/PriorityOrdered接口,重写order方法
  • 使用@Order/@Priority注解,@Order注解可以用于方法级别,而@Priority注解则不行;

针对自定义的Bean而言,上述的方式都可以实现Bean加载顺序的控制。无论是实现接口的方式还是使用注解的方式,值设置的越小则优先级越高,而通过实现PriorityOrdered接口或者使用@Priority注解的Bean时其加载优先级会高于实现Ordered接口或者使用@Order注解的Bean。

需要注意的是,使用上述方式只会改变实现同一接口Bean加载到集合(比如List、Set等)中的顺序(或者说优先级),但是这种方式并不会影响到Spring应用上下文启动时不同Bean的初始化顺序(startup order)。

  • 错误案例:以下案例代码是无法指定配置顺序的
@Component
@Order(1)
public class BeanA {// BeanA的定义
}@Component
@Order(2)
public class BeanB {// BeanB的定义
}
  • 正确使用案例:

首先定义两个 Bean 实现同一个接口,并添加上@Order注解。

public interface IBean {
}@Order(2)
@Component
public class AnoBean1 implements IBean {private String name = "ano order bean 1";public AnoBean1() {System.out.println(name);}
}@Order(1)
@Component
public class AnoBean2 implements IBean {private String name = "ano order bean 2";public AnoBean2() {System.out.println(name);}
}

然后在一个测试 bean 中,注入IBean的列表,我们需要测试这个列表中的 Bean 的顺序是否和定义的@Order规则一致

@Component
public class AnoTestBean {public AnoTestBean(List<IBean> anoBeanList) {for (IBean bean : anoBeanList) {System.out.println("in ano testBean: " + bean.getClass().getName());}}
}

@AutoConfigureOrder

这个注解用来指定配置文件的加载顺序。但是在实际测试中发现,以下这样使用是不生效的:

@Configuration
@AutoConfigureOrder(2)
public class BeanOrderConfiguration1 {@Beanpublic BeanA beanA(){System.out.println("bean A init");return new BeanA();}
}@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {@Beanpublic BeanB beanB(){System.out.println("bean B init");return new BeanB();}
}

无论你2个数字填多少,都不会改变其加载顺序结果。那这个@AutoConfigureOrder到底是如何使用的呢?

@AutoConfigureOrder适用于外部依赖的包中 AutoConfig 的顺序,而不能用来指定本包内的顺序。能被你工程内部scan到的包,都是内部的Configuration,而spring引入外部的Configuration,都是通过spring特有的spi文件:spring.factories

换句话说,@AutoConfigureOrder能改变spring.factories中的@Configuration的顺序。

具体使用方式:

@Configuration
@AutoConfigureOrder(10)
public class BeanOrderConfiguration1 {@Beanpublic BeanA beanA(){System.out.println("bean A init");return new BeanA();}
}@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {@Beanpublic BeanB beanB(){System.out.println("bean B init");return new BeanB();}
}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.demo.BeanOrderConfiguration1,\com.example.demo.BeanOrderConfiguration2

总结

其实在工作中,我相信很多人碰到过复杂的依赖关系的bean加载,把这种不确定性交给spring去做,还不如我们自己去控制,这样在阅读代码的时候 ,也能轻易看出bean之间的依赖先后顺序。

面试题专栏

Java面试题专栏已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/859144.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

未发现数据源名称并且未指定默认驱动程序

如果遇到类似“未发现数据源名称并且未指定默认驱动程序”的错误提示,通常是因为系统找不到指定的ODBC驱动程序。此时建议检查以下几个方面:确认驱动程序已正确安装:通过控制面板中的“管理工具” -> “ODBC数据源”查看是否列出了所需的MySQL ODBC驱动程序。 验证驱动程…

虚拟主机上本地连接数据库非常缓慢,应该如何排查和优化?

当在虚拟主机环境中遇到本地连接数据库变得非常缓慢的情况时,这不仅影响用户体验,还可能导致业务流程中断。因此,迅速有效地排查并解决这个问题至关重要。下面我们将详细介绍如何一步步排查导致数据库连接变慢的原因,并提供一些实用的优化建议。 首先,需要明确的是,“本地…

远程SSH无法登录,重置密码无效,该如何处理?

当遇到远程SSH无法登录且重置密码无效的情况时,这往往意味着存在较为复杂的安全或配置问题。这类问题不仅会影响日常运维工作,还可能暗示着潜在的安全风险。因此,必须谨慎对待并尽快找出根本原因加以修复。接下来,我们将详细介绍如何诊断和解决此类问题的方法。 首先要认识…

网站二级页面无法完整显示怎么办?

确认程序内部伪静态及插件设置是否正确。 检查服务器日志,查看是否有相关错误信息。 尝试清除浏览器缓存或使用不同浏览器访问,排除客户端问题。非常感谢您长期对我们服务的支持!扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、…

客户反馈无法访问网站怎么办?

让客户尝试更换不同浏览器访问,排除浏览器兼容性问题。 建议客户重启本地路由器或重新拨号后再次访问。 提供客户的IP地址,我们将在服务器端进行核查,确认是否存在拦截情况。扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML…

FTP账号无法连接,提示连接失败,如何解决?

建议您提供FTP设置截图和连接报错截图,以便我们进一步核实。同时,确保FTP服务器地址、用户名和密码正确无误。如果问题仍然存在,建议您联系技术支持获取更多帮助。扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、J…

我的世界服务器基于ubuntu的mcmanager

111.231 上海 111.230 广州3.ubuntu1 192.168.1.104 4.fnos 192.168.1.103 5.pve 192.168.1.111 6.windows 192.168.1.109 设置登录面板 ff8d862c 设置 广州 登录账号 root 设置oxterm ssh密钥登陆 密钥在edge file 利用宝塔面板下载pure ftpd 准备利用ftp…

读数据保护:工作负载的可恢复性17传统备份方案

传统备份方案1. 物联网 1.1. 边缘设备(edge device)通常是指远程站点里的服务器,这几年也用来指代智能手机与平板计算机 1.2. 物联网是边缘运算(edge computing,也叫边缘计算)的一个子集1.2.1. 所连接的设备在本组织的数据环境里处于相当边…

淘一个电池电量代码

`<div style="margin-top: 20px;">当前电量:<el-input-numberv-model="elecVal":min="0":max="100":step="5":precision="2"controls-position="right"style="width: 120px;">&…

【unity】学习制作类银河恶魔城游戏-3-

解决黏墙 当人物贴在墙上且不松开方向键时,人物会黏住一动不动,无法从空中落下创建一个2d材料,命名为光滑的材料应用给player设置摩檫力为0冲刺功能(计时器和增量时间)Update函数每帧执行一次,经历一帧所耗费的时间就是增量时间deltatime 通过计算每一帧之间的时间增量,…

java大作业7-8+期末总结

一、前言 第七次大作业: 还是家居电路模拟程序,在上一次的基础上迭代了互斥开关,互斥开关有三个引脚,且每个引脚为了不被短路,存在阻值,还有一个就是迭代了窗帘,根据室内的灯光来进行调节的,所以这次还需要多一个计算总光照的过程。总体上还是跟以前一样把总电路看成串…

Typora

测试Typora上传