【微服务】spring 控制bean加载顺序使用详解

目录

一、前言

二、使用@order注解控制顺序

2.1 @order 注解使用示例

2.2 order注解顺序失效问题

2.2.1 @order失效问题解决办法

2.3 实现Ordered接口

三、使用@dependon注解控制顺序

四、AutoConfiguration注解控制bean加载顺序

4.1 @AutoConfigureBefore 操作演示

4.2 @AutoConfigureOrder 操作演示

4.3 源码解读与分析

五、自定义ApplicationContextInitializer

5.1 ApplicationContextInitializer介绍

5.2 ApplicationContextInitializer使用

5.3 ApplicationContextInitializer控制加载顺序

六、使用场景

6.1 解决bean的依赖关系

6.2 设置某些配置类的优先级最高

6.3 依赖传递

七、写在文末


一、前言

在使用spring框架开发过程中,可能会遇到下面的情况:

  • 某个bean被另一个bean依赖,也就是bean-b的创建必须依赖bean-a;
  • 某个bean被很多其他bean依赖,比如bean-a初始化完成后,其他bean需要依靠bean-a初始化自己的业务;
  • ...

类似这样的场景还有很多,总结来说,这就涉及到bean的加载顺序问题,如何解决呢?下面列举出几种常用的解决方案。

二、使用@order注解控制顺序

@order注解是spring-core包下的一个注解,@Order的作用是定义Spring IOC容器中Bean的执行顺序的优先级(这里的顺序也可以理解为存放到容器中的先后顺序)。开发过程当中有时候经常会出现配置依赖关系,例如注入A对象使用了@ConditionalOnBean(B.class),意思是要求容器当中必须存在B.class的实例的时候,才会进行注入A。这时候我们就必须保证B对象在注入A对象前进行注入。

2.1 @order 注解使用示例

有如下两个类,Demo1和Demo2,分别在类上添加@Order注解

@Component
@Order(1)
public class Demo1 {@Beanpublic UserService serviceA(){System.out.println("serviceA 执行");return new UserService();}
}

两个类各自创建一个UserService的bean

@Component
@Order(2)
public class Demo2 {@Beanpublic UserService serviceB(){System.out.println("serviceB 执行");return new UserService();}
}

运行下面的代码,观察测试效果

public class OrderTest {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanScanConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();for(String beanName : beanDefinitionNames){System.out.println(beanName);}}
}

可以看到,order数字更小的demo1这个类的bean比demo2的bean要先创建出来;

2.2 order注解顺序失效问题

上面通过在类上添加@order,可以控制类创建的bean的顺序,事实真的如此吗?如果此时,我们将demo1的order调大,demo2的order数字调小,你将看到的效果仍然是serviceA先输出,这就是order注解顺序失效问题。关于这个问题,spring官方文档也有说明,翻译出来意思如下:

您可以在目标类级别和@Bean方法上声明@Order注释,可能针对的是单个bean定义(如果多个定义使用同一个bean类)。@Order值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注。

2.2.1 @order失效问题解决办法

解决方式1

将需要控制bean的创建顺序的两个类放到不同的包路径下

再次运行测试代码,当demo2的order数字更小的情况下,就能看到预期的demo2创建的bean优先输出的效果了

解决方式2

重新修改类的命名,比如将demo2的类修改为ADemo,让ADemo这个类在同一个包路径下置于Demo2之前

这样修改后,再次运行测试代码观察效果,此时ADemo就优先Demo1了

2.3 实现Ordered接口

使用spring提供的ordered接口,只需要自定义的类实现这个接口,并重写里面的getOrder方法,在getOrder方法的返回值中,数值越小,优先级越高,如下自定义两个被sppring管理的类,实现Ordered接口

@Component
public class Listener1 implements Ordered {@Beanpublic UserService userService1(){System.out.println("userService1 bean");return new UserService();}@Overridepublic int getOrder() {return 100;}
}
@Component
public class Listener2 implements Ordered {@Beanpublic UserService userService2(){System.out.println("userService2 bean");return new UserService();}@Overridepublic int getOrder() {return 10;}
}

