指定Bean加载顺序的能力

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容器中大部分的可扩展点的分析,之前已经写了一篇文章详细介绍了

实现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之间的依赖先后顺序。

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

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

相关文章

后端开发如何高效使用 Apifox?

Apifox 是一个 API 协作开发平台&#xff0c;后端、前端、测试都可以使用 Apifox 来提升团队的工作效率。对于后端开发者而言&#xff0c;Apifox 的核心功能主要包括四个模块&#xff1a;调用 API、定义 API、开发与调试 API 以及生成 API 文档。本文将详细介绍后端开发人员如何…

前端经典面试合集(二)——Vue/React/Node/工程化工具/计算机网络

1. 说说 Vue 中的 Diff 算法 Vue 的 Diff 算法 主要用于优化虚拟 DOM 和实际 DOM 之间的比较过程。它通过以下几种策略来提高性能&#xff1a; 最小化对 DOM 的操作&#xff1a;Vue 通过在内存中构建一个虚拟 DOM 树&#xff0c;在虚拟 DOM 树与真实 DOM 树之间进行比较和更新…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

极客说|微软新模型:Phi-4 来了

作者&#xff1a;魏新宇 - 微软 AI 全球黑带高级技术专家 「极客说」 是一档专注 AI 时代开发者分享的专栏&#xff0c;我们邀请来自微软以及技术社区专家&#xff0c;带来最前沿的技术干货与实践经验。在这里&#xff0c;您将看到深度教程、最佳实践和创新解决方案。关注「极客…

redis相关数据类型介绍

当然&#xff0c;Redis 作为一个高性能的键值存储系统&#xff0c;提供了多种数据类型来支持不同的应用场景。 1. String&#xff08;字符串&#xff09; • 定义&#xff1a;Redis 最基本的数据类型&#xff0c;用于存储字符串值。 • 操作&#xff1a;SET、GET、INCR、DECR、…

arthas查看拼接好参数的sql, redis, es完整可直接执行的命令

arthas查看拼接好参数的sql, redis, es完整可直接执行的命令 arthas查看sql可执行命令arthas查看redis可执行命令arthas查看es可执行命令相关链接 经常修bug的时候, 拿不到能够执行的命令, 真是太难受了 arthas查看sql可执行命令 # mybatis plus (参数和sql分离了) watch org.…

OpenHarmony怎么修改DPI密度值?RK3566鸿蒙开发板演示

本文介绍在开源鸿蒙OpenHarmony系统下&#xff0c;修改DPI密度值的方法&#xff0c;触觉智能Purple Pi OH鸿蒙开发板演示&#xff0c;搭载了瑞芯微RK3566四核处理器&#xff0c;Laval鸿蒙社区推荐开发板&#xff0c;已适配全新开源鸿蒙OpenHarmony5.0 Release系统&#xff0c;适…

电子应用设计方案74:智能家庭对讲系统设计

智能家庭对讲系统设计 一、引言 智能家庭对讲系统作为智能家居的重要组成部分&#xff0c;为家庭成员之间以及与访客的沟通提供了便捷、高效的方式。本设计方案旨在打造一个功能强大、稳定可靠、操作简便且具有良好扩展性的智能家庭对讲系统。 二、系统概述 1. 系统目标 - 实…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS技术理念

1.2 技术理念 在万物智联时代重要机遇期&#xff0c;HarmonyOS结合移动生态发展的趋势&#xff0c;提出了三大技术理念&#xff08;如下图3-1所示&#xff09;&#xff1a;一次开发&#xff0c;多端部署&#xff1b;可分可合&#xff0c;自由流转&#xff1b;统一生态&#xf…

《医药养生保健报》是正规报刊吗?如何在数据库搜索报刊信息?

在数据库检索报刊的正规性&#xff0c;可以说是论文发表环节中一个重中之重的环节。文章能否被数据库正常收录&#xff0c;很大程度上会影响到毕业、评职称的审核结果。 目前主流认可的三大数据库分别为中国知网、万方数据以及维普资讯。接下来就让我们以《医药养生保健报》为例…

Go主协程如何等其余协程完再操作

