在日常的业务开发中,绝大多数我们都是不关注bean的加载顺序,然而如果在某些场景下,当我们希望某个bean优于其他的bean被实例化时,往往并没有我们想象中的那么简单
在实际的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时,用这种方法还是比较多的
我们且不谈这种实现方式是否优雅,当我们希望 targetBean
在所有的bean实例化之前被实例时,上面这种写法是否一定会生效呢?
中间件同学:吭哧吭哧的开发了一个:ox::beer: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或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
打赏 如果觉得我的文章对您有帮助,请随意打赏。