在平常的业务开发中,绝大多数咱们都是不关注 bean 的加载顺序,然而若是在某些场景下,当咱们但愿某个 bean 优于其余的 bean 被实例化时,每每并无咱们想象中的那么简单git
<!-- more -->github
在实际的 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 初始化以前,确定会优先实例化demoBean
springboot
相信上面这种写法,你们并不会陌生,特别是当咱们应用启动以后,发现某个依赖的 bean(通常来说是第三方库提供的 bean)尚未初始化致使 npe 时,用这种方法仍是比较多的微信
case1app
咱们且不谈这种实现方式是否优雅,当咱们但愿targetBean
在全部的 bean 实例化以前被实例时,上面这种写法是否必定会生效呢?ide
case2spring-boot
中间件同窗:吭哧吭哧的开发了一个 🐂🍺jar 包,只要接入了保证你的应用永远不会宕机(请无视夸张的言语),惟一的要求是接入时,须要优先加载 jar 包里面的firstBean
...工具
接入方:你的 bean 要求被首先加载这个得你本身保证啊,我写些 if/else 代码已经很辛苦了,哪有精力保证你的这个优先加载!!!你本身都无法保证,那我也没办法保证...
中间件同窗:还能不能愉快的玩耍了....
InstantiationAwareBeanPostProcessorAdapter
方式在看下文的实现以前,墙裂推荐先看一下博文: 【SpringBoot 基础系列】指定 Bean 初始化顺序的若干姿式
接下来介绍另一种使用姿式,借助InstantiationAwareBeanPostProcessorAdapter
来实如今 bean 实例化以前优先加载目标 bean
声明
假设咱们提供了一个配置读取的工具包,可是不一样的应用可能对配置的存储有不一样的要求,好比有的配置存在本地,有的存在 db,有的经过 http 方式远程获取;而这些存储方式呢,经过application.yml
配置文件中的配置参数config.save.mode
来指定
这个工具包呢,会作一件事情,扫描应用程序的全部类,并注入配置信息,因此咱们但愿在应用程序启动以前,这个工具包就已经从数据源获取到了配置信息,而这又要求先获取应用究竟是用的哪一个数据源
简单来说,就是但愿在应用程序工做以前,DatasourceLoader
这个 bean 已经被实例化了
-- 插播一句,上面这个 case,正是我在筹备的SpringBoot实战教程--从0到1建立一个高可用的配置中心
的具体应用场景
新建一个 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)
接下来咱们借助全部的 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
文件能够不须要哦
上面的主要流程就完事了,接下来就须要进入测试,咱们新建一个 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); } }
在咱们启动以前,请猜想一下,DemoBean
和DatasourceLoader
这里这两个 bean,谁会优先被实例化?
下面是输出结果
从上面的两个红框输出,能够知道咱们的启动类指定方式依赖的 bean,并不必定会最早被加载哦
最后小结一下,本文提出了两种让 bean 优先加载的方式,一个是在启动类的构造方法中添加依赖,一个是借助InstantiationAwareBeanPostProcessorAdapter
在 bean 实例化以前被建立的特色,结合BeanFactory
来手动触发目标 bean 的建立
最后经过@Import
注解让咱们的BeanPostProcessorAdapter
生效
有知道其余方式的大佬,请不吝赐教啊
尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