在Go语言中&#xff0c;主协程&#xff08;main goroutine&#xff09;可以使用多种方式来等待其他协程完成其操作。常见的方法是使用通道&#xff08;channels&#xff09;和 sync 包中的工具&#xff0c;比如 sync.WaitGroup。以下是这两种方法的示例&#xff1a; 使用 sync…

OSI 七层模型 | TCP/IP 四层模型

注&#xff1a;本文为 “OSI 七层模型 | TCP/IP 四层模型” 相关文章合辑。 未整理去重。 OSI 参考模型&#xff08;七层模型&#xff09; BeretSEC 于 2020-04-02 15:54:37 发布 OSI 的概念 七层模型&#xff0c;亦称 OSI&#xff08;Open System Interconnection&#xf…

代码随想录训练营第三十一天| 56. 合并区间 738.单调递增的数字 贪心总结

56. 合并区间 题目讲解&#xff1a;56. 合并区间 - 力扣&#xff08;LeetCode&#xff09; 讲解链接&#xff1a;代码随想录 和昨天的思路一致 这个是优先用左区间排列 Java代码&#xff1a; class Solution {public int[][] merge(int[][] intervals) {List<int[]> …

使用cmark解析Markdown文档

如果在C或者C项目中解析Markdown&#xff0c;可以使用cmark库。 开发环境 Fedora系统可以直接通过 dnf install cmark-devel来安装cmark的开发库。 安装之后&#xff0c;就可以使用头文件/usr/include/cmark.h 中的函数进行开发&#xff0c;最后在程序中链接-lcmark即可。 …

基于 Python Django 的农产品销售系统的研究与实现

大家好&#xff0c;我是stormjun&#xff0c;今天为大家带来的是基于 Python Django 的农产品销售系统的研究与实现。该系统采用 Python 语言 开发&#xff0c;MySql 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强 &#xff0c;可供大学生实战项目参考使用。 博主介…

Linux隐藏登录和清除历史命令以及其他相关安全操作示例

隐藏登录 ssh -T rootxxx.xxx.xxx.xxx /bin/bash -i 命令拆解-T &#xff1a;告诉ssh客户端&#xff0c;不要分配一个TTY&#xff08;伪终端&#xff09;root &#xff1a;连接用户xxx.xxx.xxx.xxx &#xff1a;连接的服务器ip地址/bin/bash &#xff1a;在远程服务器上启动…

uniapp实现APP、小程序与webview页面间通讯

需求&#xff1a; 1、需要在Uniapp开发的APP或小程序页面嵌入一个H5网页&#xff0c;需要拿到H5给APP传递的数据。 2、并且这个H5是使用vuevant开发的。&#xff08;其实跟使用uniapp开发H5一样&#xff09; 实现步骤&#xff1a; 1、首先需要兼容多端和App端&#xff0c;因…

每天40分玩转Django:Django部署概述

一、Django部署概述 在开发阶段,我们通常使用Django内置的轻量级开发服务器runserver。但在生产环境中,为了应对大量并发请求,需要使用高性能的WSGI服务器,如Gunicorn、uWSGI等。同时还要配置Nginx等Web服务器作为反向代理,实现负载均衡、静态文件处理等。下面是Django部署的整…

Python 爬虫中的反爬策略及详细应对方法

在构建Python爬虫的过程中&#xff0c;网站为了保护自身资源和用户体验&#xff0c;常常会采取一系列反爬策略来限制或阻止自动化程序的访问。了解这些策略对于设计更智能、更合规的爬虫至关重要。以下是详细的反爬措施及其应对方法&#xff1a; 1. User-Agent 检测 策略描述…

FreeSWITCH 简单图形化界面38 - 使用uniapp中使用JsSIP进行音视频呼叫

FreeSWITCH 简单图形化界面38 - 在uniapp中使用JsSIP进行音视频呼叫 0、测试环境1、学习uniapp2、测试代码main.jsutils/render.jsstore/data.jspages/index/index.vuepages.json 3、效果4、难点 0、测试环境 http://myfs.f3322.net:8020/ 用户名&#xff1a;admin&#xff0c…