Springboot自定義重試注解@Retryable

一、概述
微服務之間相互調用,難免會出現形形色色的異常,出現異常時有些情況可能需要先落重試任務表,然后通過任務調度等進行定時重試;通過自定義重試注解@Retryable,減少對核心業(yè)務代碼入侵,增強代碼可讀性、可維護性。下面通過實戰(zhàn),開發(fā)自定義重試注解@Retryable。諸位可根據業(yè)務需要,稍作改造直接使用;如果有疑問、或者好的想法,歡迎留言,經驗共享。
二、實戰(zhàn)
重試任務表定義(retry_task):
CREATE TABLE `retry_task` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵值',
  `business_type_code` varchar(32) COLLATE NOT NULL DEFAULT '' COMMENT '業(yè)務類型編碼',
  `business_type_desc` varchar(100) COLLATE NOT NULL DEFAULT '' COMMENT '業(yè)務類型描述',
  `retry_service_name` varchar(100) COLLATE NOT NULL DEFAULT '' COMMENT '重試的service名稱',
  `business_param` text COLLATE NOT NULL DEFAULT '' COMMENT '業(yè)務參數',
  `wait_retry_times` int(11) NOT NULL DEFAULT 3 COMMENT '待重試次數',
  `already_retry_times` int(11) NOT NULL DEFAULT 0 COMMENT '已重試次數',
  `retry_result_code` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '重試結果碼',
  `retry_result_msg` varchar(255) COLLATE NOT NULL DEFAULT '' COMMENT '重試結果描述',
  `create_user` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '創(chuàng)建人',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時間',
  `update_user` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '更新人',
  `update_time` datetime NOT NULL COMMENT '更新時間',
  PRIMARY KEY (`id`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_business_type_code` (`business_type_code`)
) COMMENT='重試任務表';重試任務表實體類(RetryTaskEntity):
@Data
public class RetryTaskEntity implements Serializable {
    private static final long serialVersionUID = -1950778520234119369L;
    /**
     * 主鍵值
     */
    private BigInteger id;
    /**
     * 業(yè)務類型編碼
     */
    private String businessTypeCode;
    /**
     * 業(yè)務類型描述
     */
    private String businessTypeDesc;
    /**
     * 重試的service名稱
     */
    private String retryServiceName;
    /**
     * 業(yè)務參數
     */
    private String businessParam;
    /**
     * 待重試的次數
     */
    private Integer waitRetryTimes;
    /**
     * 已重試的次數
     */
    private Integer alreadyRetryTimes;
    /**
     * 重試結果碼
     */
    private String retryResultCode;
    /**
     * 重試結果描述
     */
    private String retryResultMsg;
    /**
     * 創(chuàng)建人
     */
    private String createUser;
    /**
     * 創(chuàng)建時間
     */
    private Date createTime;
    /**
     * 更新人
     */
    private String updateUser;
    /**
     * 更新時間
     */
    private Date updateTime;
}重試任務表mapper和對應的xml文件:
public interface RetryTaskMapper {
    int addRetryTask(RetryTaskEntity retryTaskEntity);
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.boot.demo.mapper.RetryTaskMapper">
    <insert id="addRetryTask" parameterType="com.boot.demo.pojo.RetryTaskEntity">
        INSERT INTO retry_task(business_type_code,
                               business_type_desc,
                               retry_service_name,
                               business_param,
                               wait_retry_times,
                               already_retry_times,
                               retry_result_code,
                               retry_result_msg,
                               create_user,
                               create_time,
                               update_user,
                               update_time)
        VALUES (#{businessTypeCode},
                #{businessTypeDesc},
                #{retryServiceName},
                #{businessParam},
                #{waitRetryTimes},
                #{alreadyRetryTimes},
                #{retryResultCode},
                #{retryResultMsg},
                #{createUser},
                #{createTime},
                #{updateUser},
                #{updateTime})
    </insert>
</mapper>重試任務表service和對應的serviceImpl:
public interface RetryTaskService {
    void addRetryTask(RetryTaskEntity retryTaskEntity);
}@Service
public class RetryTaskServiceImpl implements RetryTaskService {
    @Autowired
    private RetryTaskMapper retryTaskMapper;
    @Override
    public void addRetryTask(RetryTaskEntity retryTaskEntity) {
        retryTaskMapper.addRetryTask(retryTaskEntity);
    }
}業(yè)務類型枚舉類(RetryTaskDefinitionEnum):
/**
 * 重試任務枚舉
 */
public enum RetryTaskDefinitionEnum {
    ADD_STOCK("101", "采購入庫成功后新增庫存異常重試", "purchaseService", 3);
    /**
     * 業(yè)務類型編碼
     */
    private final String businessTypeCode;
    /**
     * 業(yè)務類型描述
     */
    private final String businessTypeDesc;
    /**
     * 重試的service名稱
     */
    private final String retryServiceName;
    /**
     * 重試次數
     */
    private final Integer retryTimes;
    RetryTaskDefinitionEnum(String businessTypeCode, String businessTypeDesc, String retryServiceName, Integer retryTimes) {
        this.businessTypeCode = businessTypeCode;
        this.businessTypeDesc = businessTypeDesc;
        this.retryServiceName = retryServiceName;
        this.retryTimes = retryTimes;
    }
    public static RetryTaskDefinitionEnum getTaskDefinitionByBusinessTypeCode(String businessTypeCode) {
        if (StringUtils.isBlank(businessTypeCode)) {
            return null;
        }
        for (RetryTaskDefinitionEnum taskDefinition : values()) {
            if (taskDefinition.getBusinessTypeCode().equals(businessTypeCode)) {
                return taskDefinition;
            }
        }
        return null;
    }
    public String getBusinessTypeCode() {
        return businessTypeCode;
    }
    public String getBusinessTypeDesc() {
        return businessTypeDesc;
    }
    public String getRetryServiceName() {
        return retryServiceName;
    }
    public Integer getRetryTimes() {
        return retryTimes;
    }
}自定義注解(@MyRetryable):
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface MyRetryable {
    RetryTaskDefinitionEnum businessType();
}自定義注解切面(MyRetryableAspect):
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import com.boot.demo.result.Result;
import com.boot.demo.result.ResultCode;
import com.boot.demo.pojo.RetryTaskEntity;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import com.boot.demo.annotation.MyRetryable;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.ProceedingJoinPoint;
import com.boot.demo.service.RetryTaskService;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.boot.demo.annotation.RetryTaskDefinitionEnum;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
@Slf4j
@Aspect
@Component
public class MyRetryableAspect {
    @Autowired
    private RetryTaskService retryTaskService;
    @Pointcut("@annotation(com.boot.demo.annotation.MyRetryable)")
    public void pointCut() {
    }
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Result result = null;
        try {
            // 執(zhí)行目標方法
            result = (Result) joinPoint.proceed();
            // 目標方法返回:成功結果碼(200),則無需重試
            if (ResultCode.SUCCESS.getCode() == result.getCode()) {
                return result;
            }
            // 目標方法返回:非成功結果碼(非200)則需重試(此次可根據需要判斷什么樣的返回碼需要重試)
            dealAddRetryTask(joinPoint);,
            return result;
        } catch (Throwable e) {
            log.error("myRetryableAspectLog error param: {} result: {} e: ", joinPoint.getArgs(), result, e);
            // 此處捕獲異常之后,也可以根據需要重試,這里就僅輸出異常日志
            return result;
        }
    }
    private void dealAddRetryTask(ProceedingJoinPoint joinPoint) {
        // 獲取重試注解信息
        MyRetryable myRetryableAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MyRetryable.class);
        if (null == myRetryableAnnotation) {
            return;
        }
        // 根據業(yè)務類型編碼,獲取枚舉中定義的業(yè)務類型描述、重試的service、重試次數等信息
        String businessTypeCode = myRetryableAnnotation.businessType().getBusinessTypeCode();
        RetryTaskDefinitionEnum retryTaskDefinition = RetryTaskDefinitionEnum.getTaskDefinitionByBusinessTypeCode(businessTypeCode);
        if (null == retryTaskDefinition) {
            return;
        }
        RetryTaskEntity retryTaskEntity = new RetryTaskEntity();
        retryTaskEntity.setBusinessTypeCode(businessTypeCode);
        retryTaskEntity.setBusinessTypeDesc(retryTaskDefinition.getBusinessTypeDesc());
        retryTaskEntity.setRetryServiceName(retryTaskDefinition.getRetryServiceName());
        retryTaskEntity.setBusinessParam(JSON.toJSONString(joinPoint.getArgs()[0]));
        retryTaskEntity.setWaitRetryTimes(retryTaskDefinition.getRetryTimes());
        retryTaskEntity.setAlreadyRetryTimes(0);
        retryTaskEntity.setRetryResultCode("");
        retryTaskEntity.setRetryResultMsg("");
        retryTaskEntity.setCreateUser("SYS");
        retryTaskEntity.setCreateTime(new Date());
        retryTaskEntity.setUpdateUser("SYS");
        retryTaskEntity.setUpdateTime(new Date());
        retryTaskService.addRetryTask(retryTaskEntity);
    }
}基礎類(Result、ResultCode、ResultGenerator)。
Result類:
public class Result {
    private int code;
    private String message;
    private Object data;
    public Result setCode(ResultCode resultCode) {
        this.code = resultCode.getCode();
        return this;
    }
    public int getCode() {
        return code;
    }
    public Result setCode(int code) {
        this.code = code;
        return this;
    }
    public String getMessage() {
        return message;
    }
    public Result setMessage(String message) {
        this.message = message;
        return this;
    }
    public Object getData() {
        return data;
    }
    public Result setData(Object data) {
        this.data = data;
        return this;
    }
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Result{");
        sb.append("code=").append(code);
        sb.append(", message='").append(message).append('\'');
        sb.append(", data=").append(data);
        sb.append('}');
        return sb.toString();
    }
}ResultCode類:
public enum ResultCode {
    SUCCESS(200),
    FAIL(400),
    UNAUTHORIZED(401),
    FORBIDDEN(403),
    NOT_FOUND(404),
    INTERNAL_SERVER_ERROR(500);
    private final int code;
    ResultCode(int code) {
        this.code = code;
    }
    public int getCode() {
        return code;
    }
}ResultGenerator類:
public class ResultGenerator {
    private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
    private ResultGenerator() {
    }
    public static Result genSuccessResult() {
        return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE);
    }
    public static Result genSuccessResult(Object data) {
        return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE).setData(data);
    }
    public static Result genFailResult(String message) {
        return new Result().setCode(ResultCode.FAIL).setMessage(message);
    }
    public static Result genFailResult(ResultCode code, String message) {
        return new Result().setCode(code).setMessage(message);
    }
    public static Result genFailResult(String message, Object data) {
        return new Result().setCode(ResultCode.FAIL).setMessage(message).setData(data);
    }
}測試controller(PurchaseController):
@RestController
@RequestMapping("/purchase")
public class PurchaseController {
    @Autowired
    private PurchaseService purchaseService;
    @GetMapping("/test")
    public String test(String param) {
        purchaseService.addStock(param);
        return "success";
    }
}測試PurchaseService、和PurchaseServiceImpl
public interface PurchaseService {
    Result addStock(String param);
}@Service("purchaseService")
public class PurchaseServiceImpl implements PurchaseService {
    @Override
    // 在需要重試的業(yè)務方法上新增重試注解即可
    @MyRetryable(businessType = RetryTaskDefinitionEnum.ADD_STOCK)
    public Result addStock(String param) {
//     return ResultGenerator.genSuccessResult();
        return ResultGenerator.genFailResult("系統異常...");
    }
}三、總結
新增重試任務成功之后,我們可通過調度平臺(比如:xxlJob),定時查詢重試任務表,然后調用RetryTaskDefinitionEnum中定義的重試的service(retryServiceName),這里可以定義一個模板方法,根據retryServiceName,從spring中獲取到對應的bean,執(zhí)行具體的業(yè)務方法,然后更新任務狀態(tài)和重試次數即可。















 
 
 















 
 
 
 