Gitee地址 :https://gitee.com/mr_wenpan/basis-enhance/tree/master
在实际使用 Spring/Spring Boot
开发中,一些 Bean 在初始化过程中,会执行一些准备操作,如:
在应用启动期间,这些 Bean 会增加 Spring 上下文刷新时间,导致应用启动耗时变长。为了加速应用启动,enhance-boot-async-init
模块提供了通过配置的可选项,将 Bean 的初始化方法(init-method
)使用单独线程异步执行的能力,加快 Spring 上下文加载过程,提高应用启动速度。仅需要将 @BasisAsyncInit
注解标注到需要异步执行的初始化方法上即可实现应用启动过程中异步初始化。
加粗样式## 二、异步初始化组件说明
需要显示的将@BasisAsyncInit
注解标注在某些需要在启动过程中异步初始化的方法上才能实现启动过程中异步执行。自动探测所有可异步初始化的功能待实现(因为要进行依赖关系分析,比如在执行异步初始化某个类时,该类依赖关系非常复杂,并且可能他所依赖的某些bean还为被spring 容器创建,那么此时就需要去创建依赖的bean,且分析依赖树。这个后面实现)
异步初始化时需要满足如下条件才能算是安全的稳定的异步初始化
@BasisAsyncInit
注解可以使用在类上,也可以配合@Bean注解标注在注入方法上,eg:
// 在注入HelloService处标注@BasisAsyncInit注解(此时HelloService上可以不标注)
@BasisAsyncInit
@Bean(initMethod = "init")
public HelloService helloService() {
return new HelloService();
}
// 或者直接标注在类上
@BasisAsyncInit
public class HelloService {
}
basis:
enhance:
async:
init:
# 开启异步初始化功能
enable: true
# 异步初始化线程池核心线程数量(不配置的话默认是 2*cpu核数 + 1)
asyncInitBeanCoreSize: 10
# 异步初始化线程池最大线程数量(不配置的话默认是 2*cpu核数 + 1)
asyncInitBeanMaxSize: 20
@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;
}
}
@Bean(initMethod = "init")
public HelloService helloService() {
return new HelloService();
}
pool-1-thread-1
执行的,而不是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定义信息@BasisAsyncInit
注解,则查找该bean的init方法,并以beanId为key,初始化方法名称为value,将初始化方法保存到map中(ASYNC_BEAN_INFO_CACHE
),后续统一处理
org.basis.enhance.async.init.processor.AsyncInitBeanFactoryPostProcessor#postProcessBeanFactory
BeanPostProcessor
接口的AsyncProxyBeanPostProcessor
类,拦截spring中每个bean的创建过程(bean初始化方法执行前,see: postProcessBeforeInitialization
),通过beanName
名称去ASYNC_BEAN_INFO_CACHE
缓存中查找该bean是否有需要异步初始化的方法。
AsyncInitializeBeanMethodInvoker
),拦截该bean的每一个方法并返回。org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor#postProcessBeforeInitialization
Future
Future
特性,将submit后返回的Future放到一个list (FUTURES
)中统一管理org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor.AsyncInitializeBeanMethodInvoker#invoke
ApplicationListener
接口的AsyncTaskExecutionListener
类,监听容器启动过程中发布的的刷新事件ContextRefreshedEvent
,在监听到容器启动过程中发布的 ContextRefreshedEvent 事件后,检查提交的每一个异步任务的执行情况(利用上一步submit后返回的Future)
org.basis.enhance.async.init.listener.AsyncTaskExecutionListener#onApplicationEvent