Spring6.2.0异步创建Bean容器
spring官方目前在6.2.0快照版本中已经增加支持spring启动异步创建Bean的功能。官方原文如下
As of 6.2, there is a background initialization option: @Bean(bootstrap=BACKGROUND) allows for singling out specific beans for background initialization, covering the entire bean creation step for each such bean on context startup.
大概含义就是从6.2.0开始增加了一个Bean后台初始化的选项@Bean(bootstrap=BACKGROUND)
。配合bootstrapExecutor类型的Bean可以实现在后台初始化Bean容器的功能。
1、准备
-
需要准备一个spring工程,不可以使用SpringBoot工程。原因是因为目前最新快照版本的SpringBoot依赖的Spring版本未升级到6.2.0,还停留在6.1.6阶段,强行使用会踩坑。官方预计SpringBoot3.4版本会升级到Spring6.2.0。POM引入
spring-core
,spring-context
等依赖。版本为6.2.0-SNAPSHOT
快照版本。注意:目前大家配置的maven大多为国内的阿里源仓库,目前阿里仓库尝试了下没有最新的快照版本,需要在POM中手动指定spring仓库。完整POM.xml如下:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <!--省略。。。--> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.2.0-SNAPSHOT</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.2.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/libs-snapshot-local</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>false</enabled> </releases> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone-local</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
-
项目代码结构
-
-
配置类里面配置我们定义的三个测试Bean对象
- 创建了一个配置类和三个Bean,每个Bean的构造方法休眠一秒来达到测试的目的。
-
-
2、启动,查看同步创建Bean启动运行结果
* 启动时间总共花费了3秒多,主要时间都用在了每个Bean构造方法的sleep上。
3、修改为异步创建Bean
-
每个Bean增加**@Bean(bootstrap = Bean.Bootstrap.BACKGROUND)**注解,增加BeanFactoryPostProcessor来拦截设置BootstrapExecutor或给更多的Bean手动设置
异步创建
。注:BootstrapExecutor则不会执行异步创建Bean。后面会看源码有体现。 -
-
查看运行效果
第一个Bean在49秒的时候创建,输出日志。第二第三这时候是在同一时间由两个线程输出日志,并且为我们自定义线程池executor的线程名称。由此可见这两个Bean是由Spring调用我们注册给Bean工厂的线程池来执行创建流程。
-
-
更复杂的场景,Bean之间存在依赖关系
-
假设有这样两个Bean:V1(异步创建)包含V2,V2(同步创建)。那么Spring该如何处理呢。
-
Autowired注解注入方式
-
修改代码,启动后获取V1对象,然后调用V1对象的方法来执行V2属性的方法。最后运行结果可以正常运行。并且两个Bean是在同一时间被输出,被注入的属性还是由程序的main方法创建,异步注解标注的Bean由我们自定义的线程池来创建。
-
-
-
-
-
-
构造注入方式
构造方法本质就是利用又参的构造方法来创建所依赖的Bean对象,当作参数传递进入构造方法进行Bean的创建。大白话说就是提前创建出所需要的对象。
-
-
-
-
修改代码运行程序后发现程序报错。提示:不能够在异步线程中实例化V1这个对象,原因是依赖关系存在问题。创建名称为“V2”的bean时出错,v2在主线程初始化,但是在异步线程中创建的Bean实例化早于依赖的V2。
-
-
此时把依赖的V2修改为同样异步创建后发现程序可以正常运行,观察日志发现,虽然都是在线程池中执行,但是这两个对象在线程池中同步创建了。先创建了V2,然后创建了V2。日志输出差了1秒钟正是V1构造方法中sleep中的一秒。
-
-
我们将上面的场景反过来,V1此刻变为同步,V2变为异步创建。那么此时程序还可以正常输出吗?
-
-
-
-
此时可以看见程序出错抛出异常。v1在主线程执行,但是依赖的v2是在后台线程中执行初始化。在主线程中请求初始化出错。
-
尝试下使用构造注入
-
-
程序还是抛出了一样的错误
-
-
结论
1、异步创建的Bean之间在构造或属性注入时都可以互相依赖,Spring可以正常进行注入帮我们管理。虽然也是根据依赖关系同步创建这些Bean,但是同步的操作是在线程池内执行,不影响主线程的执行逻辑。 2、异步创建的Bean和同步创建的Bean之间,异步创建的Bean在属性注入同步Bean的情况下可以正常的运行程序。其他的场景下框架都不支持,会出现异常。
4、源码解读
- Bean.bootstrap作用
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { //Spring在6.2.0版本中在Bean注解增加了bootstrap字段来标识该Bean是否运行异步进行创建。该注解的作用主要是在 //ConfigurationClassBeanDefinitionReader类中进行判断来进行设置BeanDefintion的backgroundInit属性 //Bean.Bootstrap instantiation = (Bean.Bootstrap)bean.getEnum("bootstrap"); // if (instantiation == Bootstrap.BACKGROUND) { // beanDef.setBackgroundInit(true); // } //实现BeanDefinitionRegistryPostProcessor接口拦截每个BeanDefinitionRegistry设置这个属性同样可以达到效果 Bootstrap bootstrap() default Bootstrap.DEFAULT; enum Bootstrap { /** * Constant to indicate the main pre-instantiation thread for non-lazy * singleton beans and the caller thread for prototype beans. */ DEFAULT,//默认走同步 /** * Allow for instantiating a bean on a background thread. * <p>For a non-lazy singleton, a background pre-instantiation thread * can be used while still enforcing the completion on context refresh. * For a lazy singleton, a background pre-instantiation thread can be used * with completion allowed at a later point (when actually accessed). */ BACKGROUND,//开启异步支持 } }
2、处理流程
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { 。。。。。。。。。。省略其他方法 @Override public void preInstantiateSingletons() throws BeansException {{ if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... List<CompletableFuture<?>> futures = new ArrayList<>(); this.preInstantiationThread.set(PreInstantiation.MAIN); try { for (String beanName : beanNames) { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); if (!mbd.isAbstract() && mbd.isSingleton()) { //异步Bean:返回future,在下方等待所有异步Bean创建完成后执行对应后置处理。 //同步bean:返回Null,不加入future集合,并且会将preInstantiationThread修改为BACKGROUND //证实异步同步Bean是交叉创建的。如果同步异步Bean之间产生刚才实验所出现的异常会不太好处理 CompletableFuture<?> future = preInstantiateSingleton(beanName, mbd); if (future != null) { futures.add(future); } } } } finally { this.preInstantiationThread.set(null); } if (!futures.isEmpty()) { try { //等所有的异步Bean对象都创建完成后继续执行同步Bean的创建。 CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join(); } catch (CompletionException ex) { ReflectionUtils.rethrowRuntimeException(ex.getCause()); } } //符合ins类型的话调用该Bean对应方法。类似后置处理。 // Trigger post-initialization callback for all applicable beans... for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName, false); if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) { StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize") .tag("beanName", beanName); smartSingleton.afterSingletonsInstantiated(); smartInitialize.end(); } } } 。。。。。。。。。。省略其他方法 }
3、同步Bean依赖异步Bean程序异常
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) { //异步同步依赖核心判断 //1、如果是同步线程进入,则preInstantiationThread=MAIN,如果同步调用异步Bean执行了创建方法。异步Bean在此判断就会成立,所以同步不可以依赖异步Bean //判断当前创建的Bean是否是异步创建 if (mbd.isBackgroundInit()) { //判断当前执行的环境是否是主线程环境 if (this.preInstantiationThread.get() == PreInstantiation.MAIN && getBootstrapExecutor() != null) { throw new BeanCurrentlyInCreationException(beanName, "Bean marked for background " + "initialization but requested in mainline thread - declare ObjectProvider " + "or lazy injection point in dependent mainline beans"); } } else { //当前是创建的是同步对象 && 当前是异步线程环境 则会提示异常 //为什么异步对象依赖的同步对象创建不报错。因为属性注入同步对象不从这创建,所以不报错。 if (this.preInstantiationThread.get() == PreInstantiation.BACKGROUND) { throw new BeanCurrentlyInCreationException(beanName, "Bean marked for mainline initialization " + "but requested in background thread - enforce early instantiation in mainline thread " + "through depends-on '" + beanName + "' declaration for dependent background beans"); } } }
-
-