实战指南:四种调整 Spring Bean 初始化顺序的方案

简介: 本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析:1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。

背景

因为业务需求,mentor想要某些 bean 启动时优先加载,将数据存入缓存,便问我,“能不能调下Bean初始化顺序?”,于是便有了这篇文章

结构演示

目前一共有两个 service ,每个 service 都有一个 init 方法,打印bean创建时机,正常状态打印结果如下:

正文

方案一 ( @Order )

这是第一个想到的方法,我们给每个service上加上@Order,让他们倒序创建

代码

结果

嗯?不是数字越低优先级越高吗,结果怎么还是 1 -> 2?相信眼尖的人已经看出来了,我在开头埋了个坑,用 @PostConstruct 作初始化操作

题外话( @PostConstruct 和 @Order 优先级)

  • @PostConstruct 修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次,也就在依赖注入完成后立即调用,bean 初始化阶段执行的。
  • @Order 注解用于设置组件的执行顺序,排序集合或指定某些类型的组件的优先级

@PostConstruct 方法的执行顺序是由 Spring 容器在 bean 初始化过程中自动管理的,与 @Order 注解无关。

SpringBootrun 源码角度来看

结论

优点 :简单明了,适用于需要简单控制初始化顺序的场景。

缺点:只适用于具有顺序的Bean,无法处理复杂依赖关系,遇到如 @PostConstruct 会失效

方案二 ( SmartInitializingSingleton )

SmartInitializingSingleton 用于在容器完成所有单例 bean 的初始化后执行一些额外的初始化工作。用这个接口应该能保证 FirstService 后创建了吧

代码

结果

还是不行@PostConstruct 的东西还是先创建了,不过起码保证了 FirstServiceafterSingletonsInstantiated 方法是所有单例 Bean 初始化之后执行的。

不过毫无疑问,被驳回了,还说这要是有三个或多个 Bean 有这业务怎么办

结论

优点 :确保所有单例bean都初始化,适合在所有Bean创建后执行全局初始化逻辑。

缺点:不适合控制特定Bean之间的初始化顺序。

方案三 ( @DependsOn )

既然加载顺序不行,还要有多个 Bean ,那就从 Bean 间的依赖入手嘛

代码

结果

不出意外的成功了,但是mentor嫌耦合性太高,一处改了处处改,后期项目大了找不到不方便维护

结论

优点 :简单明了,适用于明确的依赖关系。

缺点:依赖关系硬编码在配置类中,灵活性较低,耦合性高。

方案四 ( 自定义 Bean 初始化类 )

那好嘛,那自定义呗

代码

在 META-INF 的 spring.factories 加上配置

ini

代码解读

复制代码

org.springframework.context.ApplicationContextInitializer=\com.hhh.init.MyBeanInit

解释

  1. 该自定义类 MyBeanInit 分别继承了 BeanDefinitionRegistryPostProcessorApplicationContextInitializer ,重写其内部方法
  • BeanDefinitionRegistryPostProcessor提前注册或修改 Bean 定义,在所有其他 BeanFactoryPostProcessor 运行之前执行,允许我们注册或修改 BeanDefinition
  • ApplicationContextInitializer初始化应用上下文,在应用上下文刷新之前调用,可以动态地为应用上下文添加属性、BeanFactoryPostProcessor 或其他配置。
  1. postProcessBeanDefinitionRegistry 方法中,使用 BeanDefinitionBuilder 创建了两个 AbstractBeanDefinition 实例,分别对应 ThirdServiceSecondService 类,然后,将这些 Bean 定义注册到 Spring 容器中,分别命名为 "thirdService""secondService"
  2. initialize 方法中,将当前的 BeanDefinitionRegistryPostProcessor 实例( MyBeanInit 自身)添加到应用上下文的 BeanFactoryPostProcessor 列表中。在容器启动时,postProcessBeanDefinitionRegistry 方法将被调用,从而注册我们在上面定义的 Bean。

简而言之:自定义 Bean 注册方法,将自己想要优先加载的 Bean 塞进去,再加入上下文中加载

这里我们也可以把 @Component 去掉,因为在自定义初始化类中加载了,不需要被 ComponentScan 再扫描注册一次,以免出现重复注册异常

结果

结论

优点 :灵活性高,可以用于复杂的初始化逻辑。

缺点 :需要手动管理Bean的初始化顺序,代码维护成本较高。


转载来源:https://juejin.cn/post/7380663251632586767

相关文章
|
3天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
9 1
Spring高手之路24——事务类型及传播行为实战指南
|
30天前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
61 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
20天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
30天前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
64 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
28 1
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
27天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
141 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
27天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
47 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
4月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。