Springboot事件監(jiān)聽與@Async注解詳解
一、不求甚解
在開發(fā)中經(jīng)??梢岳肧pring事件監(jiān)聽來實(shí)現(xiàn)觀察者模式,進(jìn)行一些非事務(wù)性的操作,如記錄日志之類的。
Controller
Controller中注入ApplicationEventPublisher,并利用它發(fā)布事件即可。
import org.springframework.context.ApplicationEventPublisher; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/controller") public class TestController { @Resource private ApplicationEventPublisher applicationEventPublisher; @GetMapping("/test") public String test(@RequestParam String name){ System.out.println("請(qǐng)求進(jìn)來了"); TestEvent event = new TestEvent(this,name); applicationEventPublisher.publishEvent(event); return "success"; } }
Event
event繼承ApplicationEvent即可。
import org.springframework.context.ApplicationEvent; public class TestEvent extends ApplicationEvent { private String name; public TestEvent(Object source,String name) { super(source); this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Listener
Listener實(shí)現(xiàn)ApplicationListener接口即可。或者使用@EventListener注解
接口方式
import org.springframework.context.ApplicationListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @Async public class TestListener implements ApplicationListener<TestEvent> { @Override public void onApplicationEvent(TestEvent testEvent) { System.out.println(testEvent.getName()); } }
注解方式
import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @Async public class TestListener { @EventListener public void onApplicationEvent(TestEvent testEvent) { System.out.println("TestListener:"+Thread.currentThread().getName()); System.out.println("TestListener:"+testEvent.getName()); } }
Postman測試 //localhost:8080/controller/test?name=lisi
控制臺(tái)打?。?/p>
請(qǐng)求進(jìn)來了
lisi
曾經(jīng),我一直以為這樣就實(shí)現(xiàn)了異步,因?yàn)槲铱垂镜拇a都是這樣寫的,現(xiàn)網(wǎng)也沒什么問題。每次照著前輩們的代碼CV一下就可以了,也沒太多想。
二、人云亦云
@Async注解
以前一直用@Async注解,但我好像沒有去深入了解過她。使用她就像使用@Resource一樣自然一樣隨心所欲。
直到某天閑了下來,突發(fā)奇想,想要深入了解一下她。
@Async注解很容易踩坑,首先是必須在啟動(dòng)類上開啟異步配置@EnableAsync才行,否則還是同步執(zhí)行。如果不指定線程池,則使用Spring默認(rèn)的線程池 SimpleAsyncTaskExecutor。
方法上一旦標(biāo)記了@Async注解,當(dāng)其它線程調(diào)用這個(gè)方法時(shí),就會(huì)開啟一個(gè)新的子線程去異步處理該業(yè)務(wù)邏輯。
1)在方法上使用該@Async注解,申明該方法是一個(gè)異步任務(wù);在類上面使用該@Async注解,申明該類中的所有方法都是異步任務(wù);
2)使用此注解的方法的類對(duì)象,必須是Spring管理下的bean對(duì)象; 所以需要在類上加上@Component注解。
3)要想使用異步任務(wù),需要在主類上開啟異步配置,即,配置上@EnableAsync注解;
4)如果不指定線程池的名稱,則使用Spring默認(rèn)的線程池SimpleAsyncTaskExecutor。
SimpleAsyncTaskExecutor特性:
- 1)為每個(gè)任務(wù)啟動(dòng)一個(gè)新線程,異步執(zhí)行它。
- 2)支持通過“concurrencyLimit” bean 屬性限制并發(fā)線程。默認(rèn)情況下,并發(fā)線程數(shù)是無限的。
- 3)注意:此實(shí)現(xiàn)不重用線程!
- 4)考慮一個(gè)線程池 TaskExecutor 實(shí)現(xiàn),特別是用于執(zhí)行大量短期任務(wù)。
以上內(nèi)容是我百度后所得,到底對(duì)不對(duì)呢?我決定親自驗(yàn)證一下。
所以,使用@Async注解的時(shí)候一定要指定自己的線程池?。。?/p>
在啟動(dòng)類沒有指定注解@EnableAsync的情況下,測試一下打印出當(dāng)前的線程
@GetMapping("/test") public String test(@RequestParam String name){ System.out.println("TestController請(qǐng)求進(jìn)來了:"+Thread.currentThread().getName()); TestEvent event = new TestEvent(this,name); applicationEventPublisher.publishEvent(event); System.out.println("TestController請(qǐng)求出去了:"+Thread.currentThread().getName()); return "success"; }
@Override public void onApplicationEvent(TestEvent testEvent) { System.out.println("TestListener:"+Thread.currentThread().getName()); System.out.println("TestListener:"+testEvent.getName()); }
根據(jù)上述結(jié)果可證明在沒有開啟異步配置@EnableAsync的情況下還是同步執(zhí)行!
三、刨根問底
Springboot中異步默認(rèn)使用的線程池真的是SimpleAsyncTaskExecutor嗎???
在不指定線程池的情況下,debug查看spring中異步默認(rèn)的線程池。
開啟異步 在啟動(dòng)類上添加該注解 @EnableAsync
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
獲取spring管理的bean實(shí)例,實(shí)現(xiàn)這個(gè)接口即可: ApplicationContextAware
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.CustomizableThreadCreator; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/controller") public class TestController implements ApplicationContextAware { private static ApplicationContext applicationContext; @Resource private ApplicationEventPublisher applicationEventPublisher; @GetMapping("/test") public String test(@RequestParam String name){ System.out.println("TestController請(qǐng)求進(jìn)來了:"+Thread.currentThread().getName()); TestEvent event = new TestEvent(this,name); // 獲取spring管理的所有的bean的名稱 String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (int i = 0; i <beanDefinitionNames.length ; i++) { System.out.println(beanDefinitionNames[i]); } applicationEventPublisher.publishEvent(event); System.out.println("TestController請(qǐng)求出去了:"+Thread.currentThread().getName()); // 根據(jù)class對(duì)象獲取spring管理的bean實(shí)例 CustomizableThreadCreator bean = applicationContext.getBean(CustomizableThreadCreator.class); System.out.println("CustomizableThreadCreator:"+bean.getThreadNamePrefix()); return "success"; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { TestController.applicationContext=applicationContext; } }
在TestListener中打上斷點(diǎn)。
當(dāng)前spring版本為5.2.12,可能與spring版本有關(guān),當(dāng)前版本的線程池默認(rèn)是ThreadPoolTaskExecutor??梢钥吹骄€程池最大容量是Integer的最大值,在高并發(fā)場景下可能導(dǎo)致OOM。因此需要自定義線程池,并在@Async上指定為自定義線程池。
打印出了spring中所管理的bean的名稱,applicationTaskExecutor果然也在。
打印出線程池的前綴,再一次驗(yàn)證確認(rèn)線程池默認(rèn)是ThreadPoolTaskExecutor。
四、曲突徙薪
最近寫了一個(gè)接口,要求響應(yīng)時(shí)間1s以內(nèi),并且請(qǐng)求量很大,明確要求一些非重要業(yè)務(wù)的操作都必須異步操作。
按著以前的套路,我還是照著上面一操作了一遍。還好今天研究了一下,否則以后的某天怕是要背一口大鍋。
保持好奇,富有探索,始終對(duì)技術(shù)有敏感性?。?!
自定義線程池
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration public class MyExecutorConfig { @Bean("MyExecutor") public Executor myExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(2000); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("myExecutor"); executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
在異步注解上標(biāo)明使用自定義線程池 @Async(“MyExecutor”)
Jmeter壓測
壓測結(jié)果表明確實(shí)切換了線程池,使用了自定義的線程池,并且阻塞隊(duì)列已滿,開始朝著最大線程數(shù)邁進(jìn)!
到此這篇關(guān)于Springboot事件監(jiān)聽與@Async注解詳解的文章就介紹到這了,更多相關(guān)Springboot監(jiān)聽與@Async內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot 事件監(jiān)聽的實(shí)現(xiàn)方法
- SpringBoot Application事件監(jiān)聽的實(shí)現(xiàn)方案
- springboot+redis過期事件監(jiān)聽實(shí)現(xiàn)過程解析
- SpringBoot加載應(yīng)用事件監(jiān)聽器代碼實(shí)例
- SpringBoot監(jiān)聽事件和處理事件程序示例詳解
- SpringBoot利用切面注解及反射實(shí)現(xiàn)事件監(jiān)聽功能
- SpringBoot ApplicationListener事件監(jiān)聽接口使用問題探究
- SpringBoot中的ApplicationListener事件監(jiān)聽器使用詳解
- Java?Springboot異步執(zhí)行事件監(jiān)聽和處理實(shí)例
- SpringBoot實(shí)現(xiàn)事件監(jiān)聽(異步執(zhí)行)的示例代碼
相關(guān)文章
你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)
這篇文章主要為大家詳細(xì)介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02Java利用Picocli開發(fā)一個(gè)簡化命令行工具
Picocli 是一個(gè)強(qiáng)大、易用且功能豐富的 Java 庫,用于開發(fā)命令行工具,本文我們就來為大家介紹一下Java如何利用Picocli進(jìn)行命令行簡化功能的吧2025-03-03詳解如何保護(hù)SpringBoot配置文件中的敏感信息
使用過SpringBoot配置文件的朋友都知道,資源文件中的內(nèi)容通常情況下是明文顯示,安全性就比較低一些,所以為了提高安全性,就需要對(duì)配置文件中的敏感信息進(jìn)行保護(hù),下面就為大家介紹一下實(shí)現(xiàn)方法吧2023-07-07Spring數(shù)據(jù)庫連接池實(shí)現(xiàn)原理深入刨析
開發(fā)web項(xiàng)目,我們肯定會(huì)和數(shù)據(jù)庫打交道,因此就會(huì)涉及到數(shù)據(jù)庫鏈接的問題。在以前我們開發(fā)傳統(tǒng)的SSM結(jié)構(gòu)的項(xiàng)目時(shí)進(jìn)行數(shù)據(jù)庫鏈接都是通過JDBC進(jìn)行數(shù)據(jù)鏈接,我們每和數(shù)據(jù)庫打一次交道都需要先獲取一次鏈接,操作完后再關(guān)閉鏈接,這樣子效率很低,因此就出現(xiàn)了連接池2022-11-11MultipartFile中transferTo(File file)的路徑問題及解決
這篇文章主要介紹了MultipartFile中transferTo(File file)的路徑問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Springboot項(xiàng)目對(duì)數(shù)據(jù)庫用戶名密碼實(shí)現(xiàn)加密過程解析
這篇文章主要介紹了Springboot項(xiàng)目對(duì)數(shù)據(jù)庫用戶名密碼實(shí)現(xiàn)加密過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06java實(shí)現(xiàn)賬號(hào)登錄時(shí)發(fā)送郵件通知
這篇文章主要為大家詳細(xì)介紹了java如何實(shí)現(xiàn)在賬號(hào)登錄時(shí)發(fā)送郵件通知的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09springboot3.X版本集成mybatis遇到的問題及解決
在將SpringBoot3.X版本與MyBatis集成時(shí),直接參考基于SpringBoot2.X的配置方法會(huì)導(dǎo)致各種報(bào)錯(cuò),尤其是無法注入mapper的bean問題,這主要是因?yàn)镾pringBoot3.X版本需要搭配MyBatis3.0.3及以上版本才能正常工作,通過更新maven配置至MyBatis3.0.3版本,可以解決這一問題2024-09-09