前言
为了防止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 文档:
文档信息
- 本文作者:nyaaar
- 本文链接:https://nyaaarlathotep.github.io/2022/06/03/%E9%98%B2%E6%AD%A2Spring%E6%96%B9%E6%B3%95%E9%87%8D%E5%85%A5/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)