java中对接口防重复调用方案(上手即用)

1.背景

最近项目中碰到一些因为网络延迟及系统响应速度变慢造成的请求重复提交问题,之前用若依可以直接用自带的用,但是本身业务是隔离的,所以这里用自定义注解实现接口防重复调用

2.创建自定义注解

/**
 * @Author:crispsea
 * @Date :2024/6/20 - 06 - 20 - 15:45
 */
@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LockCommit {
    String key() default "";
}
  • @Target(ElementType.METHOD): 这个元注解指定了 LockCommit 注解可以应用的目标类型。在这个例子中,ElementType.METHOD 表示这个注解只能应用于方法上。

  • @Retention(value = RetentionPolicy.RUNTIME): 这个元注解指定了 LockCommit 注解的信息将保留到哪个阶段。RetentionPolicy.RUNTIME 表示注解会在编译后的class文件中存在,并且在运行时可以通过反射机制访问到该注解的信息。

  • @Documented: 这个元注解表示当使用 LockCommit 注解的方法被JavaDoc工具处理时,该注解会被包含在生成的文档中。也就是说,这个注解是可文档化的,有助于提高代码的可读性和可维护性。

  • @Inherited: 这个元注解表示 LockCommit 注解是可以被子类继承的。如果一个类被 LockCommit 注解标记,那么它的所有子类也会隐式地拥有这个注解,除非它们被显式地用不同的注解标记了。

  • public @interface LockCommit: 这一行声明了一个新的注解类型,名字为 LockCommitpublic 关键字意味着这个注解可以被任何其他类或包访问。

  • String key() default "";: 这是 LockCommit 注解的一个成员,名称为 key,类型为 String,默认值为空字符串。这意味着当你使用 LockCommit 注解时,你可以指定一个字符串值给 key 属性,也可以选择不指定,此时 key 将采用默认值,即空字符串。

总结起来,LockCommit 是一个可以在方法级别使用的注解,它携带一个可选的 key 属性,这个属性用于存储字符串类型的值。由于它的保留策略是 RUNTIME,因此可以在运行时通过反射获取到这个注解以及它的 key 属性值。这可能用于某些需要在运行时动态获取方法特定信息的场景,比如事务管理、缓存控制或者其他形式的锁机制。

 03 编写一个拦截器

/**
 * @Author:crispsea
 * @Date :2024/6/20 - 06 - 20 - 15:47
 */
@Slf4j
@Component
@Aspect
public class NoRepeatSubmitAspect  {

    public static final Cache<String,Object> CACHES = CacheBuilder.newBuilder()
            .maximumSize(50)
            .expireAfterWrite(2, TimeUnit.SECONDS)
            .build();
    @Pointcut("@annotation(com.drpanda.csservice.domain.annotation.LockCommit)")
    public void pointCut(){}

    @Around("pointCut()")
    public Object Lock(ProceedingJoinPoint joinPoint){
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        LockCommit lockCommit = method.getAnnotation(LockCommit.class);
        String key = lockCommit.key();
        if(key!=null &&!"".equals(key)){
            if(CACHES.getIfPresent(key)!=null){
                assert response != null;
                response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
            }
            CACHES.put(key,key);
        }
        Object object = null;
        try {
            object = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return object;
    }

}

@Slf4j
@Component
@Aspect
  • @Slf4j:这是一个Lombok提供的注解,用于自动注入日志对象。它会生成一个名为 log 的日志记录器,方便进行日志记录。
  • @Component:Spring框架中的注解,表明这是一个Spring管理的Bean。
  • @Aspect:Spring AOP中的注解,表明这是一个切面类,用于定义横切关注点(如事务管理、日志记录等)。

这段代码主要是一个环绕通知(Around Advice),用于处理注解 @LockCommit 的方法,以防止重复提交。接下来我将逐步分解并详细解释这段代码。

详细分解

  1. 缓存的定义

    public static final Cache<String,Object> CACHES = CacheBuilder.newBuilder()
            .maximumSize(50)
            .expireAfterWrite(2, TimeUnit.SECONDS)
            .build();
    

    CopyInsert

    • 使用 Guava 的 CacheBuilder 创建一个缓存 CACHES
    • maximumSize(50):缓存最多存储 50 个条目。
    • expireAfterWrite(2, TimeUnit.SECONDS):每个条目在写入后 2 秒内有效,超过时间后会自动失效。
  2. 定义切点

    @Pointcut("@annotation(com.drpanda.csservice.domain.annotation.LockCommit)")
    public void pointCut(){}
    

    CopyInsert

    • 使用 @Pointcut 注解定义一个切点,表示任何被 LockCommit 注解的方法都会被这个切点匹配。
  3. 环绕通知

    @Around("pointCut()")
    public Object Lock(ProceedingJoinPoint joinPoint) {
    

    CopyInsert

    • @Around 注解表明这个方法是一个环绕通知,会在切点匹配的方法执行前后被调用。
    • ProceedingJoinPoint 是一个可以控制目标方法执行的参数。
  4. 获取 HTTP 响应

    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    

    CopyInsert

    • 从 RequestContextHolder 获取当前请求的 HttpServletResponse 对象,以便在需要时修改响应状态。
  5. 获取方法和注解信息

    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    LockCommit lockCommit = method.getAnnotation(LockCommit.class);
    String key = lockCommit.key();
    

    CopyInsert

    • 通过 joinPoint 获取被调用的方法的签名和具体方法。
    • 从方法中获取 LockCommit 注解,并提取出注解中的 key
  6. 检查缓存

    if (key != null && !"".equals(key)) {
        if (CACHES.getIfPresent(key) != null) {
            assert response != null;
            response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
            return null;
        }
        CACHES.put(key, key);
    

    CopyInsert

    • 判断 key 是否有效,如果缓存中已经存在该 key,则表示该请求正在处理中。
    • 如果存在,设置 HTTP 响应状态为 503(服务不可用),然后返回 null,终止方法执行。
    • 如果不存在,将该 key 放入缓存以标记为正在处理。
  7. 执行目标方法

    try {
        Object object = joinPoint.proceed();
        CACHES.invalidate(key);
        return object;
    } catch (Throwable e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
    

    CopyInsert

    • 调用 joinPoint.proceed() 执行目标方法。
    • 正常执行后,移除缓存中该 key 的条目。
    • 捕获任何异常,打印堆栈信息并抛出运行时异常。
  8. 没有锁定的处理

    try {
        return joinPoint.proceed();
    } catch (Throwable e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
    

    CopyInsert

    • 如果 key 是空的或无效,直接执行目标方法并处理异常。

总结

这段代码的主要功能是在使用 @LockCommit 注解的方法上实现防止重复提交的机制。通过使用缓存来存储正在处理的请求标记,若在有效时间内再次请求相同的操作,则会返回 503 错误,避免重复处理。此代码适用于需要保护幂等性或避免重复操作的场景,如支付、提交表单等。

04 在controller对应的业务上添加注解 

key最好唯一,且修改跟删除较多的地方可以考虑使用

05 前端回显 

 因为这里设置了全局异常拦截,前端直接配置就可以获取到接口返回的异常信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值