SpringBoot中@EnableAsync和@Async注解的使用小結(jié)
異步的優(yōu)點(diǎn):
- 提高應(yīng)用程序的響應(yīng)能力
- 提高系統(tǒng)的吞吐量
- 節(jié)約資源:異步操作可以避免在請(qǐng)求處理期間占用過多的線程資源,減少服務(wù)器的負(fù)載。
- 優(yōu)化用戶體驗(yàn)
需要注意的問題:
- 事務(wù)問題:異步處理時(shí),需要注意在事務(wù)沒有結(jié)束時(shí)做異步操作,可能會(huì)導(dǎo)致讀取不到甚至覆蓋事務(wù)中新增或更新的數(shù)據(jù)內(nèi)容。
在 Spring Boot 中,可以通過 @EnableAsync 注解來啟動(dòng)異步方法調(diào)用的支持,通過 @Async 注解來標(biāo)識(shí)異步方法,讓方法能夠在異步線程中執(zhí)行。下面分別介紹它們的使用方法。
1.@EnableAsync 注解
@EnableAsync是一個(gè) Spring Boot 中用于啟動(dòng)異步方法調(diào)用的注解。使用@EnableAsync注解時(shí),需要將其放置在一個(gè)配置類上,并且在配置類中通過@Bean方法創(chuàng)建一個(gè)線程池。
下面舉個(gè)例子:
1.1 配置類使用示例
AsyncTaskExecutorConfig 類通過 @EnableAsync 注解來啟用異步方法調(diào)用,然后在配置類中通過 @Bean 方法創(chuàng)建了一個(gè)名為 asyncExecutor 的線程池,用于執(zhí)行異步方法。
import com.demo.async.ContextCopyingDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* <p> @Title AsyncTaskExecutorConfig
* <p> @Description 異步線程池配置
*
* @author ACGkaka
* @date 2023/4/24 19:48
*/
@EnableAsync
@Configuration
public class AsyncTaskExecutorConfig {
/**
* 核心線程數(shù)(線程池維護(hù)線程的最小數(shù)量)
*/
private int corePoolSize = 10;
/**
* 最大線程數(shù)(線程池維護(hù)線程的最大數(shù)量)
*/
private int maxPoolSize = 200;
/**
* 隊(duì)列最大長度
*/
private int queueCapacity = 10;
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("MyExecutor-");
// for passing in request scope context 轉(zhuǎn)換請(qǐng)求范圍的上下文
executor.setTaskDecorator(new ContextCopyingDecorator());
// rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù)
// CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
1.2 復(fù)制請(qǐng)求上下文
ContextCopyingDecorator 類使用了裝飾者模式,用于將主線程中的請(qǐng)求上下文拷貝到異步子線程中,并且在異步子線程執(zhí)行之后清空請(qǐng)求的上下文。
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.Map;
/**
* <p> @Title ContextCopyingDecorator
* <p> @Description 上下文拷貝裝飾者模式
*
* @author ACGkaka
* @date 2023/4/24 20:20
*/
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
try {
// 從父線程中獲取上下文,然后應(yīng)用到子線程中
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
Map<String, String> previous = MDC.getCopyOfContextMap();
SecurityContext securityContext = SecurityContextHolder.getContext();
return () -> {
try {
if (previous == null) {
MDC.clear();
} else {
MDC.setContextMap(previous);
}
RequestContextHolder.setRequestAttributes(requestAttributes);
SecurityContextHolder.setContext(securityContext);
runnable.run();
} finally {
// 清除請(qǐng)求數(shù)據(jù)
MDC.clear();
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
}
};
} catch (IllegalStateException e) {
return runnable;
}
}
}
2.用法1:@Async 注解
@Async注解是一個(gè) Spring Boot 中用于標(biāo)識(shí)異步方法的注解,通過在方法上添加@Async注解,可以讓該方法在異步線程中執(zhí)行。
下面舉個(gè)例子:
2.1 測試Controller
DemoController 類中聲明了 /demo/test 接口,接口中調(diào)用了 demoService.testError() 方法。
import com.demo.common.Result;
import com.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p> @Title DemoController
* <p> @Description 測試Controller
*
* @author ACGkaka
* @date 2023/4/24 18:02
*/
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DemoService demoService;
@RequestMapping("/test")
public Result<Object> test() {
Result<Object> result = Result.succeed();
System.out.println("start...");
demoService.testError();
System.out.println("end...");
return result;
}
}
2.2 測試Service
/**
* <p> @Title DemoService
* <p> @Description 測試Service
*
* @author ACGkaka
* @date 2023/4/24 18:13
*/
public interface DemoService {
/**
* 測試異常
*/
void testError() throws InterruptedException;
}
2.3 測試ServiceImpl
DemoServiceImpl 類使用了 @Async 注解,用于異步調(diào)用,testError() 方法中拋出了異常,用于測試異步執(zhí)行。
這里 @Async 注解的 value 值指定了我們?cè)谂渲妙愔新暶鞯?nbsp;taskExecutor 線程池。
- 假如只配置了一個(gè)線程池,直接用
@Async注解就會(huì)用自定義的線程池執(zhí)行。 - 假如配置了多個(gè)線程池,用
@Async("name")來指定使用哪個(gè)線程池,如果沒有指定,會(huì)用默認(rèn)的 SimpleAsyncTaskExecutor 來處理。
import com.demo.service.DemoService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* <p> @Title DemoServiceImpl
* <p> @Description 測試ServiceImpl
*
* @author ACGkaka
* @date 2023/4/24 18:14
*/
@Service
public class DemoServiceImpl implements DemoService {
@Async("taskExecutor")
@Override
public void testError() throws InterruptedException {
throw new RuntimeException("測試異常");
}
}
2.4.測試
訪問接口:http://localhost:8080/demo/test
訪問結(jié)果如下,可見異常并沒有接口返回正常的結(jié)果,異步測試成功。

