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

文章目录
  1. I. 启动类指定方式
  2. II. InstantiationAwareBeanPostProcessorAdapter方式
    1. 1. 场景分析
    2. 2. 常规流程
    3. 3. 特殊写法
    4. 4. 测试
    5. 5. 小结
  3. II. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog

在日常的业务开发中,绝大多数我们都是不关注bean的加载顺序,然而如果在某些场景下,当我们希望某个bean优于其他的bean被实例化时,往往并没有我们想象中的那么简单

I. 启动类指定方式

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

1
2
3
4
5
6
7
8
9
10
11
@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.yml配置文件中的配置参数config.save.mode来指定

这个工具包呢,会做一件事情,扫描应用程序的所有类,并注入配置信息,所以我们希望在应用程序启动之前,这个工具包就已经从数据源获取到了配置信息,而这又要求先获取应用到底是用的哪个数据源

简单来讲,就是希望在应用程序工作之前,DatasourceLoader这个bean已经被实例化了

– 插播一句,上面这个case,正是我在筹备的SpringBoot实战教程--从0到1创建一个高可用的配置中心的具体应用场景

2. 常规流程

新建一个SpringBoot项目工程,源码中springboot版本为2.2.1.RELEASE

首先我们来定义这个目标bean: DatasourceLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的通常玩法,声明一个自动配置类

1
2
3
4
5
6
7
@Configuration
public class ClientAutoConfiguration {
@Bean
public DatasourceLoader propertyLoader(Environment environment) {
return new DatasourceLoader(environment);
}
}

然后在资源目录下新建文件夹 META-INF,创建文件spring.factories,内容如下

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.client.ClientAutoConfiguration

然后使用方添加依赖,就完了???

上面这套流程,属于一般的工具包写法了,请注意,这种方式,一般情况下是应用程序内声明的bean加载完毕之后,才会加载第三方依赖包中声明的bean;也就是说通过上面的写法,DatasourceLoader并不会被优先加载,也达不到我们的目的(应用都开始服务了,结果所有的配置都是null)

3. 特殊写法

接下来我们借助所有的bean在实例化之前,会优先检测是否存在InstantiationAwareBeanPostProcessor接口这个特点,来实现DatasourceLoader的优先加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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注解来实现

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ClientAutoConfiguration.class, ClientBeanProcessor.class})
public @interface EnableOrderClient {
}

请注意上面的注解中,导入上面的自动配置类,和ClientBeanProcessor,所以上一节中的spring.factories文件可以不需要哦

4. 测试

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

先加一个demoBean

1
2
3
4
5
6
7
8
9
10
11
@Component
public class DemoBean {

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
@EnableOrderClient
@SpringBootApplication
public class Application {

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

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

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

下面是输出结果

从上面的两个红框输出,可以知道我们的启动类指定方式依赖的bean,并不一定会最先被加载哦

5. 小结

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

最后通过@Import注解让我们的BeanPostProcessorAdapter生效

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

II. 其他

0. 项目

1. 一灰灰Blog

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

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到