自定义注解支持SPEL表达式

4,191 阅读1分钟

SPEL 表达式

Spring 提供的EL表达式解析特性强化了注解能力。能做到注解中的属性值动态地从方法参数中获取,而不是简单地设置固定值。

自定义注解

使用 Java 原生注解自定义注解。我们想要做到 source 属性从方法参数中动态获取。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HaxService {

    /**数据来源*/
    String source() default "default";

}

// 使用效果:
@HaxService(source = "#source.name()")
public List<ImportResp> synStock(OriStockDTO oriStockDTO, StockSourceEnum source) throws Exception {
  	...
}

解析SPEL表达式

/**
 * 哈希Service切面
 * 1.记录请求报文、响应报文
 * 2.记录接口耗时
 * @author 曲元涛
 * @date 2020/11/30 15:33
 */
@Component
@Aspect
public class HaxServiceAspect {

    private static final Logger logger = LoggerFactory.getLogger(HaxServiceAspect.class);
    /**方法参数名解析器*/
    private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    /**Spel表达式解析器*/
    private final SpelExpressionParser parser = new SpelExpressionParser();
    /**SPEL表达式标识符*/
    public final String SPEL_FLAG = "#";

    @Resource
    private ApiRecordService apiRecordService;
    @Resource
    private LinkThreadPoolExecutor linkThreadPoolExecutor;

    @Pointcut("@annotation(com.hand.business.tool.annotation.HaxService)")
    private void haxServiceAspect() {}

    @Around(value = "haxServiceAspect()")
    public Object haxServiceAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        // 请求时间
        String requestTime = DateUtil.dateToStr(new Date(), DateUtil.DATE_TIME);

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        final String methodName = method.getName();

        // 方法形参名称
        String[] argNames = HaxBeanTool.getOrDefault(nameDiscoverer.getParameterNames(method), new String[0]);
        // 方法实参值
        Object[] args = joinPoint.getArgs();

        HaxService haxService = method.getAnnotation(HaxService.class);
        // 数据来源
        String dataSource = this.analysisSpel(haxService.source(), argNames, args);

        // 切面方法形参值
        JSONObject requestParam = new JSONObject();
        for (int i = 0; i < argNames.length; i++) {
            String argName = argNames[i];
            Object argVal = args[i];
            requestParam.put(argName, argVal);
        }

        StopWatch stopWatch = new StopWatch();
        stopWatch.start(methodName);
        try {
            Object result = joinPoint.proceed();
            stopWatch.stop();
            long rt = stopWatch.getTotalTimeMillis();
            ApiRecord sucRecord = new ApiRecord(dataSource, methodName, requestParam, result, rt);
            sucRecord.setRequestTime(requestTime);
            sucRecord.setSuccess(ProcessRet.success.name());
            this.doLog(sucRecord);
            return result;
        } catch (Throwable throwable) {
            logger.info("[haxServiceAspect] method [{}] proceed throw exception... [{}]", 
                        methodName, throwable.getMessage());
            stopWatch.stop();
            long rt = stopWatch.getTotalTimeMillis();
            ApiRecord failRecord = 
              new ApiRecord(dataSource, methodName, requestParam, throwable.getMessage(), rt);
            failRecord.setRequestTime(requestTime);
            failRecord.setSuccess(ProcessRet.failed.name());
            failRecord.setErrorMsg(throwable.getMessage());
            this.doLog(failRecord);
            throw throwable;
        }
    }

    private void doLog(ApiRecord record) {
        linkThreadPoolExecutor.execute(() -> {
            if (StringUtils.isBlank(record.getRequestTime())) {
                record.setRequestTime(DateUtil.dateToStr(new Date(), DateUtil.DATE_TIME));
            }
            try {
                apiRecordService.insert(record);
            } catch (Exception e) {
                logger.info("保存接口日志异常[{}]", e.getMessage());
            }
        });
    }

    /**
     * 解析SPEL表达式
     *
     * @author 曲元涛
     * @date 2020/11/30 下午6:05
     * @param spel          SPEL表达式
     * @param argNames      形参名称数组
     * @param args          实参数组
     */
    private String analysisSpel(String spel, String[] argNames, Object[] args) {
        if (!StrUtil.contains(spel, SPEL_FLAG)) {
            return spel;
        }
        // 解析后的SPEL
        Expression expression = parser.parseExpression(spel);
        // spring表达式上下文
        EvaluationContext context = new StandardEvaluationContext();
        // 给上下文赋值变量
        for (int i = 0; i < argNames.length; i++) {
            context.setVariable(argNames[i], args[i]);
        }
        return HaxBeanTool.getOrDefault(expression.getValue(context), "").toString();
    }
}