加速spring/springboot應(yīng)用啟動(dòng)速度詳解
Gitee地址 :https://gitee.com/mr_wenpan/basis-enhance/tree/master
一、解決的痛點(diǎn)
在實(shí)際使用 Spring/Spring Boot
開發(fā)中,一些 Bean 在初始化過(guò)程中,會(huì)執(zhí)行一些準(zhǔn)備操作,如:
- 拉取遠(yuǎn)程配置
- 初始化數(shù)據(jù)源
- 加載數(shù)據(jù)到j(luò)vm本地緩存
在應(yīng)用啟動(dòng)期間,這些 Bean 會(huì)增加 Spring 上下文刷新時(shí)間,導(dǎo)致應(yīng)用啟動(dòng)耗時(shí)變長(zhǎng)。為了加速應(yīng)用啟動(dòng),enhance-boot-async-init
模塊提供了通過(guò)配置的可選項(xiàng),將 Bean 的初始化方法(init-method
)使用單獨(dú)線程異步執(zhí)行的能力,加快 Spring 上下文加載過(guò)程,提高應(yīng)用啟動(dòng)速度。僅需要將 @BasisAsyncInit
注解標(biāo)注到需要異步執(zhí)行的初始化方法上即可實(shí)現(xiàn)應(yīng)用啟動(dòng)過(guò)程中異步初始化。
加粗樣式## 二、異步初始化組件說(shuō)明
- 需要顯示的將
@BasisAsyncInit
注解標(biāo)注在某些需要在啟動(dòng)過(guò)程中異步初始化的方法上才能實(shí)現(xiàn)啟動(dòng)過(guò)程中異步執(zhí)行。自動(dòng)探測(cè)所有可異步初始化的功能待實(shí)現(xiàn)(因?yàn)橐M(jìn)行依賴關(guān)系分析,比如在執(zhí)行異步初始化某個(gè)類時(shí),該類依賴關(guān)系非常復(fù)雜,并且可能他所依賴的某些bean還為被spring 容器創(chuàng)建,那么此時(shí)就需要去創(chuàng)建依賴的bean,且分析依賴樹。這個(gè)后面實(shí)現(xiàn)) - 異步初始化時(shí)需要滿足如下條件才能算是安全的穩(wěn)定的異步初始化
- 所有異步初始化完成之前容器不能算是啟動(dòng)成功。所以在容器啟動(dòng)完成前需要檢查是否所有的異步初始化都執(zhí)行完畢了
- 如果有某個(gè)異步初始化執(zhí)行失敗了,則不能讓容器完成啟動(dòng),而是要阻止容器啟動(dòng)(如果異步初始化失敗了,但此時(shí)容器啟動(dòng)完成了,比如:將restful接口對(duì)外暴露了,那么將會(huì)導(dǎo)致調(diào)用失?。?/li>
- 異步啟動(dòng)的線程池可根據(jù)項(xiàng)目的需要?jiǎng)討B(tài)配置
@BasisAsyncInit
注解可以使用在類上,也可以配合@Bean注解標(biāo)注在注入方法上,eg:
// 在注入HelloService處標(biāo)注@BasisAsyncInit注解(此時(shí)HelloService上可以不標(biāo)注) @BasisAsyncInit @Bean(initMethod = "init") public HelloService helloService() { return new HelloService(); } // 或者直接標(biāo)注在類上 @BasisAsyncInit public class HelloService { }
二、使用
1、yml配置文件中開啟異步初始化
basis: enhance: async: init: # 開啟異步初始化功能 enable: true # 異步初始化線程池核心線程數(shù)量(不配置的話默認(rèn)是 2*cpu核數(shù) + 1) asyncInitBeanCoreSize: 10 # 異步初始化線程池最大線程數(shù)量(不配置的話默認(rèn)是 2*cpu核數(shù) + 1) asyncInitBeanMaxSize: 20
2、創(chuàng)建異步初始化的bean
@Slf4j // 炸裂標(biāo)注該bean是異步初始化的 @BasisAsyncInit public class HelloService { /** * init方法 */ public void init() throws InterruptedException { log.info("i am HelloService.init method, start........"); // 睡一會(huì)兒,這里模擬初始化方法非常耗時(shí),初始化方法執(zhí)行完畢前容器不允許正常啟動(dòng)成功 TimeUnit.SECONDS.sleep(10); // 這里模擬異步初始化失敗容器不允許正常啟動(dòng) // 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、啟動(dòng)應(yīng)用觀察日志
①、正常啟動(dòng)日志
- 可以看到HelloService的init方法是被異步線程
pool-1-thread-1
執(zhí)行的,而不是main線程 - 可以看到main線程一直是等到異步線程
pool-1-thread-1
執(zhí)行完畢后再讓容器啟動(dòng)成功的
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)
②、初始化方法執(zhí)行異常日志
- 可以看到初始化方法異步執(zhí)行失敗,容器不會(huì)啟動(dòng)成功,不會(huì)將服務(wù)暴露出去
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 failedorg.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
三、原理/源碼淺析
- 使用實(shí)現(xiàn)了BeanFactoryPostProcessor接口的 AsyncInitBeanFactoryPostProcessor 類,在bean定義信息創(chuàng)建完成后會(huì)調(diào)postProcessBeanFactory方法的特性,在該方法中掃描容器中每個(gè)bean定義信息
- 在解析bean定義信息時(shí),如果發(fā)現(xiàn)某個(gè)bean標(biāo)注了@BasisAsyncInit注解,則查找該bean的init方法,并以beanId為key,初始化方法名稱為value,將初始化方法保存到map中(ASYNC_BEAN_INFO_CACHE),后續(xù)統(tǒng)一處理
- 源碼 @see org.basis.enhance.async.init.processor.AsyncInitBeanFactoryPostProcessor#postProcessBeanFactory
- 再借助實(shí)現(xiàn)了BeanPostProcessor接口的AsyncProxyBeanPostProcessor類,攔截spring中每個(gè)bean的創(chuàng)建過(guò)程(bean初始化方法執(zhí)行前,see: postProcessBeforeInitialization),通過(guò)beanName名稱去ASYNC_BEAN_INFO_CACHE緩存中查找該bean是否有需要異步初始化的方法。
- 如果沒有找到,則說(shuō)明該bean沒有需要異步初始化的方法,直接返回這個(gè)bean即可
- 如果找到了,則在這里攔截該bean的創(chuàng)建(spring中常用的代理對(duì)象創(chuàng)建攔截點(diǎn)),為該bean創(chuàng)建一個(gè)代理對(duì)象(代理對(duì)象的核心邏輯AsyncInitializeBeanMethodInvoker),攔截該bean的每一個(gè)方法并返回。
- 源碼 @see org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor#postProcessBeforeInitialization
- 當(dāng)容器啟動(dòng)過(guò)程中執(zhí)行bean的init方法時(shí),此時(shí)如果是需要異步執(zhí)行的初始化方法,則會(huì)進(jìn)入到我們上一步創(chuàng)建的代理對(duì)象的invoke方法內(nèi)。
- 在該方法中將異步初始化方法提交到線程池中執(zhí)行,提交完成后會(huì)返回一個(gè) Future
- 并且利用 Future 特性,將submit后返回的Future放到一個(gè)list (FUTURES)中統(tǒng)一管理
- @see org.basis.enhance.async.init.processor.AsyncProxyBeanPostProcessor.AsyncInitializeBeanMethodInvoker#invoke
- 那么容器啟動(dòng)過(guò)程中如何感知異步初始化方法執(zhí)行的結(jié)果呢?比如:是否都執(zhí)行完畢了?是否有異常?往下看
- 利用實(shí)現(xiàn)了ApplicationListener 接口的AsyncTaskExecutionListener類,監(jiān)聽容器啟動(dòng)過(guò)程中發(fā)布的的刷新事件ContextRefreshedEvent,在監(jiān)聽到容器啟動(dòng)過(guò)程中發(fā)布的 ContextRefreshedEvent 事件后,檢查提交的每一個(gè)異步任務(wù)的執(zhí)行情況(利用上一步submit后返回的Future)
- 如果有任意一個(gè)異步初始化方法執(zhí)行異常,則拋出異常,終止容器繼續(xù)啟動(dòng)
- 如果所有的異步初始化方法都執(zhí)行完畢,則容器繼續(xù)啟動(dòng)
- @see org.basis.enhance.async.init.listener.AsyncTaskExecutionListener#onApplicationEvent
到此這篇關(guān)于如何加速spring/springboot應(yīng)用啟動(dòng)速度的文章就介紹到這了,更多相關(guān)springboot啟動(dòng)速度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java調(diào)用python代碼的五種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java調(diào)用python代碼的五種方式,在Java中調(diào)用Python函數(shù)的方法有很多種,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09java實(shí)現(xiàn)ip地址與十進(jìn)制數(shù)相互轉(zhuǎn)換
本文介紹在java中IP地址轉(zhuǎn)換十進(jìn)制數(shù)及把10進(jìn)制再轉(zhuǎn)換成IP地址的方法及實(shí)例參考,曬出來(lái)和大家分享一下2012-12-12java存儲(chǔ)以及java對(duì)象創(chuàng)建的流程(詳解)
下面小編就為大家?guī)?lái)一篇java存儲(chǔ)以及java對(duì)象創(chuàng)建的流程(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05Java中對(duì)list map根據(jù)map某個(gè)key值進(jìn)行排序的方法
今天小編就為大家分享一篇Java中對(duì)list map根據(jù)map某個(gè)key值進(jìn)行排序的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07java ThreadPool線程池的使用,線程池工具類用法說(shuō)明
這篇文章主要介紹了java ThreadPool線程池的使用,線程池工具類用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10