前言
此文档只粗略的讲解实现思路,具体的实现逻辑还需要针对业务区别处理。
需求
因为此业务中有读和写的操作,写的执行条件依赖于读,并发条件下可能出现读到相同的条件均可以去执行写操作,此时写就会出现脏数据,。所以项目需要实现,在处理业务时,加锁防止并发问题,此处利用Redis实现,但是如果多个业务都需要这么操作的话,其实操作Redis的代码是相同的,这样就显得麻烦,所以楼主采用注解的形式实现,具体方法见下述。
实现逻辑
在请求调用Controller层时,RequestMapping 映射到的方法上加上注解,如自定义注解 @Debounce(防止多次提交)。
此时需要考虑几个问题
1、利用Redis实现并发锁的操作对Redis来说实际上就是一种Key的操作,那么自定义注解@Debounce如何实现key的自定义且根据参数可变化?
2、如何实现调用请求真实的处理方法时的拦截?
3、什么情况下才会去做这个事情?
针对第一个问题解决方法
利用处理请求的方法中的参数,实现动态定义,此时又有个问题,就是说如果时基本数据类型+String,这样的可以直接将值获取拼接,但是如果参数中有对象的话,同时又想用对象中的属性作为key值的一部分,那么直接拼接就行不通。像这种情况,统一的方式行不通,那么自然而然就会想到此处必须用到了拓展类,在上层只是定义这种功能,具体的实现由子类负责具体实现。(详见后述)。
在@Debounce注解中有定义一个处理参数数组,值为处理请求的方法中的参数位置Num,从0开始依次递增,同时也有个处理类class,作用是具体实现key值的拼接。
针对第二个问题解决方法
当然是利用代理实现,此处利用的是Spring的Cglib动态代理。同时利用Spring开放的拓展Bean处理的接口BeanPostProcessor,在bean实例化后,实例化Cglib代理。
针对第三个问题解决方法
在Controller层即在有注解@Controller 或者 @RestController 的类中才会去判断是否需要做此操作。
具体实现方法
Debounce 注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Debounce {
/**
* 使用的锁键值
*/
String lockKey() default "";
/**
* 使用方法的参数(toString)做为锁的KEY使用
* 规则:从0开始计,0表示方法中的第一个参数,以此类推
* 和 lockKey 都为空时,使用方法名进行锁定
*/
int[] methodParameters() default {};
/**
* 对注释中的参数进行修改,默认为字符串拼接
*
* @return
*/
Class<? extends MethodParametersHandler> handler() default MethodParametersHandler.class;
/**
* 延时关闭,当调用的方法结束后并不关闭锁,只有真正超时后才关闭
* 即在锁定时间内,只允许被调用一次
*
* @return
*/
boolean delayClose() default false;
/**
* 默认的超时时间,这个时间内除非原来的方法调用结束,否则无法点击
* 如果原来的方法已经结束,时间比这个短,那么这时间无效
*/
@AliasFor("lockExpireEsc")
int value() default 5;
/**
* 锁的超时时间
*
* @return
*/
@AliasFor("value")
int lockExpireEsc() default 5;
}
参数处理接口 MethodParametersHandler
此处做参数参数值的拼接同时返回拼接后的数据
public interface MethodParametersHandler {
String handler(Object[] args) throws IllegalAccessException;
static class Default implements MethodParametersHandler {
@Override
public String handler(Object[] args) {
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
if (arg != null) {
sb.append(String.valueOf(arg));
sb.append("#");
}
}
return sb.toString();
}
}
}
方法拦截器定义 DebounceInvocationHandler
public class DebounceInvocationHandler implements MethodInterceptor {
private Map<Class<? extends MethodParametersHandler>, MethodParametersHandler> methodParametersHandlerMap = new ConcurrentHashMap<>();
private final Object target;
private static final MethodParametersHandler methodParametersHandler = new MethodParametersHandler.Default();
public DebounceInvocationHandler(Object bean) {
this.target = bean;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Debounce annotation = method.getAnnotation(Debounce.class);
if (annotation != null) {
int value = (int) AnnotationUtils.getValue(annotation);
if (value <= 0) {
value = 10;
}
// 组装Redis的key
String key = annotation.lockKey();
int[] methodParameters = annotation.methodParameters();
if (methodParameters != null && methodParameters.length > 0) {
Object[] handlerArgs = new Object[methodParameters.length];
for (int i = 0; i < methodParameters.length; i++) {
if (methodParameters[i] < args.length) {
handlerArgs[i] = args[methodParameters[i]];
}
}
MethodParametersHandler parametersHandler = null;
Class<? extends MethodParametersHandler> handler = annotation.handler();
if (handler == MethodParametersHandler.class) {
parametersHandler = methodParametersHandler;
} else {
if (methodParametersHandlerMap.containsKey(handler)) {
parametersHandler = methodParametersHandlerMap.get(handler);
} else {
MethodParametersHandler instance = handler.newInstance();
parametersHandler = methodParametersHandlerMap.putIfAbsent(handler, instance);
}
}
key += parametersHandler.handler(handlerArgs);
}
if (StringUtils.isEmpty(key)) {
key = method.toString();
}
// Redis 的分布式锁实现,代码省略 , 不满足锁的条件可以直接返回或是抛异常
}
try {
if (target == null) {
return methodProxy.invokeSuper(proxy, args);
} else {
return methodProxy.invoke(target, args);
}
} finally {
// 释放Reids 锁判断
if (annotation != null && (Redis 锁不为空) && !annotation.delayClose()) {
// 释放Redis锁
}
}
}
}
Bean实例化后的实现方式-BeanPostProcessor
@Configuration
public class RestfulMVCAutoConfiguration implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
RestController annotation = beanClass.getAnnotation(RestController.class);
if (annotation == null) {
return bean;
}
boolean haveDebounce = false;
Method[] methods = beanClass.getDeclaredMethods();
for (Method method : methods) {
Debounce debounce = method.getAnnotation(Debounce.class);
if (debounce != null) {
haveDebounce = true;
break;
}
}
if (haveDebounce) {
Enhancer en = new Enhancer();
en.setSuperclass(beanClass);
en.setUseFactory(false);
en.setCallback(new DebounceInvocationHandler(bean));
return en.create();
}
return bean;
}
}
使用方式
其中的MyHandler.class 为 implements MethodParametersHandler ,参数组装的具体实现
@RestController
@RequestMapping("/test/debounce/")
public class DebounceController {
@PostMapping(value = "/post")
@Debounce(value = 10, handler = MyHandler.class , delayClose = true, methodParameters = 0)
public TResponseObject post(@RequestBody MyRequest request) {
return TResponseObject.Success("Success");
}
}