【Spring AOP】暴力打通兩個切面之間的通訊

miozus發表於2022-03-29

場景描述

在秒殺微服務中,筆者在需要各種校驗前端傳來的引數後,通過 Redis 加鎖限流(切面A)並返回,最後封裝訂單資料推送到 RabbitMQ 訊息佇列(切面B)做善後工作。

問題:如何將 切面 A 的資料傳遞 給切面B 處理呢?

/**
 * 新增到秒殺流程
 *
 * @param killId 秒殺商品快取鍵 sessionId_skuId
 * @param key    隨機碼 randomCode
 * @param num    數量
 * @return {@link R}
 */
@GetMapping("/kill")
public R addToSeckill(
        @RequestParam("killId") String killId,
        @RequestParam("key") String key,
        @RequestParam("num") Integer num) {
    // 實現類只是帶有兩個註解方法,返回 null(因為全部交給切面託管了)
    String orderSn = seckillService.kill(killId, key, num);
    if (StringUtils.isEmpty(orderSn)) {
        return R.error();
    }
    return R.ok().setData(orderSn);
}

解決方案

通過引數傳遞資料,通過捕獲異常保證業務邏輯(離譜但有用) ?

// 強制修改引數,通過異常返回正常流程,而通過AOP訊息佇列處理收尾動作
try {
    return pjp.proceed(new Object[]{orderTo, null, null});
} catch (Throwable e) {
    return orderSn;
}

注意事項:

  1. 引數一致性:必須偽造和方法簽名的數量相等的引數 ⇒ 否則執行緒會丟擲異常 I 就返回了,無法執行 pjp.proceed 原始方法 ⇒ 無法跳轉第二個切面
    java.lang.IllegalArgumentException: Expecting 3 arguments to proceed, but was passed 1 arguments

  2. 捕獲異常不丟擲,直接執行正常業務邏輯 ⇒ 否則執行緒將吞沒異常 II
    cn.miozus.gulimall.common.to.mq.SeckillOrderTo cannot be cast to java.lang.String

3.雖然兩個切面都返回了 orderSn ,實際最終只有切面A傳遞到了控制層和前端, 切面B的返回值成了擺設。

跳轉過程

打斷點檢視兩個切面的跳轉過程。

切面A:準備跳轉第二個切面

切面A:準備跳轉第二個切面.png

切面B:傳送訊息完成

切面B:傳送訊息完成.png

列印日誌,可見場景需求,已經滿足了。

2022-03-29 17:32:56.521  INFO 7904 --- [io-25000-exec-8] c.m.g.s.aspect.SeckillRabbitMqAspect     : 快速建立訂單:傳送訊息建立完成: 202203291732444881508738921192005634
2022-03-29 17:33:01.526  INFO 7904 --- [io-25000-exec-8] c.m.g.s.controller.SeckillController     : 秒殺建立訂單用時:28778
? seckill orderSn = 202203291732444881508738921192005634
2022-03-29 17:33:01.527  INFO 7904 --- [nectionFactory5] c.m.g.s.config.RabbitMqSeckillConfig     : ? 訊息已傳送, params: correlationData:null,ack:true,cause:null 

其他方案

最簡單的辦法,不切了,兩個切面耦合在一起。注入和呼叫方法。

相關文章