【SpringBoot基础系列-实战】如何指定 bean 最早加载(应用篇)

【基础系列-实战】如何指定 bean 最早加载(应用篇)java

在平常的业务开发中,绝大多数咱们都是不关注 bean 的加载顺序,然而若是在某些场景下,当咱们但愿某个 bean 优于其余的 bean 被实例化时,每每并无咱们想象中的那么简单git

<!-- more -->github

I. 启动类指定方式

在实际的 SpringBoot 开发中,咱们知道都会有一个启动类,若是但愿某个类被优先加载,一个成本最低的简单实现,就是在启动类里添加上依赖spring

@SpringBootApplication
public class Application {

    public Application(DemoBean demoBean) {
        demoBean.print();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

请注意上面的构造方法,若是咱们但愿在应用启动以前,demoBean就已经被加载了,那就让 Application 强制依赖它,因此再 Application 的 bean 初始化以前,确定会优先实例化demoBeanspringboot

相信上面这种写法,你们并不会陌生,特别是当咱们应用启动以后,发现某个依赖的 bean(通常来说是第三方库提供的 bean)尚未初始化致使 npe 时,用这种方法仍是比较多的微信

case1app

咱们且不谈这种实现方式是否优雅,当咱们但愿targetBean在全部的 bean 实例化以前被实例时,上面这种写法是否必定会生效呢?ide

case2spring-boot

中间件同窗:吭哧吭哧的开发了一个 🐂🍺jar 包,只要接入了保证你的应用永远不会宕机(请无视夸张的言语),惟一的要求是接入时,须要优先加载 jar 包里面的firstBean...工具

接入方:你的 bean 要求被首先加载这个得你本身保证啊,我写些 if/else 代码已经很辛苦了,哪有精力保证你的这个优先加载!!!你本身都无法保证,那我也没办法保证...

中间件同窗:还能不能愉快的玩耍了....

II. InstantiationAwareBeanPostProcessorAdapter方式

在看下文的实现以前,墙裂推荐先看一下博文: 【SpringBoot 基础系列】指定 Bean 初始化顺序的若干姿式

接下来介绍另一种使用姿式,借助InstantiationAwareBeanPostProcessorAdapter来实如今 bean 实例化以前优先加载目标 bean

声明

  • 我我的认为下面这种使用方式,依然很不优雅,若有更好方式,恳请大佬留言告知
  • 我我的认为下面这种使用方式,依然很不优雅,若有更好方式,恳请大佬留言告知
  • 我我的认为下面这种使用方式,依然很不优雅,若有更好方式,恳请大佬留言告知

1. 场景分析

假设咱们提供了一个配置读取的工具包,可是不一样的应用可能对配置的存储有不一样的要求,好比有的配置存在本地,有的存在 db,有的经过 http 方式远程获取;而这些存储方式呢,经过application.yml配置文件中的配置参数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 被实例,以此来间接的达到咱们的目的

关于上面这一套流程分析, 请关注微信公众号/我的博客站点,静待源码分析篇

接下来的问题就是如何让它生效了,咱们这里使用 Import 注解来实现

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ClientAutoConfiguration.class, ClientBeanProcessor.class})
public @interface EnableOrderClient {
}

请注意上面的注解中,导入上面的自动配置类,和ClientBeanProcessor,因此上一节中的spring.factories文件能够不须要哦

4. 测试

上面的主要流程就完事了,接下来就须要进入测试,咱们新建一个 SpringBoot 项目,添加依赖

先加一个 demoBean

@Component
public class DemoBean {

    public DemoBean() {
        System.out.println("demo bean init!");
    }

    public void print() {
        System.out.println("print demo bean ");
    }
}

而后是启动类, @EnableOrderClient这个注解必须得有哦

@EnableOrderClient
@SpringBootApplication
public class Application {

    public Application(DemoBean demoBean) {
        demoBean.print();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

在咱们启动以前,请猜想一下,DemoBeanDatasourceLoader这里这两个 bean,谁会优先被实例化?

下面是输出结果

IMAGE

从上面的两个红框输出,能够知道咱们的启动类指定方式依赖的 bean,并不必定会最早被加载哦

5. 小结

最后小结一下,本文提出了两种让 bean 优先加载的方式,一个是在启动类的构造方法中添加依赖,一个是借助InstantiationAwareBeanPostProcessorAdapter在 bean 实例化以前被建立的特色,结合BeanFactory来手动触发目标 bean 的建立

最后经过@Import注解让咱们的BeanPostProcessorAdapter生效

有知道其余方式的大佬,请不吝赐教啊

II. 其余

0. 项目

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛

一灰灰blog

相关文章
相关标签/搜索