Spring中@Async注解執行異步任務
https://segmentfault.com/a/1190000015190901
引言
在業務處理中,有些業務使用異步的方式更為合理。比如在某個業務邏輯中,把一些數據存入到redis緩存中,緩存只是一個輔助的功能,成功或者失敗對主業務並不會產生根本影響,這個過程可以通過異步的方法去進行。
Spring中通過在方法上設置@Async
注解,可使得方法被異步調用。也就是說該方法會在調用時立即返回,而這個方法的實際執行交給Spring的TaskExecutor去完成。
代碼示例
項目是一個普通的Spring的項目,Spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 包掃描 --> <context:component-scan base-package="com.lzumetal.ssm"/> <!-- 執行異步任務的線程池TaskExecutor --> <task:executor id="myexecutor" pool-size="5" /> <task:annotation-driven executor="myexecutor"/> </beans>
兩個Service類:
package com.lzumetal.ssm.anotation.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * 業務Service */ @Service public class BusinessService { private static final Logger log = LoggerFactory.getLogger(BusinessService.class); @Autowired private CacheService cacheService; public void doBusiness() { log.error("start to deal with our business"); cacheService.cacheData(); log.error("comlete service operation"); } /** * 獲取異步方法執行的返回值 */ public void doBusinessWithAsyncReturn() throws ExecutionException, InterruptedException { log.error("start to deal with our business"); Future<String> future = cacheService.cacheDataWithReturn(); log.error(future.get()); //future.get()方法是會阻塞的 log.error("comlete service operation"); } }
package com.lzumetal.ssm.anotation.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * 緩存服務 */ @Service public class CacheService { private static final Logger log = LoggerFactory.getLogger(CacheService.class); @Async(value = "myexecutor") //指定執行任務的TaskExecutor public void cacheData() { try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } log.error("success store the result to cache"); } @Async public Future<String> cacheDataWithReturn() { try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } log.error("success store the result to cache"); //返回的結果需要通過AsyncResult這個類包裝 return new AsyncResult<>("Async operation success"); } }
測試類:
package com.lzumetal.ssm.anotation.test; import com.lzumetal.ssm.anotation.service.BusinessService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.concurrent.TimeUnit; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-context.xml"}) public class MainTest { @Autowired private BusinessService businessService; @Test public void test() throws InterruptedException { businessService.doBusiness(); //不讓主線程過早結束,否則控制台看不到異步方法中的輸出內容 TimeUnit.SECONDS.sleep(5L); } @Test public void testAsyncReturn() throws Exception { businessService.doBusinessWithAsyncReturn(); TimeUnit.SECONDS.sleep(5L); } }
執行test()
方法的結果:
22:20:33,207 INFO main support.DefaultTestContextBootstrapper:260 - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] 22:20:33,226 INFO main support.DefaultTestContextBootstrapper:209 - Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext] 22:20:33,227 INFO main support.DefaultTestContextBootstrapper:187 - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@100fc185, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@643b1d11, org.springframework.test.context.support.DirtiesContextTestExecutionListener@2ef5e5e3, org.springframework.test.context.transaction.TransactionalTestExecutionListener@36d4b5c, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@6d00a15d]22:20:33,324 INFO main xml.XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [spring-context.xml] 22:20:33,585 INFO main support.GenericApplicationContext:583 - Refreshing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 22:20:33 CST 2018]; root of context hierarchy 22:20:33,763 INFO main concurrent.ThreadPoolTaskExecutor:165 - Initializing ExecutorService 22:20:33,766 INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.config.TaskExecutorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 22:20:33,767 INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 22:20:34,107 ERROR main service.BusinessService:24 - start to deal with our business 22:20:34,113 ERROR main service.BusinessService:26 - comlete service operation 22:20:37,166 ERROR myexecutor-1 service.CacheService:28 - success store the result to cache 22:20:39,117 INFO Thread-0 support.GenericApplicationContext:984 - Closing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 22:20:33 CST 2018]; root of context hierarchy 22:20:39,118 INFO Thread-0 concurrent.ThreadPoolTaskExecutor:203 - Shutting down ExecutorService
執行testAsyncReturn()
方法的結果:
21:38:16,908 INFO main support.DefaultTestContextBootstrapper:260 - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] 21:38:16,926 INFO main support.DefaultTestContextBootstrapper:209 - Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext] 21:38:16,927 INFO main support.DefaultTestContextBootstrapper:187 - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@100fc185, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@643b1d11, org.springframework.test.context.support.DirtiesContextTestExecutionListener@2ef5e5e3, org.springframework.test.context.transaction.TransactionalTestExecutionListener@36d4b5c, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@6d00a15d]21:38:17,025 INFO main xml.XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [spring-context.xml] 21:38:17,263 INFO main support.GenericApplicationContext:583 - Refreshing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 21:38:17 CST 2018]; root of context hierarchy 21:38:17,405 INFO main concurrent.ThreadPoolTaskExecutor:165 - Initializing ExecutorService 21:38:17,407 INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.config.TaskExecutorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 21:38:17,407 INFO main support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325 - Bean 'myexecutor' of type [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 21:38:17,692 ERROR main service.BusinessService:35 - start to deal with our business 21:38:20,833 ERROR myexecutor-1 service.CacheService:39 - success store the result to cache 21:38:20,834 ERROR main service.BusinessService:37 - Async operation success 21:38:20,835 ERROR main service.BusinessService:38 - comlete service operation 21:38:25,838 INFO Thread-0 support.GenericApplicationContext:984 - Closing org.springframework.context.support.GenericApplicationContext@4f7d0008: startup date [Wed May 30 21:38:17 CST 2018]; root of context hierarchy 21:38:25,839 INFO Thread-0 concurrent.ThreadPoolTaskExecutor:203 - Shutting down ExecutorService
@Async的使用注意點
- 返回值:不要返回值直接void;需要返回值用AsyncResult或者CompletableFuture
- 可自定義執行器並指定例如:@Async("otherExecutor")
@Async
必須不同類間調用: A類—>B類.C方法()(@Async
注釋在B類/方法中),如果在同一個類中調用,會變同步執行,例如:A類.B()—>A類.@Async C()。- @Async也可以加到類,表示這個類的所有方法都是異步執行,並且方法上的注解會覆蓋類上的注解。但一般不這么用!

@Async Could not find unique TaskExecutor bean; NoUniqueBeanDefinitionException;
異步任務,項目啟動或接口調用,控制台報錯:Could not find unique TaskExecutor bean;
或者報錯:org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available: expected single matching bean but found 2: applicationTaskExecutor,taskScheduler
原因為:@Async所注解的方法存在相同的方法名,或者同一個異步任務被多個任務同時調用
解決方法:
1、將@Async所注解的方法名修改為不同的方法名
2、增加異步任務配置類:
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; /** * @description 異步任務配置類,為防止異步任務如下錯誤: * No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available: * expected single matching bean but found 2: applicationTaskExecutor,taskScheduler * @author: chenping * @create: 2020-01-17 **/ @Configuration @ComponentScan("cn.com.*.*.*.service")//異步任務所在的包 @EnableAsync //開啟異步任務支持 public class TaskExecutorConfig implements AsyncConfigurer { /** * @description: 實現AsyncConfigurer接口並重寫getAsyncExecutor方法, * 並返回一個ThreadPoolTaskExecutor,這樣我們就獲得了一個基於線程池TaskExecutor * @param: * @return: java.util.concurrent.Executor * @author: chenping * @date: 2020/1/17 **/ @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(10); taskExecutor.setMaxPoolSize(80); taskExecutor.setQueueCapacity(100); taskExecutor.initialize(); return taskExecutor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; } }