此文檔只粗略的講解實現思路,具體的實現邏輯還須要針對業務區別處理。數組
由於此業務中有讀和寫的操做,寫的執行條件依賴於讀,併發條件下可能出現讀到相同的條件都可以去執行寫操做,此時寫就會出現髒數據,。因此項目須要實現,在處理業務時,加鎖防止併發問題,此處利用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"); } }