加速spring/springboot应用启动速度详解

 更新时间:2023年07月31日 10:07:30   作者:wen-pan  
这篇文章主要介绍了加速spring/springboot应用启动速度详解,本文通过实例代码给大家介绍的非常详细对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

Gitee地址 :https://gitee.com/mr_wenpan/basis-enhance/tree/master

一、解决的痛点

在实际使用 Spring/Spring Boot 开发中,一些 Bean 在初始化过程中,会执行一些准备操作,如:

  • 拉取远程配置
  • 初始化数据源
  • 加载数据到jvm本地缓存

在应用启动期间,这些 Bean 会增加 Spring 上下文刷新时间,导致应用启动耗时变长。为了加速应用启动,enhance-boot-async-init模块提供了通过配置的可选项,将 Bean 的初始化方法(init-method)使用单独线程异步执行的能力,加快 Spring 上下文加载过程,提高应用启动速度。仅需要将 @BasisAsyncInit注解标注到需要异步执行的初始化方法上即可实现应用启动过程中异步初始化。

加粗样式## 二、异步初始化组件说明

  • 需要显示的将@BasisAsyncInit注解标注在某些需要在启动过程中异步初始化的方法上才能实现启动过程中异步执行。自动探测所有可异步初始化的功能待实现(因为要进行依赖关系分析,比如在执行异步初始化某个类时,该类依赖关系非常复杂,并且可能他所依赖的某些bean还为被spring 容器创建,那么此时就需要去创建依赖的bean,且分析依赖树。这个后面实现)
  • 异步初始化时需要满足如下条件才能算是安全的稳定的异步初始化
    • 所有异步初始化完成之前容器不能算是启动成功。所以在容器启动完成前需要检查是否所有的异步初始化都执行完毕了
    • 如果有某个异步初始化执行失败了,则不能让容器完成启动,而是要阻止容器启动(如果异步初始化失败了,但此时容器启动完成了,比如:将restful接口对外暴露了,那么将会导致调用失败)
    • 异步启动的线程池可根据项目的需要动态配置
  • @BasisAsyncInit 注解可以使用在类上,也可以配合@Bean注解标注在注入方法上,eg:
// 在注入HelloService处标注@BasisAsyncInit注解(此时HelloService上可以不标注)
@BasisAsyncInit
@Bean(initMethod = "init")
public HelloService helloService() {
  return new HelloService();
}
// 或者直接标注在类上
@BasisAsyncInit
public class HelloService {
}

二、使用

1、yml配置文件中开启异步初始化

basis:
  enhance:
    async:
      init:
        # 开启异步初始化功能
        enable: true
        # 异步初始化线程池核心线程数量(不配置的话默认是 2*cpu核数 + 1)
        asyncInitBeanCoreSize: 10
        # 异步初始化线程池最大线程数量(不配置的话默认是 2*cpu核数 + 1)
        asyncInitBeanMaxSize: 20

2、创建异步初始化的bean

@Slf4j
// 炸裂标注该bean是异步初始化的
@BasisAsyncInit
public class HelloService {
    /**
     * init方法
     */
    public void init() throws InterruptedException {
        log.info("i am HelloService.init method, start........");
      // 睡一会儿,这里模拟初始化方法非常耗时,初始化方法执行完毕前容器不允许正常启动成功
        TimeUnit.SECONDS.sleep(10);
      // 这里模拟异步初始化失败容器不允许正常启动
//        final int i = 1 / 0;
        log.info("i am HelloService.init method, end........");
    }
    public String sayHello(String name) {
        log.info("hello {}", name);
        return "hello-" + name;
    }
}

3、注入异步初始化的bean并指定初始化方法

@Bean(initMethod = "init")
public HelloService helloService() {
    return new HelloService();
}

4、启动应用观察日志

①、正常启动日志

  • 可以看到HelloService的init方法是被异步线程pool-1-thread-1执行的,而不是main线程
  • 可以看到main线程一直是等到异步线程pool-1-thread-1执行完毕后再让容器启动成功的

