防止Spring方法重入

2022/06/03 Spring 共 3824 字,约 11 分钟

前言

为了防止Spring中的方法在前一次调用结束前被再次调用,做了一个小工具,标注注解使用,利用切片实现,使用原子量防止并发。

这个小玩意是我拍脑袋想出来的,有个文件处理的需求,在进行处理的时候,占内存什么的比较大,我害怕数据量大了处理生成结果太慢,用户等不及多次点击,调用,把我服务给打崩了,以前就有类似的情况发生过。或者防止出现各种奇妙的问题。就想通过什么方法阻止一下重入,让接口不能被并发地调用,只能串行执行。

为什么不直接上redis呢,因为这是个小服务,不想再接入redis加复杂度了,也没有分布式那些问题。网上查了查也没有什么好的解决方案。就想着自己试着写吧。

写完了,才感觉还是redis好,我这写的花里胡哨的,总感觉哪里会出问题,很害怕。

代码

注解类


import java.lang.annotation.*;

/**
 * 防止重入注解
 */
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventReEntryAnnotation {
}

在需要防止重入的controller方法上使用此注解。

切片


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.reflections.Reflections;
import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;



/**
 * 防止重入切片
 */
@Aspect
@Component
@Slf4j
public class PreventReEntryAspect {
    @Pointcut("@annotation(com.saicfc.efsw.reportform.efswbizreportformservice.common.annotation.PreventReEntryAnnotation)")
    public void logPointCut() {
    }

	// 用来控制是否拒绝方法调用的hashmap,key是方法名,value是用来控制并发的原子量
    private final HashMap<String, AtomicInteger> entryCtrlMap = new HashMap<>();

    @Autowired(required = false)
    // 初始化
    public void init() {
        log.info("PreventReEntryAspect init start");
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                // 指定路径URL,这里我们要扫的是controller
                .forPackages("***.***.***.***.***.controller")
                // 添加 方法注解扫描工具
                .addScanners(new MethodAnnotationsScanner())
        );
        Set<Method> methodSet = reflections.getMethodsAnnotatedWith(PreventReEntryAnnotation.class);
        for (Method method : methodSet) {
            entryCtrlMap.put(method.getName(), new AtomicInteger(0));
        }
        log.info("PreventReEntryAspect init end");
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        AtomicInteger atomicInteger = entryCtrlMap.get(signature.getMethod().getName());
        int now = atomicInteger.getAndIncrement();
        if (now == 0) {
            try {
                result = proceedingJoinPoint.proceed();
            } finally {
                atomicInteger.decrementAndGet();
            }
        } else {
            atomicInteger.decrementAndGet();
            log.info("方法执行结束前重入, method:{}", signature.getMethod().getName());
            throw new RfsException(RE_ENTRY_ERROR);
        }

        return result;
    }
}

主要就是用了一个HashMap来存原子量,并初始化。写的时候还想了想要不要懒加载,但HashMap并发不安全,两个请求一起调用的情况下不知道会发生什么,可能会两次set同一个方法名对应原子量,之后会怎么样呢?所以还得上ConcurrentHashMap,但这个又怕忘了释放什么的再搞出点啥问题来,徒然增加复杂度。

所以最后还是统一初始化了,此时就算两次调用同时取一个method name对应的原子量,他们的并发也是由原子量自己来保证的,hashMap只是提供一个原子量的索引,不会出问题。

org.reflections.Reflections

顺便,我还发现了个好用的东西,org.reflections.Reflections这个包,做了一些反射的东西,我找了半天Spring提供的接口,找不到合适使用的,还以为要自己写,最后搜索到了这个,有很多功能,方便啊。

Reflections scans your classpath, indexes the metadata, allows you to query it on runtime and may save and collect that information for many modules within your project.

Reflections扫描你的classpath,对元数据进行索引,允许你在运行时进行查询,并且可以为你项目中的许多模块保存和收集这些信息。

Using Reflections you can query your metadata such as:

  • get all subtypes of some type
  • get all types/constructors/methods/fields annotated with some annotation, optionally with annotation parameters matching
  • get all resources matching matching a regular expression
  • get all methods with specific signature including parameters, parameter annotations and return type
  • get all methods parameter names
  • get all fields/methods/constructors usages in code
  • 获得某个类型的所有子类型
  • 得到所有的类型/结构体/方法/字段的注释,可以选择与注释参数相匹配的注解
  • 获得所有与正则表达式匹配的资源
  • 获得所有具有特定签名的方法,包括参数、参数注解和返回类型
  • 获得所有方法的参数名称
  • 获得代码中所有字段/方法/结构体的使用情况

api 文档:

javadoc.io: Class Reflections

文档信息

Search

    Table of Contents