3.用法2:直接使用 taskExecutor 做異步
由于我們?cè)诘?步中,將異步線程池注入到了 taskExecutor Bean 容器中,我們就可以直接通過 @Autowired 或者 @Resource 獲取到線程池,然后使用。
我們通過直接使用 taskExecutor 線程池的方式,重新實(shí)現(xiàn) DemoServiceImpl.java:
3.1 重新實(shí)現(xiàn):測試ServiceImpl
import com.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
/**
* <p> @Title DemoServiceImpl
* <p> @Description 測試ServiceImpl
*
* @author ACGkaka
* @date 2023/4/24 18:14
*/
@Service
public class DemoServiceImpl implements DemoService {
@Qualifier("taskExecutor")
@Autowired
private TaskExecutor taskExecutor;
@Override
public void testError() {
taskExecutor.execute(() -> {
throw new RuntimeException("測試異常");
});
}
}
3.2 測試
訪問接口:http://localhost:8080/demo/test
訪問結(jié)果如下,可見異常并沒有接口返回正常的結(jié)果,異步測試成功,直接使用線程池的方式也可行。


4.@Async異步不生效原因
1)@SpringBootApplication 啟動(dòng)類或配置類當(dāng)中沒有添加 @EnableAsync 注解。
(補(bǔ)充:項(xiàng)目中除了啟動(dòng)類和配置類外,任何一個(gè)注入到 Bean 容器中的類添加 @EnableAsync 注解都可以,但是規(guī)范用法是在啟動(dòng)類和配置類中添加注解。)
2)異步方法使用注解@Async的返回值只能為void或者Future。
3)異步方法不能使用static修飾
4)異步類沒有使用 @Component 注解(或其他注解)導(dǎo)致spring無法掃描到異步類
5)異步方法不能與異步方法在同一個(gè)類中
6)類中需要使用@Autowired或@Resource等注解自動(dòng)注入,不能自己手動(dòng)new對(duì)象
7)在 @Async 方法上標(biāo)注 @Transactional 是沒用的。 在 @Async 方法調(diào)用的方法上標(biāo)注@Transactional 有效。
8)調(diào)用被 @Async 標(biāo)記的方法的調(diào)用者不能和被調(diào)用的方法在同一類中不然不會(huì)起作用?。。。。。。?/p>
9)使用 @Async 時(shí)是獲取不到方法的返回值的,拿到的值為 null,如果返回的值是原始類型int、double、long等(不能為 null),就會(huì)報(bào)錯(cuò)。

