Spring6.2.0支持异步创建Bean容器

本文介绍了Spring6.2.0版本新增的异步Bean创建功能,包括如何配置、异步与同步Bean之间的依赖处理以及源码解析。开发者需要注意在不同场景下正确使用异步初始化以避免错误。
摘要由CSDN通过智能技术生成

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、准备

  1. 需要准备一个spring工程,不可以使用SpringBoot工程。原因是因为目前最新快照版本的SpringBoot依赖的Spring版本未升级到6.2.0,还停留在6.1.6阶段,强行使用会踩坑。官方预计SpringBoot3.4版本会升级到Spring6.2.0。POM引入spring-corespring-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>
    
    1. 项目代码结构

      1. 在这里插入图片描述

      2. 配置类里面配置我们定义的三个测试Bean对象

        在这里插入图片描述

        1. 创建了一个配置类和三个Bean,每个Bean的构造方法休眠一秒来达到测试的目的。

        在这里插入图片描述

2、启动,查看同步创建Bean启动运行结果

    * 启动时间总共花费了3秒多,主要时间都用在了每个Bean构造方法的sleep上。

在这里插入图片描述

3、修改为异步创建Bean

  1. 每个Bean增加**@Bean(bootstrap = Bean.Bootstrap.BACKGROUND)**注解,增加BeanFactoryPostProcessor来拦截设置BootstrapExecutor或给更多的Bean手动设置异步创建。注:BootstrapExecutor则不会执行异步创建Bean。后面会看源码有体现。

  2. 在这里插入图片描述
    在这里插入图片描述

    1. 查看运行效果

      第一个Bean在49秒的时候创建,输出日志。第二第三这时候是在同一时间由两个线程输出日志,并且为我们自定义线程池executor的线程名称。由此可见这两个Bean是由Spring调用我们注册给Bean工厂的线程池来执行创建流程。

      在这里插入图片描述

  3. 更复杂的场景,Bean之间存在依赖关系

    1. 假设有这样两个Bean:V1(异步创建)包含V2,V2(同步创建)。那么Spring该如何处理呢

      1. Autowired注解注入方式

        1. 修改代码,启动后获取V1对象,然后调用V1对象的方法来执行V2属性的方法。最后运行结果可以正常运行。并且两个Bean是在同一时间被输出,被注入的属性还是由程序的main方法创建,异步注解标注的Bean由我们自定义的线程池来创建。

        2. 在这里插入图片描述

        3. 在这里插入图片描述

        4. 在这里插入图片描述

        5. 在这里插入图片描述

      2. 构造注入方式

        构造方法本质就是利用又参的构造方法来创建所依赖的Bean对象,当作参数传递进入构造方法进行Bean的创建。大白话说就是提前创建出所需要的对象。
        
        1. 在这里插入图片描述

        2. 在这里插入图片描述

        3. 在这里插入图片描述

        4. 修改代码运行程序后发现程序报错。提示:不能够在异步线程中实例化V1这个对象,原因是依赖关系存在问题。创建名称为“V2”的bean时出错,v2在主线程初始化,但是在异步线程中创建的Bean实例化早于依赖的V2。

        5. 在这里插入图片描述

        6. 此时把依赖的V2修改为同样异步创建后发现程序可以正常运行,观察日志发现,虽然都是在线程池中执行,但是这两个对象在线程池中同步创建了。先创建了V2,然后创建了V2。日志输出差了1秒钟正是V1构造方法中sleep中的一秒。

      3. 我们将上面的场景反过来,V1此刻变为同步,V2变为异步创建。那么此时程序还可以正常输出吗?

        1. 在这里插入图片描述

        2. 在这里插入图片描述

        3. 在这里插入图片描述

        4. 此时可以看见程序出错抛出异常。v1在主线程执行,但是依赖的v2是在后台线程中执行初始化。在主线程中请求初始化出错。

        5. 尝试下使用构造注入

        6. 在这里插入图片描述

        7. 程序还是抛出了一样的错误

      4. 结论

        1、异步创建的Bean之间在构造或属性注入时都可以互相依赖,Spring可以正常进行注入帮我们管理。虽然也是根据依赖关系同步创建这些Bean,但是同步的操作是在线程池内执行,不影响主线程的执行逻辑。
        2、异步创建的Bean和同步创建的Bean之间,异步创建的Bean在属性注入同步Bean的情况下可以正常的运行程序。其他的场景下框架都不支持,会出现异常。
        

      4、源码解读

      1. 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");
      			}
      		}
      	}
      
  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值