资讯专栏INFORMATION COLUMN

Controller层利用Redis实现分布式锁(注解实现)

kevin / 2706人阅读

摘要:实现逻辑在请求调用层时,映射到的方法上加上注解如自定义注解防止多次提交。针对第二个问题解决方法当然是利用代理实现,此处利用的是的动态代理。同时利用开放的拓展处理的接口在实例化后,实例化代理。

前言

此文档只粗略的讲解实现思路,具体的实现逻辑还需要针对业务区别处理。

需求

因为此业务中有读和写的操作,写的执行条件依赖于读,并发条件下可能出现读到相同的条件均可以去执行写操作,此时写就会出现脏数据,。所以项目需要实现,在处理业务时,加锁防止并发问题,此处利用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 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, 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 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");
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76537.html

相关文章

  • 一个两年Java的面试总结

    摘要:数据结构和算法树快速排序,堆排序,插入排序其实八大排序算法都应该了解一致性算法,一致性算法的应用的内存结构。如何存储一个的。八大排序算法一定要手敲一遍快排,堆排尤其重要。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。 前言 16年毕业到现在也近两年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,...

    anRui 评论0 收藏0
  • 【荐】令人心情愉悦的一次面试总结

    摘要:中四种修饰符的限制范围。数据结构和算法树快速排序,堆排序,插入排序其实八大排序算法都应该了解一致性算法,一致性算法的应用的内存结构。的部署方式,主从,集群。八大排序算法一定要手敲一遍快排,堆排尤其重要。 前言 15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有...

    20171112 评论0 收藏0
  • Java面试 32个核心必考点完全解析

    摘要:如问到是否使用某框架,实际是是问该框架的使用场景,有什么特点,和同类可框架对比一系列的问题。这两个方向的区分点在于工作方向的侧重点不同。 [TOC] 这是一份来自哔哩哔哩的Java面试Java面试 32个核心必考点完全解析(完) 课程预习 1.1 课程内容分为三个模块 基础模块: 技术岗位与面试 计算机基础 JVM原理 多线程 设计模式 数据结构与算法 应用模块: 常用工具集 ...

    JiaXinYi 评论0 收藏0
  • Java 最常见 200+ 面试题全解析:面试必备(附答案)

    摘要:的简称,运行环境,为的运行提供了所需环境。分割字符串,返回一个分割后的字符串数组。线程安全是线程安全的,而是非线程安全的。迭代器取代了集合框架中的,迭代器允许调用者在迭代过程中移除元素。 本文分为十九个模块,分别是: Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Clou...

    hufeng 评论0 收藏0

发表评论

0条评论

kevin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<