Java自定义注解实战之实现接口的幂等性

1,192 阅读3分钟

Java自定义注解实战

什么是注解?

注解是写在.java文件中,使用@interface作为关键字的,所以注解也是Java的一种数据类型;注解从 JDK1.5开始引入的一个特性,可以对类、接口、方法、属性、方法参数等进行注解。

注解的作用

注解主要有以下四个作用:

  • 生成文档:生成javadoc文档,如@Documented
  • 编译检查:在编译期间对代码进行检查验证,如@Override
  • 编译时处理:编译时对代码进行特定的处理,如@Data
  • 运行时处理:运行时对代码进行特定的处理,如@Slf4j

注解与反射

定义注解后,我们可以通过反射java.lang.reflect机制获取注解的内容。注:只有当注解的使用范围定义为Runtime才能通过反射获取。

以下为具体接口API:

方法说明方法
获取某个类型的注解public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
获取所有注解(包括父类中被Inherited修饰的注解)public Annotation[] getAnnotations();
获取声明的注解(不包括父类中被Inherited修饰的注解)public Annotation[] getDeclaredAnnotations();
判断某个对象上是否被某个注解进行标注public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
获取某个类声明的所有字段public Field[] getDeclaredFields() throws SecurityException;
获取某个方法public Method getMethod(String name, Class<?>... parameterTypes);

自定义注解实战

结合AOP可以进行权限控制,日志输出及幂等性校验等;

以幂等性1校验举例子:

第一步:定义注解

package com.wedjg.cloud.finance.aspect.idem;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableIdem {
    /**
     * 幂等时间间隔(秒)
     * @return
     */
    int expired() default 3;
}

第二步:使用注解

@PostMapping("test")
@EnableIdem
public CommonResult test(@RequestBody DemoDTO dto) {
    financeService.test(dto);
    return CommonResult.success(true);
}

第三步:编写切面

@Aspect
@Component
public class IdempotentAspect {

    public static final Log log = LogFactory.get();

    public static final String KEY = "Idem|";

    @Before("@annotation(com.wedjg.cloud.aspect.idem.EnableIdem)")
    public void handle(JoinPoint joinPoint) throws Exception{
        // 获取参数值
        Object[] args = joinPoint.getArgs();
        //构造缓存的key
        //此处使用的是方法路径 + 参数的hashcode 作为key,在个别情况下有可能会重复
        //在实际使用时应根据业务调整
        StringBuilder sb = new StringBuilder();
        for (Object arg : args) {
            sb.append(Convert.toStr(arg));
        }
        String methodPath = joinPoint.getSignature().getDeclaringTypeName() + CharUtil.DOT + joinPoint.getSignature().getName();
        String redisKey = KEY + methodPath + sb.toString().hashCode();

        //将key存到redis中。要注意保证 setnx 和 expired 的原子性;
        //若key已存在,说明在expired()的时间范围内已经请求过。当前请求为重复请求;
        //若key不存在,则把当前key设置到redis中,并设置过期时间。
        //获取注解的信息
        EnableIdem enableIdem = this.getDeclaredAnnotation(joinPoint);
        if(JedisUtil.setnxAndExpire(redisKey, enableIdem.expired(), "1") == 0L) {
            throw new BaseException("操作过于频繁,请稍后再试!");
        }
    }

    /**
     * 获取方法中声明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public EnableIdem getDeclaredAnnotation(JoinPoint joinPoint) throws NoSuchMethodException {
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 反射获取目标类
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法对应的参数类型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根据类、方法、参数类型(重载)获取到方法的具体信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        // 拿到方法定义的注解信息
        EnableIdem annotation = objMethod.getDeclaredAnnotation(EnableIdem.class);
        // 返回
        return annotation;
    }
}

参考文章

Footnotes

  1. 用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用