Springboot事件監(jiān)聽與@Async注解詳解
一、不求甚解
在開發(fā)中經(jīng)常可以利用Spring事件監(jiān)聽來實現(xià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("請求進來了");
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實現(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

控制臺打?。?/p>
請求進來了
lisi
曾經(jīng),我一直以為這樣就實現(xiàn)了異步,因為我看公司的代碼都是這樣寫的,現(xiàn)網(wǎng)也沒什么問題。每次照著前輩們的代碼CV一下就可以了,也沒太多想。
二、人云亦云
@Async注解
以前一直用@Async注解,但我好像沒有去深入了解過她。使用她就像使用@Resource一樣自然一樣隨心所欲。
直到某天閑了下來,突發(fā)奇想,想要深入了解一下她。
@Async注解很容易踩坑,首先是必須在啟動類上開啟異步配置@EnableAsync才行,否則還是同步執(zhí)行。如果不指定線程池,則使用Spring默認的線程池 SimpleAsyncTaskExecutor。
方法上一旦標(biāo)記了@Async注解,當(dāng)其它線程調(diào)用這個方法時,就會開啟一個新的子線程去異步處理該業(yè)務(wù)邏輯。
1)在方法上使用該@Async注解,申明該方法是一個異步任務(wù);在類上面使用該@Async注解,申明該類中的所有方法都是異步任務(wù);
2)使用此注解的方法的類對象,必須是Spring管理下的bean對象; 所以需要在類上加上@Component注解。
3)要想使用異步任務(wù),需要在主類上開啟異步配置,即,配置上@EnableAsync注解;
4)如果不指定線程池的名稱,則使用Spring默認的線程池SimpleAsyncTaskExecutor。
SimpleAsyncTaskExecutor特性:
- 1)為每個任務(wù)啟動一個新線程,異步執(zhí)行它。
- 2)支持通過“concurrencyLimit” bean 屬性限制并發(fā)線程。默認情況下,并發(fā)線程數(shù)是無限的。
- 3)注意:此實現(xiàn)不重用線程!
- 4)考慮一個線程池 TaskExecutor 實現(xiàn),特別是用于執(zhí)行大量短期任務(wù)。
以上內(nèi)容是我百度后所得,到底對不對呢?我決定親自驗證一下。
所以,使用@Async注解的時候一定要指定自己的線程池?。?!
在啟動類沒有指定注解@EnableAsync的情況下,測試一下打印出當(dāng)前的線程
@GetMapping("/test")
public String test(@RequestParam String name){
System.out.println("TestController請求進來了:"+Thread.currentThread().getName());
TestEvent event = new TestEvent(this,name);
applicationEventPublisher.publishEvent(event);
System.out.println("TestController請求出去了:"+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中異步默認使用的線程池真的是SimpleAsyncTaskExecutor嗎???
在不指定線程池的情況下,debug查看spring中異步默認的線程池。
開啟異步 在啟動類上添加該注解 @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實例,實現(xiàn)這個接口即可: 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請求進來了:"+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請求出去了:"+Thread.currentThread().getName());
// 根據(jù)class對象獲取spring管理的bean實例
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中打上斷點。

當(dāng)前spring版本為5.2.12,可能與spring版本有關(guān),當(dāng)前版本的線程池默認是ThreadPoolTaskExecutor??梢钥吹骄€程池最大容量是Integer的最大值,在高并發(fā)場景下可能導(dǎo)致OOM。因此需要自定義線程池,并在@Async上指定為自定義線程池。
打印出了spring中所管理的bean的名稱,applicationTaskExecutor果然也在。

打印出線程池的前綴,再一次驗證確認線程池默認是ThreadPoolTaskExecutor。

四、曲突徙薪
最近寫了一個接口,要求響應(yīng)時間1s以內(nèi),并且請求量很大,明確要求一些非重要業(yè)務(wù)的操作都必須異步操作。
按著以前的套路,我還是照著上面一操作了一遍。還好今天研究了一下,否則以后的某天怕是要背一口大鍋。
保持好奇,富有探索,始終對技術(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ù)邁進!
到此這篇關(guān)于Springboot事件監(jiān)聽與@Async注解詳解的文章就介紹到這了,更多相關(guān)Springboot監(jiān)聽與@Async內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot 事件監(jiān)聽的實現(xiàn)方法
- SpringBoot Application事件監(jiān)聽的實現(xiàn)方案
- springboot+redis過期事件監(jiān)聽實現(xiàn)過程解析
- SpringBoot加載應(yīng)用事件監(jiān)聽器代碼實例
- SpringBoot監(jiān)聽事件和處理事件程序示例詳解
- SpringBoot利用切面注解及反射實現(xiàn)事件監(jiān)聽功能
- SpringBoot ApplicationListener事件監(jiān)聽接口使用問題探究
- SpringBoot中的ApplicationListener事件監(jiān)聽器使用詳解
- Java?Springboot異步執(zhí)行事件監(jiān)聽和處理實例
- SpringBoot實現(xiàn)事件監(jiān)聽(異步執(zhí)行)的示例代碼
相關(guān)文章
你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)
這篇文章主要為大家詳細介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
Spring數(shù)據(jù)庫連接池實現(xiàn)原理深入刨析
開發(fā)web項目,我們肯定會和數(shù)據(jù)庫打交道,因此就會涉及到數(shù)據(jù)庫鏈接的問題。在以前我們開發(fā)傳統(tǒng)的SSM結(jié)構(gòu)的項目時進行數(shù)據(jù)庫鏈接都是通過JDBC進行數(shù)據(jù)鏈接,我們每和數(shù)據(jù)庫打一次交道都需要先獲取一次鏈接,操作完后再關(guān)閉鏈接,這樣子效率很低,因此就出現(xiàn)了連接池2022-11-11
MultipartFile中transferTo(File file)的路徑問題及解決
這篇文章主要介紹了MultipartFile中transferTo(File file)的路徑問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Springboot項目對數(shù)據(jù)庫用戶名密碼實現(xiàn)加密過程解析
這篇文章主要介紹了Springboot項目對數(shù)據(jù)庫用戶名密碼實現(xiàn)加密過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06
springboot3.X版本集成mybatis遇到的問題及解決
在將SpringBoot3.X版本與MyBatis集成時,直接參考基于SpringBoot2.X的配置方法會導(dǎo)致各種報錯,尤其是無法注入mapper的bean問題,這主要是因為SpringBoot3.X版本需要搭配MyBatis3.0.3及以上版本才能正常工作,通過更新maven配置至MyBatis3.0.3版本,可以解決這一問題2024-09-09

