Spring aop优雅实现redis分布式锁 aop应用redis分布式锁

redis分布式锁切面实现

# 说明

网上找了一部分aop实现分布式锁的设计,感觉都不是特别好用,就自己写了一份。可以满足绝大部分分布式锁需求,请直接看代码,方法名很明了,注释也很明了啦,希望您看到不足的地方或者有不同见解的,还请再评论里回复,我会十分高兴和您探讨,十分感谢。

# @DistributedLock

注解定义

```java

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import java.util.concurrent.TimeUnit;

/**

* @author Zl

* @date 2019/8/2

* @since

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DistributedLock {

    /**

    * 过期时间

    * 时间按时间单位换算

    * @return

    */

    long expire() default 3;

    /**

    * 等待时长

    * 时间按时间单位换算

    * 当为0时,不等待 默认不等待

    *

    * @return

    */

    long waitTime() default 0;

    /**

    * 时间单位 默认为秒

    *

    * @return

    */

    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**

    * springEl表达式

    * 为空时取方法名称锁方法

    *

    * @return

    */

    String key() default "";

    /**

    * 定义lock作用域,避免key重复

    * 为空时取类完整包名

    *

    * @return

    */

    String lockName() default "";


    /**

    * 异常i18n编码定义

    * 用于获取失败后做异常信息抛出

    * @return

    */

    @AliasFor("errorCode")

    String value();

    @AliasFor("value")

    String errorCode() default "";

}

```

# RedisLockAspect

redis分布式锁切面处理

```java

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.expression.EvaluationContext;

import org.springframework.expression.ExpressionParser;

import org.springframework.expression.spel.standard.SpelExpressionParser;

import org.springframework.expression.spel.support.StandardEvaluationContext;

import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

import java.util.concurrent.TimeUnit;

/**

* @author Zl

* @date 2019/8/2

* @since

*/

@Slf4j

@Aspect

@Component

public class RedisLockAspect {

    private ExpressionParser parser = new SpelExpressionParser();

    @Autowired

    private RedisLockUtils redisLockUtils;

    @Pointcut("@annotation(com.ztesoft.zsmart.nros.base.annotation.DistributedLock)")

    public void pointCut() {

    }

    @Around("pointCut()")

    public Object around(ProceedingJoinPoint point) throws Throwable {

        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

        String className = point.getTarget().getClass().getName();

        Object[] args = point.getArgs();

        String[] paramNames = signature.getParameterNames();

        //参数写入SpringEl域中

        EvaluationContext context = new StandardEvaluationContext();

        for (int i = 0; i < args.length; i++) {

            context.setVariable(paramNames[i], args[i]);

        }

        //获取切面注解

        DistributedLock lock = method.getAnnotation(DistributedLock.class);

        TimeUnit timeUnit = lock.timeUnit();

        //redis key过期时间

        long expire = timeUnit.toMillis(lock.expire());

        //获取锁等待时间

        long waitTime = timeUnit.toMillis(lock.waitTime());

        //业务i18n异常编码

        String errorCode = lock.value();

        //key为空时锁方法,否则按SpringEl表达式取值

        String key = StringUtils.isEmpty(lock.key()) ? method.getName() : parser.parseExpression(lock.key()).getValue

                (context, String.class);

        //作用域为空时取className

        String lockName = StringUtils.isEmpty(lock.lockName()) ? className : lock.lockName();

        //构造redisKey

        String redisKey = lockName + "#" + key;

        try {

            if (redisLockUtils.setLock(redisKey, expire, waitTime)) {

                log.info("获取分布式锁成功,class={},method={},key={}", className, method, redisKey);

                //执行方法

                return point.proceed();

            }

        }

        catch (Exception e) {

            log.error("获取分布式锁错误,class={},method={},key={}", className, method, redisKey);

            ExceptionHandler.publish(errorCode, "", e);

        }

        finally {

            redisLockUtils.releaseLock(redisKey);

        }

        log.info("获取分布式锁失败,class={},method={},key={}", className, method, redisKey);

        //失败处理逻辑 此处抛出异常

        ExceptionHandler.publish(errorCode);

        return null;

    }

}

```

# RedisLockUtils

与redis的交互域

