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 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 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 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 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) connection -> {

                Object nativeConnection = connection.getNativeConnection();

                String redisResult = "";

                @SuppressWarnings("unchecked")

                RedisSerializer stringRedisSerializer = (RedisSerializer) 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表达式取值后加上方法名。

你可能感兴趣的:(Spring aop优雅实现redis分布式锁 aop应用redis分布式锁)