基于Redis的分布式锁(注解形式)

项目中需要使用分布式锁,且不止一处使用,所以提取为公共,具体代码实现如下:

工具类:

import java.util.HashMap;
import java.util.Map;

/**
 * 当前线程工具类,存放登录数据
 */
public class ThreadLocalUtil {

    private static ThreadLocal tlContext = new ThreadLocal();

    /**
     * 放入缓存
     *
     * @param key   键
     * @param value 数值
     */
    public static void put(Object key, Object value) {
        Map m = (Map) tlContext.get();
        if (m == null) {
            m = new HashMap();
            tlContext.set(m);
        }
        m.put(key, value);
    }

    /**
     * 获取缓存
     *
     * @param key 键
     */
    public static Object get(Object key) {
        Map m = (Map) tlContext.get();
        if (m == null) {
            return null;
        }
        return m.get(key);
    }

    /**
     * 清理
     *
     * @param key 键
     */
    public static void clear(Object key) {
        Map m = (Map) tlContext.get();
        if (m == null) {
            return;
        }
        m.remove(key);
    }
}
异常:

/**
 * redis 锁 异常
 */
public class RedisLockException extends RuntimeException {
    /**
     * Constructs a new runtime exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param message the detail message. The detail message is saved for
     *                later retrieval by the {@link #getMessage()} method.
     */
    public RedisLockException(String message) {
        super(message);
    }
}
注解:
import java.lang.annotation.*;

/**
 * 通过注解实现锁的添加
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {

    /**
     * key 默认为类名+方法名
     * 使用方法:
     * 1.String 字符串
     * 2.#args[]变量
     * 例如: #args[0]
     * #args[1].getName() 只支持无参方法调用
     */
    String key() default "";

    /**
     * 重新获取锁的间隔时间,默认100ms
     */
    long interval() default 100L;

    /**
     * 失效时间,默认10秒
     */
    long expireTime() default 10 * 1000L;

    /**
     * 阻塞时间,超时获取不到锁,抛异常 或走回调方法
     * 小于0 不等待
     */
    long timeout() default 5 * 1000L;
}
实现:

import com.base.core.util.ThreadLocalUtil;
import com.base.redis.err.RedisLockException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * RedisLock注解 具体实现
 */
@Aspect
@Slf4j
@AllArgsConstructor
public class RedisLockAspect {

    IRedisLockService redisService;
    SpelExpressionParser spelExpressionParser;
    DefaultParameterNameDiscoverer nameDiscoverer;
    private final static String KEY_PREFIX = "redisLock:";

    /**
     * 环绕通知  加锁 解锁
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(RedisLock)")
    public Object redisLockAop(ProceedingJoinPoint joinPoint) {
        RedisLock lock = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(RedisLock.class);
        String uuid = UUID.randomUUID().toString();
        String key = getKey(joinPoint, lock.key());
        log.info(Thread.currentThread().getName() + ":获取key:" + key);
        if (ThreadLocalUtil.get(key) != null) {
            //当前线程已经获取到锁 不需要重复获取锁。保证可重入性
            return execute(key, uuid, joinPoint);
        }
        if (redisService.tryLock(key, uuid, lock.expireTime(), lock.timeout(), lock.interval())) {
            ThreadLocalUtil.put(key, "");
            return execute(key, uuid, joinPoint);
        }
        throw  new RedisLockException("redis分布式锁异常");
    }

    private Object execute(String key, String uuid, ProceedingJoinPoint joinPoint) {
        //获取到锁进行标记 执行方法
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("redisAop方法执行异常:{}",throwable.toString());
        } finally {
            //方法执行结束 释放锁
            ThreadLocalUtil.clear(key);
            redisService.unLock(key, uuid);
        }
        return null;
    }


    /**
     * 根据参数 和注解 获取 redis key值
     *
     * @param joinPoint joinPoint
     * @param key key
     * @return key
     */
    public String getKey(ProceedingJoinPoint joinPoint, String key) {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        if ("".equals(key)) {
            //默认类名 + 方法名
            return className + methodName;
        }
        String suffixKey = generateKeyBySpEL(key, joinPoint);
        return KEY_PREFIX + className + ":" + methodName + ":" + suffixKey;

    }

    privateString generateKeyBySpEL(String expressionString, ProceedingJoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = spelExpressionParser.parseExpression(expressionString);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }
}
Redis锁

/**
 * redis锁 接口
 */
public interface IRedisLockService {
    /**
     *  加锁
     * @param key redis key
     * @param value redis value
     * @param expireTime 过期时间
     * @param timeout 获取不到锁超时时间
     * @param interval 重试间隔
     * @return
     */
    boolean tryLock(String key, String value, long expireTime, long timeout, long interval);

    /**
     * 解锁
     * @param key
     * @param value
     */
    void unLock(String key, String value);
}
Redis锁实现

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * redis 锁 接口实现
 */
@Slf4j
@AllArgsConstructor
public class RedisLockService implements IRedisLockService {

    RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean tryLock(String key, String value, long expireTime, long timeout, long interval) {
        if (interval <= 0) {
            //默认等待时间 30 毫秒
            interval = 30L;
        }
        if (timeout > 0) {
            long begin = System.currentTimeMillis();
            while (System.currentTimeMillis() - begin < timeout) {
                if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS))) {
                    log.info(Thread.currentThread().getName() + ":" + key + ":上锁");
                    return true;
                }
                //等待
                synchronized (Thread.currentThread()) {
                    log.info(Thread.currentThread().getName() + ":等待");
                    try {
                        Thread.currentThread().wait(interval);
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
            return false;
        } else {
            Boolean is = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
            return Boolean.TRUE.equals(is);
        }
    }

    @Override
    public void unLock(String key, String value) {
        Boolean delete = redisTemplate.delete(key);
        if (null != delete && delete) {
            log.info(Thread.currentThread().getName() + ":" + key + ":解锁成功");
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑猪撞地球QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值