与ordered接口类似的还有PriorityOrdered,用法大同小异,有兴趣的同学可以进一步挖掘。

三、使用@dependon注解控制顺序

当某个bean-a的创建或加载需要明确依赖另一个bean-b的时候可以考虑使用dependon注解,换言之,bean-b要优先于bean-a创建;

如下有两个类,OrderDemo1和OrderDemo2

@Component
@DependsOn("orderDemo2")
public class OrderDemo1 {public OrderDemo1(){System.out.println("OrderDemo1 ...");}}

其中OrderDemo1的创建依赖OrderDemo2

@Component
public class OrderDemo2 {public OrderDemo2(){System.out.println("OrderDemo2 ...");}}

运行程序,可以看到OrderDemo2优先于OrderDemo1创建

由于这种方法是通过bean的名字(字符串)来控制顺序的,如果改了bean的类名,很可能就会忘记来改所有用到它的注解,那就问题大了,所以这种方式一般不推荐使用,但是也不失为控制bean创建顺序的方式。

四、AutoConfiguration注解控制bean加载顺序

Spring Boot会根据当前容器内的情况来动态的判断自动配置类的配置顺序,它给我们提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder 三大注解:

@AutoConfigureBefore

用在自动配置类上面,表示该自动配置类需要在另外指定的自动配置类配置完之后配置加载

@AutoConfigureAfter

用在自动配置类上面,表示该自动配置类需要在另外指定的自动配置类配置完之前配置加载

@AutoConfigureOrder

确定配置加载的优先级顺序,表示绝对顺序(数字越小,优先顺序越高)

4.1 @AutoConfigureBefore 操作演示

定义两个配置类DbConfig1与DbConfig2,代码如下

@Configuration
public class DbConfig1 {public DbConfig1(){System.out.println("DbConfig1 构建方法...");}}

其中,DbConfig2类上使用了注解AutoConfigureBefore,期望DbConfig2比DbConfig1先加载

@Configuration
@AutoConfigureBefore(DbConfig1.class)
public class DbConfig2 {public DbConfig2(){System.out.println("DbConfig2 构建方法...");}}

到这里还没有完事,还需在resources目录下,将DbConfig1与DbConfig2配置到spring.factories文件中进行自动装配

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.congge.config.DbConfig2,\
com.congge.config.DbConfig1

运行上面的代码,观察控制台输出,可以看到DbConfig2比DbConfig1先加载

我们还可以将注解换成AutoConfigureAfter,看看是什么效果

@Configuration
@AutoConfigureAfter(DbConfig1.class)
public class DbConfig2 {public DbConfig2(){System.out.println("DbConfig2 构建方法...");}}

再次运行代码,可以看到这次就是DbConfig1先加载

4.2 @AutoConfigureOrder 操作演示

也可以使用AutoConfigureOrder 注解来控制不同配置类的加载顺序,数字越小,优先顺序越高,添加两个配置类,分别使用该注解进行标注

@Configuration
@AutoConfigureOrder(2)
public class OrderConfig1 {public OrderConfig1(){System.out.println("OrderConfig1 加载...");}
}

OrderConfig2中的数值更大,理论上OrderConfig1优先加载

@Configuration
@AutoConfigureOrder(7)
public class OrderConfig2 {public OrderConfig2(){System.out.println("OrderConfig2 加载...");}
}

同样,使用AutoConfigureOrder控制配置类的加载顺序也需要将其配置到spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.congge.config.DbConfig2,\
com.congge.config.DbConfig1,\
com.congge.config.OrderConfig1,\
com.congge.config.OrderConfig2

运行上面的代码,看到如下效果,说明OrderConfig1优于OrderConfig2加载

4.3 源码解读与分析

最关键的代码在AutoConfigurationImportSelector这个类中,spring在bean装载过程中,将自动配置类从spring.factories加载出来之后会根据条件排序,在selectImports()方法中最后一行代码进行排序,如下

跟进sortAutoConfigurations这个方法,最终的排序逻辑来到下面这个getInPriorityOrder方法中;