2023-07-30 16:09:29.238  INFO 24094 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 711 ms
2023-07-30 16:09:29.307  INFO 24094 --- [           main] o.b.e.a.init.executor.AsyncTaskExecutor  : create async-init-bean thread pool, corePoolSize: 10, maxPoolSize: 20.
2023-07-30 16:09:29.308  INFO 24094 --- [pool-1-thread-1] o.e.async.init.service.HelloService      : i am HelloService.init method, start........
2023-07-30 16:09:29.541  INFO 24094 --- [           main] io.undertow                              : starting server: Undertow - 2.2.8.Final
2023-07-30 16:09:29.545  INFO 24094 --- [           main] org.xnio                                 : XNIO version 3.8.0.Final
2023-07-30 16:09:29.549  INFO 24094 --- [           main] org.xnio.nio                             : XNIO NIO Implementation Version 3.8.0.Final
2023-07-30 16:09:29.578  INFO 24094 --- [           main] org.jboss.threads                        : JBoss Threads version 3.1.0.Final
2023-07-30 16:09:29.623  INFO 24094 --- [           main] o.s.b.w.e.undertow.UndertowWebServer     : Undertow started on port(s) 8080 (http)
2023-07-30 16:09:39.310  INFO 24094 --- [pool-1-thread-1] o.e.async.init.service.HelloService      : i am HelloService.init method, end........
2023-07-30 16:09:39.311  INFO 24094 --- [pool-1-thread-1] o.b.e.a.i.p.AsyncProxyBeanPostProcessor  : org.enhance.async.init.service.HelloService(helloService) init method execute 10003 ms.
2023-07-30 16:09:39.321  INFO 24094 --- [           main] o.e.async.init.DemoAsyncInitApplication  : Started DemoAsyncInitApplication in 11.168 seconds (JVM running for 11.853)

②、初始化方法执行异常日志

  • 可以看到初始化方法异步执行失败,容器不会启动成功,不会将服务暴露出去

2023-07-30 16:12:30.000  INFO 24127 --- [           main] o.e.async.init.DemoAsyncInitApplication  : Starting DemoAsyncInitApplication using Java 1.8.0_281 on wenpf-MacBook-Pro.local with PID 24127 (/Users/wenpanfeng/MyFolder/DevelopWorkspace/IdeaWorkSpace/StudyProjects/basis-enhance/enhance-demo-parent/demo-enhance-async-init/target/classes started by wenpanfeng in /Users/wenpanfeng/MyFolder/DevelopWorkspace/IdeaWorkSpace/StudyProjects/basis-enhance)
2023-07-30 16:12:30.002  INFO 24127 --- [           main] o.e.async.init.DemoAsyncInitApplication  : No active profile set, falling back to default profiles: default
2023-07-30 16:12:30.763  WARN 24127 --- [           main] io.undertow.websockets.jsr               : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
2023-07-30 16:12:30.779  INFO 24127 --- [           main] io.undertow.servlet                      : Initializing Spring embedded WebApplicationContext
2023-07-30 16:12:30.779  INFO 24127 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 731 ms
2023-07-30 16:12:30.860  INFO 24127 --- [           main] o.b.e.a.init.executor.AsyncTaskExecutor  : create async-init-bean thread pool, corePoolSize: 10, maxPoolSize: 20.
2023-07-30 16:12:30.861  INFO 24127 --- [pool-1-thread-1] o.e.async.init.service.HelloService      : i am HelloService.init method, start........
2023-07-30 16:12:31.109  INFO 24127 --- [           main] io.undertow                              : starting server: Undertow - 2.2.8.Final
2023-07-30 16:12:31.114  INFO 24127 --- [           main] org.xnio                                 : XNIO version 3.8.0.Final
2023-07-30 16:12:31.119  INFO 24127 --- [           main] org.xnio.nio                             : XNIO NIO Implementation Version 3.8.0.Final
2023-07-30 16:12:31.151  INFO 24127 --- [           main] org.jboss.threads                        : JBoss Threads version 3.1.0.Final
2023-07-30 16:12:31.197  INFO 24127 --- [           main] o.s.b.w.e.undertow.UndertowWebServer     : Undertow started on port(s) 8080 (http)
2023-07-30 16:12:40.867  INFO 24127 --- [           main] io.undertow                              : stopping server: Undertow - 2.2.8.Final
2023-07-30 16:12:40.879  INFO 24127 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-07-30 16:12:40.899 ERROR 24127 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.basis.enhance.async.init.exception.BasisAsyncInitException: 异步初始化失败.
    at org.basis.enhance.async.init.executor.AsyncTaskExecutor.ensureAsyncTasksFinish(AsyncTaskExecutor.java:94) ~[classes/:na]
    at org.basis.enhance.async.init.listener.AsyncTaskExecutionListener.onApplicationEvent(AsyncTaskExecutionListener.java:28) ~[classes/:na]
    at org.basis.enhance.async.init.listener.AsyncTaskExecutionListener.onApplicationEvent(AsyncTaskExecutionListener.java:20) ~[classes/:na]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.8.jar:5.3.8]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144) ~[spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:771) [spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:763) [spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) [spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:339) [spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1329) [spring-boot-2.4.8.jar:2.4.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1318) [spring-boot-2.4.8.jar:2.4.8]
    at org.enhance.async.init.DemoAsyncInitApplication.main(DemoAsyncInitApplication.java:15) [classes/:na]