5.補(bǔ)充:使用@Async后項(xiàng)目啟動(dòng)報(bào)Bean注入異常
使用 @Async 后項(xiàng)目啟動(dòng)報(bào)Bean注入異常,提示 in its raw version as part of a circular reference, but has eventually been wrap。
詳細(xì)報(bào)錯(cuò)信息如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userInfoServiceImpl': Bean with name 'userInfoServiceImpl' has been injected into other beans [authServiceImpl, loginLogServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:623)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1307)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
... 74 common frames omittedDisconnected from the target VM, address: '127.0.0.1:61563', transport: 'socket'
Process finished with exit code 1
主要是因?yàn)楸?nbsp;@Async 修飾后,項(xiàng)目啟動(dòng)時(shí)會(huì)生成一個(gè)代理對(duì)象,這個(gè)代理對(duì)象產(chǎn)生的實(shí)例和 Spring 注解的實(shí)例不一致,就會(huì)拋出這個(gè)異常??梢試L試使用 @Lazy 注解通過懶加載的方式進(jìn)行修復(fù),或者直接使用自定義線程池的方式進(jìn)行異步操作。
整理完畢,完結(jié)撒花~ ??
參考地址:
2.spring boot- @EnableAsync和@Async(Spring boot 注解@Async不生效 無效 不起作用)
到此這篇關(guān)于SpringBoot中@EnableAsync和@Async注解的使用小結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot @EnableAsync和@Async內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MybatisPlus 插入或更新數(shù)據(jù)時(shí)自動(dòng)填充更新數(shù)據(jù)解決方案
本文主要介紹了MybatisPlus 插入或更新數(shù)據(jù)時(shí)自動(dòng)填充更新數(shù)據(jù)解決方案,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Spring Boot中利用JavaMailSender發(fā)送郵件的方法示例(附源碼)
這篇文章主要介紹了Spring Boot中利用JavaMailSender發(fā)送郵件的方法示例, 相信使用過Spring的眾多開發(fā)者都知道Spring提供了非常好用的JavaMailSender接口實(shí)現(xiàn)郵件發(fā)送。在Spring Boot的Starter模塊中也為此提供了自動(dòng)化配置。需要的朋友可以參考借鑒。2017-02-02
Spring常用注解及http數(shù)據(jù)轉(zhuǎn)換教程
這篇文章主要為大家介紹了Spring常用注解及http數(shù)據(jù)轉(zhuǎn)換原理以及接收復(fù)雜嵌套對(duì)象參數(shù)與Http數(shù)據(jù)轉(zhuǎn)換的原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
Java?RAG集成實(shí)戰(zhàn)指南(含代碼)
在這個(gè)背景下,大模型JavaAPI和RAG技術(shù)逐漸嶄露頭角,成為軟件開發(fā)者們關(guān)注的焦點(diǎn),下面這篇文章主要介紹了Java RAG集成的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-04-04
JavaWeb如何實(shí)現(xiàn)限制單個(gè)賬號(hào)多處登錄
這篇文章主要介紹了JavaWeb如何實(shí)現(xiàn)限制單個(gè)賬號(hào)多處登錄問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
如何解決java獲取時(shí)間相差8小時(shí)的問題
最近使用new date()獲取的時(shí)間會(huì)和真實(shí)的本地時(shí)間相差8小時(shí)。本文就詳細(xì)的來介紹一下解決java獲取時(shí)間相差8小時(shí)的問題,感興趣的可以了解一下2021-09-09
Josephus環(huán)的四種解法(約瑟夫環(huán))基于java詳解
這篇文章主要介紹了Josephus環(huán)的四種解法(約瑟夫環(huán))基于java詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09

