此文档只粗略的讲解实现思路,具体的实现逻辑还须要针对业务区别处理。数组
由于此业务中有读和写的操做,写的执行条件依赖于读,并发条件下可能出现读到相同的条件都可以去执行写操做,此时写就会出现脏数据,。因此项目须要实现,在处理业务时,加锁防止并发问题,此处利用Redis实现,可是若是多个业务都须要这么操做的话,其实操做Redis的代码是相同的,这样就显得麻烦,因此楼主采用注解的形式实现,具体方法见下述。并发
在请求调用Controller层时,RequestMapping 映射到的方法上加上注解,如自定义注解 @Debounce(防止屡次提交)。app
一、利用Redis实现并发锁的操做对Redis来讲实际上就是一种Key的操做,那么自定义注解@Debounce如何实现key的自定义且根据参数可变化?
二、如何实现调用请求真实的处理方法时的拦截?
三、什么状况下才会去作这个事情?分布式
利用处理请求的方法中的参数,实现动态定义,此时又有个问题,就是说若是时基本数据类型+String,这样的能够直接将值获取拼接,可是若是参数中有对象的话,同时又想用对象中的属性做为key值的一部分,那么直接拼接就行不通。像这种状况,统一的方式行不通,那么天然而然就会想到此处必须用到了拓展类,在上层只是定义这种功能,具体的实现由子类负责具体实现。(详见后述)。
在@Debounce注解中有定义一个处理参数数组,值为处理请求的方法中的参数位置Num,从0开始依次递增,同时也有个处理类class,做用是具体实现key值的拼接。ide
固然是利用代理实现,此处利用的是Spring的Cglib动态代理。同时利用Spring开放的拓展Bean处理的接口BeanPostProcessor,在bean实例化后,实例化Cglib代理。post
在Controller层即在有注解@Controller 或者 @RestController 的类中才会去判断是否须要作此操做。ui
@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; }
此处作参数参数值的拼接同时返回拼接后的数据this
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(); } } }
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锁 } } } }
@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"); } }