Caused by: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:1.8.0_281]
    at java.util.concurrent.FutureTask.get(FutureTask.java:192) ~[na:1.8.0_281]
    at org.basis.enhance.async.init.executor.AsyncTaskExecutor.ensureAsyncTasksFinish(AsyncTaskExecutor.java:92) ~[classes/:na]
    ... 17 common frames omitted
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor$AsyncInitializeBeanMethodInvoker.lambda$invoke$0(AsyncProxyBeanPostProcessor.java:119) ~[classes/:na]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_281]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_281]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_281]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_281]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_281]
Caused by: java.lang.reflect.InvocationTargetException: null
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_281]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_281]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_281]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_281]
    at org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor$AsyncInitializeBeanMethodInvoker.lambda$invoke$0(AsyncProxyBeanPostProcessor.java:115) ~[classes/:na]
    ... 5 common frames omitted
Caused by: java.lang.ArithmeticException: / by zero
    at org.enhance.async.init.service.HelloService.init(HelloService.java:25) ~[classes/:na]
    ... 10 common frames omitted

三、原理/源码浅析

  • 使用实现了BeanFactoryPostProcessor接口的 AsyncInitBeanFactoryPostProcessor 类,在bean定义信息创建完成后会调postProcessBeanFactory方法的特性,在该方法中扫描容器中每个bean定义信息
  • 在解析bean定义信息时,如果发现某个bean标注了@BasisAsyncInit注解,则查找该bean的init方法,并以beanId为key,初始化方法名称为value,将初始化方法保存到map中(ASYNC_BEAN_INFO_CACHE),后续统一处理
    • 源码 @see org.basis.enhance.async.init.processor.AsyncInitBeanFactoryPostProcessor#postProcessBeanFactory
  • 再借助实现了BeanPostProcessor接口的AsyncProxyBeanPostProcessor类,拦截spring中每个bean的创建过程(bean初始化方法执行前,see: postProcessBeforeInitialization),通过beanName名称去ASYNC_BEAN_INFO_CACHE缓存中查找该bean是否有需要异步初始化的方法。
    • 如果没有找到,则说明该bean没有需要异步初始化的方法,直接返回这个bean即可
    • 如果找到了,则在这里拦截该bean的创建(spring中常用的代理对象创建拦截点),为该bean创建一个代理对象(代理对象的核心逻辑AsyncInitializeBeanMethodInvoker),拦截该bean的每一个方法并返回。
    • 源码 @see org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor#postProcessBeforeInitialization
  • 当容器启动过程中执行bean的init方法时,此时如果是需要异步执行的初始化方法,则会进入到我们上一步创建的代理对象的invoke方法内。
    • 在该方法中将异步初始化方法提交到线程池中执行,提交完成后会返回一个 Future
    • 并且利用 Future 特性,将submit后返回的Future放到一个list (FUTURES)中统一管理
    • @see org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor.AsyncInitializeBeanMethodInvoker#invoke
    • 那么容器启动过程中如何感知异步初始化方法执行的结果呢?比如:是否都执行完毕了?是否有异常?往下看
  • 利用实现了ApplicationListener 接口的AsyncTaskExecutionListener类,监听容器启动过程中发布的的刷新事件ContextRefreshedEvent,在监听到容器启动过程中发布的 ContextRefreshedEvent 事件后,检查提交的每一个异步任务的执行情况(利用上一步submit后返回的Future)
    • 如果有任意一个异步初始化方法执行异常,则抛出异常,终止容器继续启动
    • 如果所有的异步初始化方法都执行完毕,则容器继续启动
    • @see org.basis.enhance.async.init.listener.AsyncTaskExecutionListener#onApplicationEvent

