前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring6.2震撼来袭,多线程实例化Bean应用启动速度飙升!

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

作者头像
lyb-geek
发布2024-04-19 10:22:14
1050
发布2024-04-19 10:22:14
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

环境: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 环境准备

代码语言:javascript
复制
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的构造方法中,分别模拟了耗时的操作。

代码语言:javascript
复制
@Configuration
static class AppConfig {
  @Bean
  public PersonService personService() {
    return new PersonService() ;
  }
  @Bean
  public CommonService commonService() {
    return new CommonService() ;
  }
}

2.2 传统Bean初始化

代码语言:javascript
复制
StopWatch watch = new StopWatch("初始化容器") ;
watch.start() ;
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)) {
}
watch.stop();
System.out.println(watch.prettyPrint()) ;

输出结果

代码语言:javascript
复制
PersonService init ...
CommonService init ...
StopWatch '初始化容器': 6.3145531 seconds
----------------------------------------
Seconds       %       Task name
----------------------------------------
6.3145531     100%

整体耗时:6.3s。

2.3 并发Bean初始化

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

代码语言:javascript
复制
// 设置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。

代码语言:javascript
复制
@Bean
public Executor bootstrapExecutor() {
  ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
  taskExecutor.setCorePoolSize(5) ;
  taskExecutor.setMaxPoolSize(5) ;
  taskExecutor.initialize() ;
  return taskExecutor ;
}

接着测试,输出结果

代码语言:javascript
复制
CommonService init ...
PersonService init ...
StopWatch '初始化容器': 3.3203919 seconds
----------------------------------------
Seconds       %       Task name
----------------------------------------
3.3203919     100%

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

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

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
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

代码语言:javascript
复制
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;
  }
}

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

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

完毕!!!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档