==>>Gitee地址<<== :gitee.com/mr_wenpan/b…

  • 由于项目发动时间较长,导致每次发布很慢,经过剖析下来是在发动进程中经过 @PostConstruct注解或指定bean初始化办法而且在初始化办法内加载数据到本地缓存或拉取长途装备导致的,所以亟需处理掉这些发动耗时的操作。
  • 可是这些发动时初始化的动作又是必要的,无法在运用发动好后再进行,所以不能直接去掉。所以想到了经过异步初始化的方式来处理
  • 最近翻看sofa的文档,发现供给了一个运用发动进程中异步初始化的功用,所以将该功用进行收拾和抽取为独自模块,完美的完成了将发动进程中耗时的操作进行异步化
  • 当然这仅仅发动进程加速的一种方式,还有其他的加速方式欢迎弥补

一、处理的痛点

在实际运用 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