Controller層利用Redis實現分佈式鎖(註解實現)

前言

此文檔只粗略的講解實現思路,具體的實現邏輯還須要針對業務區別處理。數組

需求

由於此業務中有讀和寫的操做,寫的執行條件依賴於讀,併發條件下可能出現讀到相同的條件都可以去執行寫操做,此時寫就會出現髒數據,。因此項目須要實現,在處理業務時,加鎖防止併發問題,此處利用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

具體實現方法

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

此處作參數參數值的拼接同時返回拼接後的數據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();
        }
    }
}

方法攔截器定義 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");
    }
}
相關文章
相關標籤/搜索