到此这篇关于如何加速spring/springboot应用启动速度的文章就介绍到这了,更多相关springboot启动速度内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现秒表功能

    java实现秒表功能

    这篇文章主要为大家详细介绍了java实现秒表功能,利用javax.swing.Timer类设计实现秒表应用程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Spring整合Struts2的两种方法小结

    Spring整合Struts2的两种方法小结

    下面小编就为大家带来一篇Spring整合Struts2的两种方法小结。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Java滚动数组计算编辑距离操作示例

    Java滚动数组计算编辑距离操作示例

    这篇文章主要介绍了Java滚动数组计算编辑距离操作,涉及java字符串与数组的遍历、计算、转换等相关操作技巧,需要的朋友可以参考下
    2019-12-12
  • IDEA创建Java Web项目的超详细图文教学

    IDEA创建Java Web项目的超详细图文教学

    IDEA是程序员们常用的java集成开发环境,也是被公认为最好用的java开发工具,下面这篇文章主要给大家介绍了关于IDEA创建Java Web项目的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • springboot默认文件缓存(easy-captcha 验证码)

    springboot默认文件缓存(easy-captcha 验证码)

    这篇文章主要介绍了springboot的文件缓存(easy-captcha 验证码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • zookeeper+Springboot实现服务器动态上下线监听教程详解

    zookeeper+Springboot实现服务器动态上下线监听教程详解

    这篇文章主要介绍了zookeeper+Springboot实现服务器动态上下线监听,主要介绍了什么是服务器动态上下线监听及为什么要实现对服务器上下线的监听,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • Java编程中的HashSet和BitSet详解

    Java编程中的HashSet和BitSet详解

    这篇文章主要介绍了Java编程中的HashSet和BitSet详解的相关资料,需要的朋友可以参考下
    2017-03-03
  • SSH框架网上商城项目第28战之使用Ajax技术局部更新商品数量和总价

    SSH框架网上商城项目第28战之使用Ajax技术局部更新商品数量和总价

    这篇文章主要为大家详细介绍了SSH框架网上商城项目第28战之使用Ajax技术局部更新商品数量和总价,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • java 实现线程同步的方式有哪些

    java 实现线程同步的方式有哪些

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题,所以我们用同步机制来解决这些问题,本文将详细介绍,需要的朋友可以参考下
    2012-11-11
  • SpringBoot基于HttpMessageConverter实现全局日期格式化

    SpringBoot基于HttpMessageConverter实现全局日期格式化

    这篇文章主要介绍了SpringBoot基于HttpMessageConverter实现全局日期格式化,使用Jackson消息转换器,非常具有实用价值,需要的朋友可以参考下
    2018-12-12

最新评论