[基础系列 - 实战] 如何指定 bean 最先加载 (应用篇)
在日常的业务开发中, 绝大多数我们都是不关注 bean 的加载顺序, 然而如果在某些场景下, 当我们希望某个 bean 优于其他的 bean 被实例化时, 往往并没有我们想象中的那么简单
I. 启动类指定方式
在实际的 SpringBoot 开发中, 我们知道都会有一个启动类, 如果希望某个类被优先加载, 一个成本最低的简单实现, 就是在启动类里添加上依赖
- @SpringBootApplication
- public class Application {
- public Application(DemoBean demoBean) {
- demoBean.print();
- }
- public static void main(String[] args) {
- SpringApplication.run(Application.class);
- }
- }
请注意上面的构造方法, 如果我们希望在应用启动之前, demoBean 就已经被加载了, 那就让 Application 强制依赖它, 所以再 Application 的 bean 初始化之前, 肯定会优先实例化 demoBean
相信上面这种写法, 大家并不会陌生, 特别是当我们应用启动之后, 发现某个依赖的 bean(一般来讲是第三方库提供的 bean) 还没有初始化导致 npe 时, 用这种方法还是比较多的
case1
我们且不谈这种实现方式是否优雅, 当我们希望 targetBean 在所有的 bean 实例化之前被实例时, 上面这种写法是否一定会生效呢?
case2
中间件同学: 吭哧吭哧的开发了一个 jar 包, 只要接入了保证你的应用永远不会宕机 (请无视夸张的言语), 唯一的要求是接入时, 需要优先加载 jar 包里面的 firstBean...
接入方: 你的 bean 要求被首先加载这个得你自己保证啊, 我写些 if/else 代码已经很辛苦了, 哪有精力保证你的这个优先加载!!! 你自己都没法保证, 那我也没办法保证...
中间件同学: 还能不能愉快的玩耍了....
II.
InstantiationAwareBeanPostProcessorAdapter
方式
在看下文的实现之前, 墙裂推荐先看一下博文: [SpringBoot 基础系列] 指定 Bean 初始化顺序的若干姿势
接下来介绍另外一种使用姿势, 借助 InstantiationAwareBeanPostProcessorAdapter 来实现在 bean 实例化之前优先加载目标 bean
声明
我个人认为下面这种使用方式, 依然很不优雅, 如有更好方式, 恳请大佬留言告知
我个人认为下面这种使用方式, 依然很不优雅, 如有更好方式, 恳请大佬留言告知
我个人认为下面这种使用方式, 依然很不优雅, 如有更好方式, 恳请大佬留言告知
1. 场景分析
假设我们提供了一个配置读取的工具包, 但是不同的应用可能对配置的存储有不同的要求, 比如有的配置存在本地, 有的存在 db, 有的通过 http 方式远程获取; 而这些存储方式呢, 通过 application.YAML 配置文件中的配置参数 config.save.mode 来指定
这个工具包呢, 会做一件事情, 扫描应用程序的所有类, 并注入配置信息, 所以我们希望在应用程序启动之前, 这个工具包就已经从数据源获取到了配置信息, 而这又要求先获取应用到底是用的哪个数据源
简单来讲, 就是希望在应用程序工作之前, DatasourceLoader 这个 bean 已经被实例化了
-- 插播一句, 上面这个 case, 正是我在筹备的 SpringBoot 实战教程 -- 从 0 到 1 创建一个高可用的配置中心的具体应用场景
2. 常规流程
新建一个 SpringBoot 项目工程, 源码中 springboot 版本为 2.2.1.RELEASE
首先我们来定义这个目标 bean: DatasourceLoader
- public class DatasourceLoader {
- @Getter
- private String mode;
- public DatasourceLoader(Environment environment) {
- this.mode = environment.getProperty("config.save.mode");
- System.out.println("init DatasourceLoader for:" + mode);
- }
- @PostConstruct
- public void loadResourcres() {
- System.out.println("开始初始化资源");
- }
- }
因为这个工程主要是供第三方使用, 所以按照 SpringBoot 的通常玩法, 声明一个自动配置类
- @Configuration
- public class ClientAutoConfiguration {
- @Bean
- public DatasourceLoader propertyLoader(Environment environment) {
- return new DatasourceLoader(environment);
- }
- }
然后在资源目录下新建文件夹 META-INF, 创建文件 spring.factories, 内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.Git.hui.boot.client.ClientAutoConfiguration
然后使用方添加依赖, 就完了???
上面这套流程, 属于一般的工具包写法了, 请注意, 这种方式, 一般情况下是应用程序内声明的 bean 加载完毕之后, 才会加载第三方依赖包中声明的 bean; 也就是说通过上面的写法, DatasourceLoader 并不会被优先加载, 也达不到我们的目的 (应用都开始服务了, 结果所有的配置都是 null)
3. 特殊写法
接下来我们借助所有的 bean 在实例化之前, 会优先检测是否存在 InstantiationAwareBeanPostProcessor 接口这个特点, 来实现 DatasourceLoader 的优先加载
- public class ClientBeanProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
- private ConfigurableListableBeanFactory beanFactory;
- @Override
- public void setBeanFactory(BeanFactory beanFactory) {
- if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
- throw new IllegalArgumentException(
- "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory:" + beanFactory);
- }
- this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
- // 通过主动调用 beanFactory#getBean 来显示实例化目标 bean
- DatasourceLoader propertyLoader = this.beanFactory.getBean(DatasourceLoader.class);
- System.out.println(propertyLoader);
- }
- }
上面的实现比较简单, 借助 beanFactory#getBean 来手动触发 bean 的实例, 通过实现 BeanFactoryAware 接口来获取 BeanFactory, 因为实现 InstantiationAwareBeanPostProcessor 接口的类会优先于 Bean 被实例, 以此来间接的达到我们的目的
- @Target({
- ElementType.TYPE
- })
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @Import({
- ClientAutoConfiguration.class, ClientBeanProcessor.class
- })
- public @interface EnableOrderClient {
- }
- @Component
- public class DemoBean {
- public DemoBean() {
- System.out.println("demo bean init!");
- }
- public void print() {
- System.out.println("print demo bean");
- }
- }
- @EnableOrderClient
- @SpringBootApplication
- public class Application {
- public Application(DemoBean demoBean) {
- demoBean.print();
- }
- public static void main(String[] args) {
- SpringApplication.run(Application.class);
- }
- }
来源: https://www.cnblogs.com/yihuihui/p/12525117.html