在这个方法中,关于排序是分成3种方式完成的

  • 先按字母排序;
  • 再按照@AutoConfigureOrder进行排序;
  • 最后按照 @AutoConfigureBefore和@AutoConfigureAfter排序;

而从配置的顺序不难发现,最终决定权还是在@AutoConfigureAfter、@AutoConfigureBefore这两个注解。

五、自定义ApplicationContextInitializer

对spring的bean的生命周期和加载过程熟悉的同学,想必了解到,容器启动过程中,通过解析xml文件中的配置生成bean或者通过扫描路径并解析得到bean,这些bean最终会统一保存在一个容器中被spring管理。既然如此,开发人员就可以人工的干预这个bean的解析过程,通过spring提供的相关扩展点,改变bean在容器中的位置就可以达到控制bean的顺序了,下面看具体的操作流程。

5.1 ApplicationContextInitializer介绍

先看spring官网的介绍:

翻译过来大概的意思如下

  • 用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。(剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法);
  • 通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等;
  • 可排序的(实现Ordered接口,或者添加@Order注解);

5.2 ApplicationContextInitializer使用

新建一个类 MyAppInitializer并实现 ApplicationContextInitializer 接口

public class MyAppInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("-----MyAppInitializer initialize-----");}
}

然后在启动类中添加进去

@SpringBootApplication
public class BootApp {public static void main(String[] args) {SpringApplication application = new SpringApplication(BootApp.class);application.addInitializers(new MyAppInitializer());application.run(args);}
}

运行上面的main程序,在启动时看到控制台输出下面的信息

从上面的介绍了解到,实现ApplicationContextInitializer该接口,将会在容器刷新之前进行加载,对应到spring源码中,即refreshContext(context)方法,了解spring的bean的加载流程的同学应该知道,refreshContext里面就是调用AbstractApplicationContext的refresh的方法,其主要功能就是注册spring容器里面的bean,以及对bean的处理还有广播等功能,简而言之,就是在这个方法中,完成系统中bean的创建和存储的一系列过程。

基于此,按照上面的效果来看,实现ApplicationContextInitializer该接口之后,其类的加载时机要更加靠前,于是就可以借助这个特性,在bean还没有真正创建出来之前,人为的干预bean的创建顺序,将指定的类注册到spring上下文中。

5.3 ApplicationContextInitializer控制加载顺序

自定义一个类实现ApplicationContextInitializer接口

public class MyAppInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("-----MyAppInitializer initialize-----");applicationContext.addBeanFactoryPostProcessor(new MyBeanFactoryPostProcessor());}
}

自定义一个类实现接口BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry方法,手动注册需要控制加载到容器中顺序的类,注意,按照上面的流程操作完成之后,对于你要控制的配置类,就不要使用相关的注解标注了,比如下面要控制的是OrderService这个类的顺序在最前面。

public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(OrderService.class);beanDefinitionRegistry.registerBeanDefinition("orderService",beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}

将MyAppInitializer配置到spring.factories文件中

org.springframework.context.ApplicationContextInitializer=\
com.congge.config.MyAppInitializer

运行main程序,观察控制台输出效果如下,说明OrderService不仅被容器管理,而且顺序在最前面,这就达到了控制bean的顺序的目的。

六、使用场景

到这里,再来看文章开头提出的问题,为什么需要控制spring中bean的加载顺序呢?关于这个问题,这里提出下面的几种常用的场景。

6.1 解决bean的依赖关系

比如在初始化时,配置类B需要用到配置类A中的某些属性,配置类C需要用到B中的某些属性,在这种级联依赖的情形下,为了避免启动过程中依赖的问题导致启动失败,就可以通过控制配置bean的顺序来解决。

6.2 设置某些配置类的优先级最高

比如说,我们有一个商品管理的配置类,需要在项目启动的时候,完成从数据库的数据写入到redis缓存中,并且定时刷新商品数据,并且该类还提供了一个对外访问的static方法,这种场景下,由于对外提供了static方法,其他类可以直接调用它的方法,如果不是最先加载的话,当请求获取商品数据时,商品还没有加载完成,那就就会出现问题。因此就需要保证这个配置bean最先被加载。

