@Async注解加上之后,是否真实现异步线程了呢?
哈喽有缘人,如果碰巧点击了这篇文章,暂时不谈原理方面,说明您对@Async注解的使用还是不够熟练哦,一步一步的,听我娓娓道来。
实现异步线程需要两步即可完成
- 其一:在执行方法上添加注解@Async
- 其二:在主启动类上添加注解@EnableAsync用来激活配置。
备注说明:
对于线程池的定义本章暂不赘述,等后面我再梳理完知识点之后,再一起交流沟通。继续回到本章内容中,Spring5之后默认实现类为:LazyTraceThreadPoolTaskExecutor,如下图所示:在不采取自定义线程池大小时,默认采用Integer.MAX_VALUE,即最大值,一定情况下会引发OOM !!!,切记,需采用自定义线程池的大小。
失效使用
- 核心代码之Service层
/**
* @author: Mr.Gao
* @date: 2022年09月08日 14:28
* @description:
*/
@Slf4j
@Component
public class AsyncClient {
/**
* 调用代码
*
* @throws Exception
*/
public void asyncExecTest() throws Exception {
log.info("asyncExecTest BEGIN!");
// TODO 失效原因
this.asyncTest();
log.info("asyncExecTest END!");
}
/**
* 异步推送处理 间隔时间为2秒 重试三次
* @return
*/
@Async(value = "transNotifyReplySendThread")
@Retryable(value = Exception.class, backoff = @Backoff(value = 2000))
public void asyncTest() throws Exception {
log.info("接受到微信回调 开启异步推送内部工程进行处理");
TimeUnit.SECONDS.sleep(2);
log.info("订单异步处理成功....");
}
}
- 核心代码之Controller层
/**
* @author: Mr.Gao
* @date: 2022年09月08日 14:28
* @description:
*/
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncClient asyncClient;
/**
* 异步推送结果
*
* @return
* @throws Exception
*/
@PostMapping("/asyncExecTest")
public BaseResMessage<Void> asyncExecTest() throws Exception {
asyncClient.asyncExecTest();
return BaseResMessage.build(ServiceConstants.RES_SUCCESS_CODE, "异步推送成功!");
}
}
- 运行结果(失效案例)
2022-09-08 17:09:26.066 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.g.alipay.aop.AopPreHandleConfig : tc-gateway-alipay 接口 [[ALIPAY] 异步推送结果] 接收的请求参数:null
2022-09-08 17:09:26.071 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.gateway.alipay.client.AsyncClient : asyncExecTest BEGIN!
2022-09-08 17:09:26.072 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.gateway.alipay.client.AsyncClient : 接受到微信回调 开启异步推送内部工程进行处理
2022-09-08 17:09:28.080 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.gateway.alipay.client.AsyncClient : 订单异步处理成功....
2022-09-08 17:09:28.080 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.gateway.alipay.client.AsyncClient : asyncExecTest END!
2022-09-08 17:09:28.080 INFO [tc-gateway-alipay,38bf20182499f4e0,38bf20182499f4e0,false] 98248 --- [nio-9389-exec-1] c.t.c.g.alipay.aop.AopPreHandleConfig : tc-gateway-alipay 接口 [[ALIPAY] 异步推送结果] 返回的响应参数:code:00,msg:异步推送成功!, 耗时2014ms
- 注意
由此可以发现为同一个线程执行了,细心的同学可能发现了,TimeUnit.SECONDS.sleep(2) ;这行代码 模拟业务逻辑沉睡了 2s
- 响应结果
由此说明@Async已失效
解决方案如下
- 1、Controller层直接调用Service的异步方法
/**
* 异步推送结果
*
* @return
* @throws Exception
*/
@ControllerLoggerInfo("[ALIPAY] 异步推送结果")
@PostMapping("/asyncExecTest")
public BaseResMessage<Void> asyncExecTest() throws Exception {
asyncClient.asyncTest();
return BaseResMessage.build(ServiceConstants.RES_SUCCESS_CODE, "异步推送成功!");
}
- 2、去除this方式调用,采用SpringAOP 代理方式
public void asyncExecTest() throws Exception {
log.info("asyncExecTest BEGIN!");
// 采用SpringAOP 代理类进行调用
AsyncClient notifyClient = iocClientUtil.getBean(AsyncClient.class);
notifyClient.asyncTest();
log.info("asyncExecTest END!");
}
- 执行结果
[nio-9389-exec-1] c.t.c.g.alipay.aop.AopPreHandleConfig : tc-gateway-alipay 接口 [[ALIPAY] 异步推送结果] 接收的请求参数:null
[nio-9389-exec-1] c.t.c.g.alipay.aop.AopPreHandleConfig : tc-gateway-alipay 接口 [[ALIPAY] 异步推送结果] 返回的响应参数:code:00,msg:异步推送成功!, 耗时6ms
[eplySendThread1] c.t.c.gateway.alipay.client.AsyncClient : 接受到微信回调 开启异步推送内部工程进行处理
[eplySendThread1] c.t.c.gateway.alipay.client.AsyncClient : 订单异步处理成功....
此时会发现异步线程生效了
- 3、AopContext来获取当前代理类方式
- 核心代码如下:
// 获取当前代理类
AsyncClient notifyClient = (AsyncClient) AopContext.currentProxy();
// 调用异步方法
notifyClient.asyncTest();
- 继续执行,会发现报错代码
2022-09-08 17:53:20.336 ERROR [tc-gateway-alipay,774ef38467a96093,774ef38467a96093,false] 48156 --- [nio-9389-exec-1] c.t.c.g.alipay.aop.AopPreHandleConfig : Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)
com.tc.cloud.gateway.alipay.client.AsyncClient.asyncExecTest(AsyncClient.java:37)
com.tc.cloud.gateway.alipay.client.AsyncClient$$FastClassBySpringCGLIB$$67ca3f33.invoke(<generated>)
org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
- 在主启动类上加上配置
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
- 或通过xml配置文件添加配置
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
总结
- 在类方法中使用this调用异步线程方法,导致失效!!!
- 若要想在类内部调用异步方法,需要获取代理类。因为基于Spring注解化配置,底层已然是实现了异步逻辑,如此类推,事务(@Transactional)失效的原理也是如此。
- 基于Spring获取代理类方式如下:
- 从SpringIOC容器中获取:
Objetc bean = applicationContext.getBean(beanName);
- 通过AopContext方式
Object bean = AopContext.currentProxy();
- 从SpringIOC容器中获取:
到此,我想了下,类似于这种注解的方式,两部曲:标记(@AAA) + 启动配置(@EnableAAA);而且凡是基于Spring框架的,均需要采用代理的方式才行, 否则就会导致其失效。
题外话:
以此记录,可能文章排版有些问题,总感觉怪怪的呢,但就是说不上来,反正就是写的时候想到啥说啥,哈哈哈哈哈哈,后面我尽量调整下,把文章的深度再挖一挖。总的来说,也算是一种文字方面的提升,当然上边有说错的地方,也欢迎进行批评指正,非常感谢!