前端的寶寶會用ajax,用異步編程到快樂的不行~ 咱們java也有異步,用起來比他們還快樂~ 咱們bia~ji~一個注(gǒupí)解(gāoyào)
,也是快樂風男...前端
註冊一個用戶,給他的帳戶初始化積分(也能夠想象成註冊獎勵),再給用戶發個註冊通知短信,再發個郵件,(只是舉栗子,切莫牛角大法
),這樣一個流程,短信和郵件我以爲徹底能夠拆分出來,不必拖在在主流程上來(再補充上事務[ACID:原子性,一致性,隔離性,持久性
]就行了): java
個人首發原創博客地址:你的@Async就真的異步嗎 ☞ 異步歷險奇遇記 裏面有gitHub項目地址,關注我,裏面實戰多多哦~git
看code:ajax
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserService userService;
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
userService.senMsg(user);
userService.senEmail(user);
return insert;
}
@Async
@Override
public Boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
public Boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
複製代碼
結果:啓動不起來,Spring循環依賴問題。 Spring不是解決了循環依賴問題嗎,它是支持循環依賴的呀?怎麼會呢?spring
不能否認,在這以前我也是這麼堅信的,假若你目前也和我有同樣堅挺的想法,那就讓異常UnsatisfiedDependencyException
,has been injected into other beans [userServiceImpl] in its raw version as part of a circular reference,
,來鼓勵你,擁抱你, 就是這麼的不給面子,赤裸裸的circular reference
。數據庫
談到Spring Bean的循環依賴,有的小夥伴可能比較陌生,畢竟開發過程當中好像對循環依賴這個概念無感知。其實否則,你有這種錯覺,那是由於你工做在Spring的襁褓中,從而讓你「高枕無憂」~ 其實咱們的代碼中確定被咱們寫了循環依賴,好比像這樣:編程
@Service
public class AServiceImpl implements AService {
@Autowired
private BService bService;
...
}
@Service
public class BServiceImpl implements BService {
@Autowired
private AService aService;
...
}
複製代碼
經過實驗總結出,出現使用@Async致使循環依賴問題的必要條件:springboot
- 已開啓@EnableAsync的支持
- @Async註解所在的Bean被循環依賴了
那麼既然不能循環依賴,咱們就不循環依賴,咱們這麼來:bash
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
SendService sendService;
@Override
@Transactional()
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
this.senMsg(user);
this.senEmail(user);
return insert;
}
@Async
@Override
public Boolean senMsg(User user) {
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
public Boolean senEmail(User user) {
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
複製代碼
結果咱們測試了幾把,我打印一下結果:併發
2019-08-05 21:59:32.304 INFO 14360 --- [nio-8080-exec-3] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2019-08-05 21:59:32.346 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Preparing: insert into t_user (username, sex, mobile,email) values (?, ?, ?,?)
2019-08-05 21:59:32.454 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Parameters: 王麻子(String), 男(String), 18820158833(String), 22qq@qq.com(String)
2019-08-05 21:59:32.463 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : <== Updates: 1
User 保存用戶成功:User(id=101, username=王麻子, mobile=18820158833, email=22qq@qq.com, sex=男, password=123435, createTime=Mon Aug 05 12:20:51 CST 2019, updateTime=null)
發送短信中:.....
http-nio-8080-exec-3給用戶id:101,手機號:18820158833發送短信成功
發送郵件中:.....
http-nio-8080-exec-3給用戶id:101,郵箱:22qq@qq.com發送郵件成功
複製代碼
這不白瞎了嗎?感知不到個人愛,白寫了,難受~~線程依然是http-nio-8080-exec-3
,那麼爲何了呢? 下面會講的哦,先說結論:
經過實驗總結出,出現使用@Async致使異步失效的緣由:
- 在本類中使用了異步是不支持異步的
- 調用者實際上是this,是當前對象,不是真正的代理對象
userService
,spring沒法截獲這個方法調用 因此不在不在本類中去調用,網上的解決方法有applicationContext.getBean(UserService.class)
和AopContext.currentProxy()
那麼,既然不能用當前對象,那咱們用代理,AopContext.currentProxy()
,然鵝,你發現,報錯了,對Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
就他:
@EnableAspectJAutoProxy(exposeProxy = true)
複製代碼
我也這麼搞,可是又報錯了,細細的看報錯內容,才發現少了個jar包這東西要配合切面織入,要配合,懂嗎?,來上包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
複製代碼
再來看爲撒: 這是一個性感的話題,exposeProxy = true
它的做用就是啓用切面的自動代理,說人話就是暴露當前代理對象到當前線程綁定, 看個報錯Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
就是AopContext搞得鬼.
public final class AopContext {
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
private AopContext() {
}
// 該方法是public static方法,說明能夠被任意類進行調用
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
// 它拋出異常的緣由是當前線程並無綁定對象
// 而給線程綁定對象的方法在下面:特別有意思的是它的訪問權限是default級別,也就是說只能Spring內部去調用
if (proxy == null) {
throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
// 它最有意思的地方是它的訪問權限是default的,表示只能給Spring內部去調用
// 調用它的類有CglibAopProxy和JdkDynamicAopProxy
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
} else {
currentProxy.remove();
}
return old;
}
}
複製代碼
因此咱們要作啓用代理設置,讓代理生效,來走起,主線程的方法使用來調用異步方法,來測試走起: no code said niao:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.senMsg(user);
currentProxy.senEmail(user);
int i = 1 / 0;
return insert;
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senMsg(User user) {
user.setUsername(Thread.currentThread().getName()+"發短信測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senEmail(User user) {
user.setUsername("發郵件測試事務...."+ new Random().nextInt());
userMapper.insert(user);
int i = 1 / 0;
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
}
}
複製代碼
測試結果:
1. 異步線程SimpleAsyncTaskExecutor-1和SimpleAsyncTaskExecutor-2分別發短信和郵件,主線程保存用戶
2. 實際結果,主線程保存的那個用戶失敗,名字叫'發郵件'的也保存失敗,只有叫'發送短信'的用戶插入成功
3. 那麼就作到了事務的線程隔離,事務的互不影響,完美
4. 親,你這麼寫了嗎?這麼寫不優美,雖然有用,可是寫的另闢蹊徑啊
複製代碼
來,咱們看個更騷氣的,異步中嵌套異步,來上code:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.send(user);
return insert;
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void send(User user) {
//發短信
user.setUsername(Thread.currentThread().getName()+"發短信測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
//發郵件
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.senEmail(user);
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senEmail(User user) {
user.setUsername("發郵件測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
}
}
複製代碼
看咱們猜下結果? 數據庫會新增幾個數據?3個?2個?1個?0個?納尼報錯?
哈哈``` 上結果:
Set 'exposeProxy' property on Advised to 'true'
磨人的小妖精
經過實驗總結出,出現致使異步嵌套使用失敗的緣由:
- 在本類中使用了異步嵌套異步是不支持的
- 調用者其實被代理過一次了,再嵌套會出現'二次代理',實際上是達不到代理了效果,由於已經異步了.即時你在嵌套中不使用代理去獲取,即便不保存,可是事務和異步效果都會跟隨當前的代理,即嵌套的效果是達不到再次異步的.
- 解決辦法應該有,可是我以爲我還沒找到.這個寫法是咱們應該規避的,咱們應該遵循規範,啓用新的服務類去完成咱們的異步工做
下面咱們舉個栗子:正確的寫法,優雅的寫法
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
SendService sendService;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
sendService.senMsg(user);
sendService.senEmail(user);
return insert;
}
}
---------------無責任分割線--------------------
@Service
public class SendServiceImpl implements SendService {
@Override
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
}
複製代碼
結果確定完美:
所以當你看到你同事就在本類寫個方法標註上@Async而後調用,請制止他吧,作的無用功~~~(關鍵本身還覺得有用,這是最可怕的深坑~)
那我補充點: @EnableAspectJAutoProxy(exposeProxy = true)
的做用: 此註解它導入了AspectJAutoProxyRegistrar,最終設置此註解的兩個屬性的方法爲:
public abstract class AopConfigUtils {
...正在加(sheng)載(lue)代碼中 請稍後....
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
}
複製代碼
看到此註解標註的屬性值最終都被設置到了internalAutoProxyCreator
身上,也就是它:自動代理建立器。 首先咱們須要明晰的是:@Async的代理對象並非由自動代理建立器來建立的,而是由AsyncAnnotationBeanPostProcessor
一個單純的BeanPostProcessor
實現的,很顯然當執行AopContext.currentProxy()這句代碼的時候報錯了。 @EnableAsync
給容器注入的是AsyncAnnotationBeanPostProcessor
,它用於給@Async
生成代理,可是它僅僅是個BeanPostProcessor
並不屬於自動代理建立器,所以exposeProxy = true
對它無效。 因此AopContext.setCurrentProxy(proxy);
這個set
方法確定就不會執行,因此,所以,但凡只要業務方法中調用AopContext.currentProxy()
方法就鐵定拋異常~~
看嘛,發短信實際上是一些網關調用,我想寫個看短信,郵件發送成功的標誌,是否調用成功的狀態,來走起
....省略...UserService
---------------無責任分割線--------------------
@Service
public class SendServiceImpl implements SendService {
@Override
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
}
複製代碼
瞪大眼睛看,個人返回結果是boolean,屬於基本類型,雖然沒有用,可是報錯了:
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for:
public boolean com.boot.lea.mybot.service.impl.SendServiceImpl.senMsg(com.boot.lea.mybot.entity.User)
複製代碼
致使個人數據庫一條數據都沒有,影響到主線程了,可見問題發生在主線程觸發異步線程的時候,那咱們找緣由: 是走代理觸發的:我先找這個類 CglibAopProxy
再順藤摸瓜
/** * Process a return value. Wraps a return of {@code this} if necessary to be the * {@code proxy} and also verifies that {@code null} is not returned as a primitive. */
private static Object processReturnType(Object proxy, Object target, Method method, Object retVal) {
// Massage return value if necessary
if (retVal != null && retVal == target &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this". Note that we can't help
// if the target sets a reference to itself in another returned object.
retVal = proxy;
}
Class<?> returnType = method.getReturnType();
if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
複製代碼
在這retVal == null && returnType != Void.TYPE && returnType.isPrimitive()
,由於咱們的這種異步實際上是不支持友好的返回結果的,咱們的結果應該是void,由於這個異步線程被主線程觸發後其實被當作一個任務提交到Spring的異步的一個線程池中進行異步的處理任務了,線程之間的通訊是不能之間返回的,其實用這種寫法咱們就應該用void去異步執行,不要有返回值,並且咱們的返回值是isPrimitive()
,是基本類型,恰好達標....
那麼我大聲喊出,使用異步的時候儘可能不要有返回值,實在要有你也不能用基本類型.
有些人就是難受,就是想要返回結果,那麼也是能夠滴:可是要藉助Furtrue
小姐姐的get()
來進行線程之間的阻塞通訊,畢竟小姐姐⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄害羞.
醬紫寫,你就能夠阻塞等到執行任務有結果的時候去獲取真正的結果了,這個寫法和我以前的文章 JAVA併發異步編程 原來十個接口的活如今只須要一個接口就搞定!是同樣的道理了
import com.boot.lea.mybot.service.AsyncService;
import com.boot.lea.mybot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
@Service
@Async("taskExecutor")
public class AsyncServiceImpl implements AsyncService {
@Autowired
private UserService userService;
@Override
public Future<Long> queryUserMsgCount(final Long userId) {
System.out.println("當前線程:" + Thread.currentThread().getName() + "=-=====queryUserMsgCount");
long countByUserId = userService.countMsgCountByUserId(userId);
return new AsyncResult<>(countByUserId);
}
@Override
public Future<Long> queryCollectCount(final Long userId) {
System.out.println("當前線程:" + Thread.currentThread().getName() + "=-====queryCollectCount");
long collectCount = userService.countCollectCountByUserId(userId);
return new AsyncResult<>(collectCount);
}
複製代碼
@Async的異步:
奸笑...嘻嘻嘻嘻...
)。SpringBoot環境中,要使用@Async註解,咱們須要先在啓動類上加上@EnableAsync註解。這個與在SpringBoot中使用@Scheduled註解須要在啓動類中加上@EnableScheduling是同樣的道理(固然你使用古老的XML配置也是能夠的,可是在SpringBoot環境中,建議的是全註解開發),具體原理下面會分析。加上@EnableAsync註解後,若是咱們想在調用一個方法的時候開啓一個新的線程開始異步操做,咱們只須要在這個方法上加上@Async註解,固然前提是,這個方法所在的類必須在Spring環境中。
示例:非spingboot項目
<task:annotation-driven executor="annotationExecutor" />
<!-- 支持 @Async 註解 -->
<task:executor id="annotationExecutor" pool-size="20"/>
複製代碼
執行流程:
EnableAsync
,@EnableAsync註解上有個@Import(AsyncConfigurationSelector.class)
,springboot的注入老套路了AsyncConfigurationSelector
,看到selectImports
方法了沒,這裏使用的是默認使用的是ProxyAsyncConfiguration
這個配置類ProxyAsyncConfiguration
繼承AbstractAsyncConfiguration
,它裏面的的setConfigurers
說明了咱們能夠經過實現AsyncConfigurer
接口來完成線程池以及異常處理器的配置,並且在Spring環境中只能配置一個實現類,不然會拋出異常。 上一點代碼:/** * Collect any {@link AsyncConfigurer} beans through autowiring. */
@Autowired(required = false)
void setConfigurers(Collection<AsyncConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
//AsyncConfigurer用來配置線程池配置以及異常處理器,並且在Spring環境中最多隻能有一個,在這裏咱們知道了,若是想要本身去配置線程池,只須要實現AsyncConfigurer接口,而且不能夠在Spring環境中有多個實現AsyncConfigurer的類。
if (configurers.size() > 1) {
throw new IllegalStateException("Only one AsyncConfigurer may exist");
}
AsyncConfigurer configurer = configurers.iterator().next();
this.executor = configurer.getAsyncExecutor();
this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();
}
複製代碼
ProxyAsyncConfiguration
注入的bean AsyncAnnotationBeanPostProcessor
,這個BeanPostBeanPostProcessor
很顯然會對帶有可以引起異步操做的註解(好比@Async
)的Bean進行處理AsyncAnnotationBeanPostProcessor
有重寫父類的setBeanFactory
,這個方法是否是有點熟悉呢,它是BeanFactoryAware
接口中的方法,AsyncAnnotationBeanPostProcessor
的父類實現了這個接口,在咱們好久以前分析過的Bean的初始化中,是有提到過這個接口的,實現了Aware類型接口的Bean,會在初始化Bean的時候調用相應的初始化方法,具體能夠查看AbstractAutowireCapableBeanFactory#initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)
方法postProcessAfterInitialization
方法在祖先類AbstractAdvisingBeanPostProcessor
中。從源碼中能夠看到。AsyncAnnotationBeanPostProcessor
是對Bean進行後置處理的BeanPostProcessor
JdkDynamicAopProxy
的invoke方法中,是用了責任鏈模式:List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
,將代理進行攔截來執行,通知鏈會包含setBeanFactory()方法生成的通知,執行鏈會用於建立ReflectiveMethodInvocation
對象,最終是調用ReflectiveMethodInvocation
的proceed()
來完成對方法的加強處理,proceed()
方法在這裏會執行最後一個分支AsyncExecutionInterceptor
的invoke()
AsyncConfigurer
實現類,可是不意味着,在Spring環境中只能配置一個線程池,在Spring環境中是能夠配置多個線程池,並且咱們能夠在使用@Async註解進行異步操做的時候,經過在value屬性上指定線程池BeanName,這樣就能夠指定相應的線程池來做爲任務的載體,參見:determineAsyncExecutor
當咱們想要在SpringBoot中方便的使用@Async註解開啓異步操做的時候,只須要實現AsyncConfigurer接口(這樣就配置了默認線程池配置,固然該類須要在Spring環境中,由於是默認的,因此只能有一個,沒有多個實現類排優先級的說法),實現對線程池的配置,並在啓動類上加上@EnableAsync註解,便可使得@Async註解生效。
咱們甚至能夠不顯式的實現AsyncConfigurer,咱們能夠在Spring環境中配置多個Executor類型的Bean,在使用@Async註解時,將註解的value指定爲你Executor類型的BeanName,就可使用指定的線程池來做爲任務的載體,這樣就使用線程池也更加靈活。