6.3 依赖传递

当程序中需要对接口的参数进行不同梯度的校验和拦截时,一个常见的做法就是利用AOP,减少对主业务流程的干扰,在这种情况下,如果在A类的AOP逻辑处理完成之后继续传递到下一级B的AOP中进行处理,这就需要控制不同的AOP类的执行顺序,这时就需要控制不同AOP的执行顺序。

七、写在文末

在某些特殊的业务场景下,合理控制bean的加载顺序可以帮助我们解决很多复杂的业务需求,同时也可以作为spring提供的一种功能扩展点进行使用,在spring体系中具有重要的作用,本篇到此结束,感谢观看。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

安防视频平台EasyCVR视频调阅全屏播放显示异常是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

AIRIOT亮相IOTE2023深圳物联网展,产品创新力再获“IOTE金奖”

9月20-22日,IOTE 2023第二十届深圳国际物联网展在深圳国际会展中心(宝安)圆满落幕。作为物联网领域年度最重要的行业盛会之一,本届展会以“IoT构建数字经济底座”为主题,汇聚全球来自工业、物流、基建、智慧城市、智慧…

freemarker自定义模板

模板编程器指南 <dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.31</version> </dependency>freemarker官网参考&#xff1a; https://freemarker.apache.org/docs/pgui_qu…

浏览器原生JavaScript离线文字转语音TTS播放,支持Windows自带TTS语音和移动端(安卓、IOS)

前言 JS已经可以实现语音合成(文字转语音)和语音识别(语音转文字),各个浏览器支持列表如下所示: 语音识别支持列表: 因此,浏览器上面使用语音合成非常简单。 页面效果示例: 实现功能 1、支持速度,音调设置 2、支持下拉选择语音模板 3、文字转语音 代码实现 …

云服务器 CentOS7 操作系统上安装Jpress (Tomcat 部署项目)

1、xShell 和 xftp 下载安装&#xff08;略&#xff09; https://www.xshell.com/zh/free-for-home-school/2、xftp 连接云服务器 xftp 新建连接 3、JDK 压缩包下载 下载 jdk1.8 注&#xff1a;此处 CentOS7 是64位&#xff0c;所以下载的是&#xff1a;Linux x64&#xf…

Hbuilder本地调试微信H5项目(二)--添加UView框架插件

摘要 在一个已创建的Hbuilder项目中&#xff0c;添加uView框架插件 前置准备 已安装Hbuilder 已创建uni-app的H5默认模板项目 实现逻辑 在Hbuilder官网找到组件说明页面 下载插件并导入HbuilderX 具体实现 访问网站 访问网址Hbuilder的uView1.8.6版本说明页 或者访问…

Python3操作MySQL8.XX创建表|CRUD基本操作

Python3操作MySQL8.XX创建表|CRUD基本操作 Python3操作SQLite3创建表主键自增长|CRUD基本操作 一&#xff1a; Python3操作Mysql数据库建表 import pymysqlPython3操作Mysql创建表&#xff1a; # 打开数据库连接 db pymysql.connect(host"localhost", user"您…

芯片SoC设计你了解吗?

数字IC设计根据岗位性质一般包含SOC设计&#xff0c;前端设计&#xff0c;ASIC设计&#xff0c;逻辑设计&#xff0c;IP设计&#xff0c;CPU设计等。 有人说&#xff1a;做IP设计就是翻译官&#xff0c;做SOC设计就是连连看。 SoC设计是做什么的&#xff1f;与IP设计有什么不同…

现代架构设计:构建可伸缩、高性能的分布式系统

文章目录 第1节&#xff1a;引言第2节&#xff1a;架构设计的关键原则2.1 微服务架构2.2 异步通信2.3 数据分区和复制2.4 负载均衡 第3节&#xff1a;代码示例3.1 创建产品服务3.2 创建消息队列3.3 创建产品更新服务 第4节&#xff1a;性能优化和监控4.1 建立性能基准4.2 水平扩…

解密智能化评估在培训考试系统中的应用

