今日播报!Spring Boot |如何让你的 bean 在其他 bean 之前完成加载

来源:阿里开发者 2023-05-09 03:05:46

1000万云上开发者,全栈云产品0元试用:点击 免费试用 ,即刻开启云上实践之旅!


【资料图】

本文围绕 Spring Boot 中如何让你的 bean 在其他 bean 之前完成加载展开讨论。

作者 | 刘顺(萧易)

来源 | 阿里开发者公众号

问题

今天有个小伙伴给我出了一个难题:在 SpringBoot 中如何让自己的某个指定的 Bean 在其他 Bean 前完成被 Spring 加载?我听到这个问题的第一反应是,为什么会有这样奇怪的需求?

Talk is cheap,show me the code,这里列出了那个想做最先加载的“天选 Bean” 的代码,我们来分析一下:

/** * 系统属性服务**/@Servicepublic class SystemConfigService {    // 访问 db 的 mapper    private final SystemConfigMapper systemConfigMapper;    // 存放一些系统配置的缓存 map    private static Map>SYS_CONF_CACHE = new HashMap<>()    // 使用构造方法完成依赖注入    public SystemConfigServiceImpl(SystemConfigMapper systemConfigMapper) {        this.systemConfigMapper = systemConfigMapper;    }    // Bean 的初始化方法,捞取数据库中的数据,放入缓存的 map 中    @PostConstruct    public void init() {        // systemConfigMapper 访问 DB,捞取数据放入缓存的 map 中        // SYS_CONF_CACHE.put(key, value);        // ...    }    // 对外提供获得系统配置的 static 工具方法    public static String getSystemConfig(String key) {        return SYS_CONF_CACHE.get(key);    }    // 省略了从 DB 更新缓存的代码    // ...}

看过了上面的代码后,很容易就理解了为什么会标题中的需求了。

SystemConfigService 是一个提供了查询系统属性的服务,系统属性存放在 DB 中并且读多写少,在 Bean 创建的时候,通过 @PostConstruct 注解的 init() 方法完成了数据加载到缓存中,最关键的是,由于是系统属性,所以需要在很多地方都想使用,尤其需要在很多 bean 启动的时候使用, 为了方便就提供了 static 方法来方便调用,这样其他的 bean 不需要依赖注入就可以直接调用,但问题是系统属性是存在 db 里面的,这就导致了不能把 SystemConfigService做成一个纯「工具类」,它必须要被 Spring 托管起来,完成 mapper 的注入才能正常工作。因此这样一来就比较麻烦,其他的类或者 Bean 如果想安全的使用 SystemConfigService#getSystemConfig 中的获取配置的静态方法,就必须等 SystemConfigService 先被 Spring 创建加载起来,完成 init() 方法后才可以。

所以才有了最开头提到的问题,如何让这个 Bean 在其他的 Bean 之前加载。

SpringBoot 官方文档推荐做法

这里引用了一段 Spring Framework 官方文档的原文:

Constructor-based or setter-based DI?

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.

可以看到 Spring 对于依赖注入更推荐(is preferable)使用构造函数来注入必须的依赖,用 setter 方法来注入可选的依赖。至于我们平时工作中更多采用的 @Autowired 注解 + 属性的注入方式是不推荐的,这也是为什么你用 Idea 集成开发环境的时候会给你一个警告。

按照 Spring 的文档,我们应该直接去掉 getSystemConfig 的 static 修饰,让 getSystemConfig 变成一个实例方法,让每个需要依赖的 SystemConfigService 的 Bean 通过构造函数完成依赖注入,这样 Spring 会保证每个 Bean 在创建之前会先把它所有的依赖创建并初始化完成。

这也是我建议的做法,接下来我们以抱着学习和技术交流为目的看看还能做些什么来完成提名中的需求。

尝试解决问题的一些方法

@Order 注解或者实现 org.springframework.core.Ordered

最先想到的就是 Spring 提供的 Order 相关的注解和接口,实际上测试下来不可行。Order 相关的方法一般用来控制 Spring 自身组件相关 Bean 的顺序,比如 ApplicationListener,RegistrationBean 等,对于我们自己使用 @Service @Compont 注解注册的业务相关的 bean 没有排序的效果。

@AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore 注解

测试下来这些注解也是不可行,它们和 Ordered 一样都是针对 Spring 自身组件 Bean 的顺序。

@DependsOn 注解

接下来是尝试加上 @DependsOn 注解:

@Service@DependsOn({\"systemConfigService\"})public class BizService {    public BizService() {        String xxValue = SystemConfigService.getSystemConfig(\"xxKey\");        // 可行    }}

这样测试下来是可以是可以的,就是操作起来也太麻烦了,需要让每个每个依赖 SystemConfigService 的 Bean 都改代码加上注解,那有没有一种默认就让 SystemConfigService 提前的方法?

上面提到的方法都不好用,那我们只能利用 spring 给我们提供的扩展点来做文章了。

Spring 中 Bean 创建的相关知识

首先要明白一点,Bean 创建的顺序是怎么来的,如果你对 Spring 的源码比较熟悉,你会知道在 AbstractApplicationContext 里面有个 refresh 方法, Bean 创建的大部分逻辑都在 refresh 方法里面,在 refresh 末尾的 finishBeanFactoryInitialization(beanFactory) 方法调用中,会调用 beanFactory.preInstantiateSingletons(),在这里对所有的 beanDefinitionNames 一一遍历,进行 bean 实例化和组装:

这个 beanDefinitionNames 列表的顺序就决定了 Bean 的创建顺序,那么这个 beanDefinitionNames 列表又是怎么来的?答案是 ConfigurationClassPostProcessor 通过扫描你的代码和注解生成的,将 Bean 扫描解析成 Bean 定义(BeanDefinition),同时将 Bean 定义(BeanDefinition)注册到 BeanDefinitionRegistry 中,才有了 beanDefinitionNames 列表。

剩余60%,完整内容请点击下方链接查看:

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

关键词:

为你推荐

Copyright   2015-2022 海峡家具网 版权所有  备案号:皖ICP备2022009963号-10   联系邮箱:396 029 142 @qq.com