大数跨境
0
0

Spring6.2震撼来袭,多线程实例化Bean应用启动速度飙升!

Spring6.2震撼来袭,多线程实例化Bean应用启动速度飙升! Spring全家桶实战案例
2024-04-15
0
导读:Spring6.2并行化Bean初始化,启动速度飞跃提升!

环境:Spring6.2.0-SNAPSHOT



1. 简介

在最新的Spring6.2.0-SNAPSHOT版本中,一项引人注目的新功能被引入——Parallel Bean Initialization during Startup,即启动过程中的并行Bean初始化。此功能旨在显著提升Spring应用程序的启动速度,为开发者带来更为流畅的开发体验。

在传统的Spring应用程序中,Bean的初始化通常是按照特定的顺序进行的,这在一定程度上限制了启动过程的并行性,影响了启动速度。然而,在Spring6.2.0-SNAPSHOT版本中,通过引入并行Bean初始化功能,Spring框架能够同时初始化多个Bean,从而显著减少启动时间

此项功能实现基于精心设计的并发控制机制,确保Bean之间的依赖关系得到正确维护,同时最大限度地提高并行度。这意味着,即使存在复杂的Bean依赖关系,Spring也能够有效地管理并行初始化过程,避免潜在的初始化冲突或错误。

2. 实战案例

2.1 环境准备

static class PersonService {  public PersonService() {    try {      TimeUnit.SECONDS.sleep(3) ;    } catch (InterruptedException e) {}    System.out.println("PersonService init ...") ;  }}static class CommonService {  public CommonService() {    try {      TimeUnit.SECONDS.sleep(3) ;    } catch (InterruptedException e) {}    System.out.println("CommonService init ...") ;  }}

在上面的两个Service的构造方法中,分别模拟了耗时的操作。

@Configurationstatic class AppConfig {  @Bean  public PersonService personService() {    return new PersonService() ;  }  @Bean  public CommonService commonService() {    return new CommonService() ;  }}

2.2 传统Bean初始化

StopWatch watch = new StopWatch("初始化容器") ;watch.start() ;try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)) {}watch.stop();System.out.println(watch.prettyPrint()) ;

输出结果

PersonService init ...CommonService init ...StopWatch '初始化容器': 6.3145531 seconds----------------------------------------Seconds       %       Task name----------------------------------------6.3145531     100%

整体耗时:6.3s。

2.3 并发Bean初始化

这里首先需要修改@Bean定义配置

// 设置Bean的初始化在后台运行@Bean(bootstrap = Bootstrap.BACKGROUND)public PersonService personService() {  return new PersonService() ;}@Bean(bootstrap = Bootstrap.BACKGROUND) public CommonService commonService() {  return new CommonService() ;}

接着测试,输出结果

日志提示:Bean "commonService"标记为后台初始化,但未配置引导执行器-回退到主线初始化。根据提示我们还需要配置一个Executor类型的Bean。

@Beanpublic Executor bootstrapExecutor() {  ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();  taskExecutor.setCorePoolSize(5) ;  taskExecutor.setMaxPoolSize(5) ;  taskExecutor.initialize() ;  return taskExecutor ;}

接着测试,输出结果

CommonService init ...PersonService init ...StopWatch '初始化容器': 3.3203919 seconds----------------------------------------Seconds       %       Task name----------------------------------------3.3203919     100%

整体耗时:3.3s,说明我们Bean的初始化分别在不同的线程中执行。

这样一来,如果你项目中如果有很多比较耗时的操作,那么通过异步线程的方式,那将节省很多的时间。

注意:上面的示例是通过注解的方式定义bean,如果你使用BeanDefinition方式定义,那么你需要做如下配置

try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {  context.register(AppConfig.class) ;  context.registerBean(PersonDAO.class, bd -> {    if (bd instanceof AnnotatedGenericBeanDefinition bean) {      bean.setBackgroundInit(true) ;    }  }) ;  context.refresh() ;}

以上是关于Spring6.2中如何配置Bean的并行处理。

3. 实现原理

Spring容器启动和兴方法refresh。

public abstract class AbstractApplicationContext {  public void refresh() {    finishBeanFactoryInitialization(beanFactory);  }  protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {    // 初始化bootstrap executor    // 判断容器是否存在bootstrapExecutor为名的Bean,并且类型是Executor    if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) &&        beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) {      // 设置到当前的BeanFactory中,后面实例化时使用      beanFactory.setBootstrapExecutor(        beanFactory.getBean(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)      );    }    // ...    // 实例化非延迟初始化单例Bean    beanFactory.preInstantiateSingletons();  }}

实例化单例Bean

public class DefaultListableBeanFactory {  public void preInstantiateSingletons() {    List<CompletableFuture<?>> futures = new ArrayList<>();    for (String beanName : beanNames) {      RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);      if (!mbd.isAbstract() && mbd.isSingleton()) {        // 实例化Bean        CompletableFuture<?> future = preInstantiateSingleton(beanName, mbd);        if (future != null) {          futures.add(future);        }      }    }    // 等待所有的bean都完成    CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();  }  private CompletableFuture<?> preInstantiateSingleton(String beanName, RootBeanDefinition mbd) {    // 是否支持后台运行    if (mbd.isBackgroundInit()) {      Executor executor = getBootstrapExecutor();      if (executor != null) {        String[] dependsOn = mbd.getDependsOn();        if (dependsOn != null) {          for (String dep : dependsOn) {            getBean(dep);          }        }        CompletableFuture<?> future = CompletableFuture.runAsync(            () -> instantiateSingletonInBackgroundThread(beanName), executor);        addSingletonFactory(beanName, () -> {          try {            future.join();          }          return future;         });        return (!mbd.isLazyInit() ? future : null);      }    }    // 回退到main线程执行    if (!mbd.isLazyInit()) {      instantiateSingleton(beanName);    }    return null;  }}

整体的处理方式还是比较简单的。

以上是本篇文章的全部内容,如对你有帮助就请作者吃个棒棒糖🍭

完毕!!!

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832