```java

import io.lettuce.core.SetArgs;

import io.lettuce.core.api.async.RedisAsyncCommands;

import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.connection.RedisConnection;

import org.springframework.data.redis.connection.ReturnType;

import org.springframework.data.redis.core.RedisCallback;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.script.DefaultRedisScript;

import org.springframework.data.redis.serializer.RedisSerializer;

import org.springframework.stereotype.Component;

import java.util.Optional;

/**

* redis 分布式锁Utils

*

* @author Zl

* @date 2019/8/2

* @since

*/

@Slf4j

@Component

public class RedisLockUtils {

    private static final DefaultRedisScript<String> UNLOCK_LUA;

    static {

    //构造脚本

        StringBuilder sb = new StringBuilder();

        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");

        sb.append("then ");

        sb.append("    return redis.call(\"del\",KEYS[1]) ");

        sb.append("else ");

        sb.append("    return 0 ");

        sb.append("end ");

        DefaultRedisScript<String> script = new DefaultRedisScript<>();

        script.setScriptText(sb.toString());

        UNLOCK_LUA = script;

    }

    private static final String LOCK_VALUE = "1";

    private static final String LOCK_HEARD = "lock:";

    @Autowired

    private RedisTemplate<String, String> redisTemplate;

    public boolean setLock(String key, String value, long expire) {

        if (StringUtils.isBlank(value)) {

            value = LOCK_VALUE;

        }

        return setNx(buildKey(key), value, expire);

    }

    public boolean setLock(String key, long expire) {

        return setNx(key, null, expire);

    }

    public boolean setLock(String key, long expire, long waitTime) {

        return setLock(key, null, expire, waitTime);

    }

    public boolean setLock(String key, String value, long expire, long waitTime) {

        if (waitTime == 0L) {

            return setLock(key, value, expire);

        }

        long start = System.currentTimeMillis();

        while (true) {

            //检测是否超时

            if (System.currentTimeMillis() - start > waitTime) {

                return false;

            }

            if (setLock(key, value, expire)) {

                return Boolean.TRUE;

            }

        }

    }

    public Optional<String> getLockValue(String key) {

        String o = redisTemplate.opsForValue().get(buildKey(key));

        return Optional.ofNullable(o);

    }

    public boolean releaseLock(String key) {

        return releaseLock(key, LOCK_VALUE);

    }

    public boolean releaseLock(String key, String value) {

        try {

            Object execute = redisTemplate.execute(

                    (RedisConnection connection) -> connection.eval(

                            UNLOCK_LUA.getScriptAsString().getBytes(),

                            ReturnType.INTEGER,

                            1,

                            buildKey(key).getBytes(),

                            value.getBytes())

            );

            return execute.equals(1L);

        } catch (Exception e) {

            log.error("release lock occured an exception", e);

        } finally {

        }

        return false;

    }

    /**

    * @param key        key值

    * @param value      value值

    * @param expiredTime 毫秒

    * @return

    */

    private boolean setNx(String key, String value, long expiredTime) {

        Boolean resultBoolean = null;

        try {

            resultBoolean = redisTemplate.execute((RedisCallback<Boolean>) connection -> {

                Object nativeConnection = connection.getNativeConnection();

                String redisResult = "";

                @SuppressWarnings("unchecked")

                RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();

                //lettuce连接包下序列化键值,否知无法用默认的ByteArrayCodec解析

                byte[] keyByte = stringRedisSerializer.serialize(key);

                byte[] valueByte = stringRedisSerializer.serialize(value);

                // lettuce连接包下 redis 单机模式setnx

                if (nativeConnection instanceof RedisAsyncCommands) {

                    RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;

                    //同步方法执行、setnx禁止异步

                    redisResult = commands

                            .getStatefulConnection()

                            .sync()

                            .set(keyByte, valueByte, SetArgs.Builder.nx().px(expiredTime));

                }

                // lettuce连接包下 redis 集群模式setnx

                if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {

                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;

                    redisResult = clusterAsyncCommands

                            .getStatefulConnection()

                            .sync()

                            .set(keyByte, keyByte, SetArgs.Builder.nx().px(expiredTime));

                }

                //返回加锁结果

                return "OK".equalsIgnoreCase(redisResult);

            });

        } catch (Exception e) {

            e.printStackTrace();

        }

        return resultBoolean != null && resultBoolean;

    }

    private String buildKey(String key) {

        return LOCK_HEARD + key;

    }

}

```

# 应用

```java

    /**

    * 锁className+#+test

    */

    @DistributedLock("*CENTER-100001")

    public void test(){}

    /**

    * 锁className+#+test1

    * key过期设置为100毫秒

    */

    @DistributedLock(value = "*CENTER-100001",expire = 100,timeUnit = TimeUnit.MILLISECONDS)

    public void test1(){}

    /**

    * 锁className+#+test2

    * 轮询10秒获取

    */

    @DistributedLock(value = "*CENTER-100001",waitTime = 10)

    public void test2(){}

    /**

    * 锁testNamespace+#+test3

    */

    @DistributedLock(value = "*CENTER-100001",lockName = "testNamespace")

    public void test3(){}

    /**

    * 锁className+#+id

    */

    @DistributedLock(value = "*CENTER-100001",key = "#id")

    public void test(String id){}

    /**

    * 锁className+#+id

    */

    @DistributedLock(value = "*CENTER-100001",key = "#o.id")

    public void test(Object o){}

```

如上,value与errorCode 按具体项目修改实现,有固定放回格式的可以采用返回错误返回值,这里时抛出异常信息构造i18nMassage。也可以考虑el表达式取值后加上方法名。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容