基于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 + ":解锁成功");
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44974020/article/details/121612546