智能化评估在培训考试系统中的应用旨在提供更全面和准确的评估方式&#xff0c;以帮助培训机构或个人评估学员的学习成果。该系统结合了现代技术和评估理论&#xff0c;能够自动化地进行评估、反馈和分析&#xff0c;提供个性化的学习支持和指导。 智能化评估系统通过采集学员…

TensorFlow入门(四、数据流向机制)

session与"图"工作过程中存在的两种数据的流向机制,即:注入机制和取回机制 注入机制(feed):即通过占位符向模式中传入数据 取回机制(fetch):指在执行计算图时&#xff0c;可以同时获取多个操作节点的计算结果 实例代码如下: import tensorflow.compat.v1 as tftf…

信息收集进阶版-张榜公告型收集

信息收集进阶版-张榜公告型收集 一、思路&#xff08;1&#xff09;张榜公告型收集1.明确思维&#xff0c;构建思维导图2.逐行分析①利用FOFA、SHODAN、Hunter来直接精确定位到想要的资产②调用nmap来确认端口是否是正常开放③批量检测收集到的资产是否是正常回复④编写POC检测…

csdn未经允许将我的文章设置成vip收费

以前在csdn写了一些笔记&#xff0c;后来不用csdn了&#xff0c;想着留下这些笔记或多或少能帮助其他初学者&#xff0c;就没管它。结果csdn把文章设置成收费了&#xff0c;这个收费不是我本人弄的&#xff0c;是csdn弄的&#xff01;我现在只能把这些文章删除掉了。

【慕伏白教程】 Linux 深度学习服务器配置指北

文章目录 镜像烧录系统安装系统配置常用包安装 镜像烧录 下载 Ubuntu 镜像 Ubuntu 桌面版 下载烧录工具 balenaEtcher 准备至少 8G 的 空白U盘 开始烧录 系统安装 开机进入BIOS&#xff0c;修改U盘为第一启动 选择 Try or Install Ubuntu 往下拉&#xff0c;选择 中文&a…

1、靶机——Pinkys-Place v3(1)

文章目录 一、环境二、获取flag11、扫描局域网内存活主机1.1 查看kali的IP地址1.2 扫描存活主机 2、粗略扫描靶机端口&#xff08;服务&#xff09;3、寻找ftp服务漏洞4、扫描端口详细信息5、匿名登录ftp 一、环境 攻击机&#xff1a;kali 靶机&#xff1a;Pinkys-Place v3&am…

【独家专访】“数网”同防筑牢屏障——新型电力系统网络安全保障体系需加快调整

随着全球数字化进程不断加快&#xff0c;在国际竞争和冲突中&#xff0c;网络战和数据战已然屡见不鲜。电力作为关系国计民生的关键行业&#xff0c;更成为网络攻击的重要对象。加强电力等关键信息基础设施的网络安全保障&#xff0c;是国家今后一段时期的重点工作。7月15日召开…

json对象中嵌套一个json字符串,python如何生成带有转义字符的json的字符串?

前言 不想用java去弄&#xff0c;一顿操作json.dumps也没用&#xff0c;后面才知道需要这么操作 目的生成&#xff1a; data {"json": "{\"key1\": \"value1\", \"key2\": \"value2\"}" }但是直接用 import …

微信删除好友对方知道吗?如何加回微信好友?

微信是我们日常生活中使用最多的社交软件&#xff0c;很多小伙伴在使用微信时都曾发出过这样的疑问&#xff1a;微信删除好友对方知道吗&#xff1f;当自己在微信中删除某人后&#xff0c;对方是否会收到信息提醒&#xff1f;另外&#xff0c;如果删除好友后感到后悔&#xff0…

lenovo联想笔记本电脑ThinkPad X13 AMD Gen2(20XH,20XJ)原装出厂Windows10系统镜像

联想原厂Win10系统&#xff0c;自带所有驱动、出厂主题壁纸、系统属性联想LOGO专属标志、Office办公软件、联想电脑管家等预装程序 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;dolg 适用于型号&#xff1a;20XL,20XJ,20XG,21A1,20XK,20XH,20XF,21A0 所需要…

竞赛 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…