Spring AOP + Redis + 注解实现redis 分布式锁

关键字:RedisLock 锁 Spring  Boot MVC  分布式锁 基于注解

先上效果,rediskey 为hello+第一个从参数的userid  最大等待时间为5000毫秒,占用锁之后的自动释放时间为5秒,如果5秒内方法体执行完成,AOP代码会手动释放锁,但是对于写业务的人来说是透明的。

@Override
    @AddLock(key = "'hello'+#p0.userId", maxWait = 5000, timeout =5)
    public void sayHello(FrontUserVO user){
        try {
            log.info("拿到了锁,准备睡眠");
            Thread.sleep(6);
            log.info("你好" + user.getUserId());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

  AOP代码,这里是核心,主要是先拼接rediskey,然后是是否获取到锁和释放锁的判断,也没啥。

/**
 * redis分布式锁实现
 * @ProjectName: framework_v2_idea2
 * @Package: com.ylm.lock.aop
 * @ClassName: AddLockAspect
 * @Author: JackWang
 * @CreateDate: 2019/1/8 0008 14:19
 * @UpdateUser: JackWang
 * @UpdateDate: 2019/1/8 0008 14:19
 * @Version: 1.0
 */
@Aspect
@Component
public class AddLockAspect {

    /**
     * 日志记录
     */
    private static final Logger LOG = Logger.getLogger(AddLockAspect.class);

    @Resource
    private RedisLockService redisLockService;

    @Pointcut("@annotation(com.ylm.lock.annotations.AddLock)")
    public void addLockAnnotationPointcut() {

    }

    @Around(value = "addLockAnnotationPointcut()")
    public Object addKeyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //定义返回值
        Object proceed = null;
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Object target = joinPoint.getTarget();
        Object[] arguments = joinPoint.getArgs();
        AddLock annotation = AnnotationUtils.findAnnotation(targetMethod, AddLock.class);
        String spel=null;
        if(annotation != null){
            spel = annotation.key();
        }
        //前置方法 开始
        String redisKey =  "fhslock:" + SpelUtil.parse(target,spel, targetMethod, arguments);
        LOG.infoMsg("添加redisKey={}",redisKey);
        boolean isLock = false;
        long excuteTime = 0;
        try {
            //true代表成功了,false代表加锁失败
            isLock = redisLockService.addRedisLock(redisKey,annotation.maxWait(),annotation.timeout());
            // 目标方法执行
            if(isLock){
                excuteTime = new Date().getTime();
                proceed = joinPoint.proceed();
            }
        } catch (Exception exception) {
            //如果我自己加锁成功,出了异常则将锁释放掉
            if(isLock) {
                redisLockService.delRedisLock(redisKey);
            }
            throw exception;
        } finally {
            //加锁失败抛出异常
            if (!isLock) {
                throw new BusinessException(redisKey + "add LockTimeout,key:" + redisKey);
            }
            else
            {
                //如果当前时间 减去 service方法开始执行时间大于timeout的话,可能redis已经自动释放锁了,所以这边不在主动释放锁
                if((new Date().getTime() - excuteTime) < annotation.timeout()*1000)
                {
                    redisLockService.delRedisLock(redisKey);
                }
                return proceed;
            }
        }
    }


}

 

 

 注解代码

package com.ylm.lock.annotations;

import java.lang.annotation.*;

/**
 * by jackwang
 * redis分布式锁注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AddLock {

    /**
     * 锁的key
     * @return 锁的key
     */
    String key();

    /**
     * 最大等待时间 毫秒
     * @return 最大等待时间毫秒
     */
    long maxWait();

    /**
     * 锁最大占用时间秒
     * @return 锁最大占用时间秒
     */
    int timeout();
}

服务类



/**
 *
 * 内存数据库Redis的辅助类,负责对内存数据库的所有操作
 *
 * @author wangpengfei
 * @version [版本号, 2016年7月22日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service("redisLockServiceImpl")
@DataSource("default")
public class RedisLockServiceImpl implements RedisLockService
{
    /**
     * 日志记录
     */
    private static final Logger LOG = Logger.getLogger(RedisLockServiceImpl.class);


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * redisLock工具类
     */
    private RedisLock redisLock;



    @Override
    public boolean addRedisLock(String lockMark)
    {
        try
        {
            // 加锁失败或者异常后,返回false
            redisLock = new RedisLock(lockMark, redisTemplate);
            LOG.info("加锁:" + lockMark);
            return redisLock.lock(5000, 1 * 60);
        }
        catch (Exception e)
        {
            LOG.error("加锁失败,key:" + lockMark + ",异常:", e);
            // 异常之后发邮件通知管理员
            return false;
        }
    }



    @Override
    public boolean checkLockExit(String lockMark, long timeout)
    {
        try
        {
            // 加锁失败或者异常后,返回false
            redisLock = new RedisLock(lockMark, redisTemplate);
            LOG.warn("判断锁是否存在:" + lockMark);
            return redisLock.checkLockExit(timeout);
        }
        catch (Exception e)
        {
            LOG.error("加锁失败,key:" + lockMark + ",异常:", e);
            // 异常之后发邮件通知管理员
            return false;
        }
    }



    @Override
    public boolean addRedisLock(String lockMark, long timeout, int expire)
    {
        try
        {
            // 加锁失败或者异常后,返回false
            redisLock = new RedisLock(lockMark, redisTemplate);
            LOG.info("addLock:" + lockMark);
            return redisLock.lock(timeout, expire);
        }
        catch (Exception e)
        {
            LOG.error("add Lock Error,key:" + lockMark + ",异常:", e);
            // 异常之后发邮件通知管理员
            return false;
        }
    }

    @Override
    public void delRedisLock(String lockMark)
    {
        try
        {
            redisLock = new RedisLock(lockMark, redisTemplate);
            LOG.warn("解锁:" + lockMark);
            redisLock.unlock();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            LOG.error("删除锁失败,key:" + lockMark + ",异常:", e);
            // 异常之后发邮件通知管理员
        }
    }
}

redislock对象

public class RedisLock
{
    private static Logger LOG = Logger.getLogger(RedisLock.class);

    /** 加锁标志 */
    public static final String LOCKED = "TRUE";

    /** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */
    public static final long MILLI_NANO_CONVERSION = 1000 * 1000L;

    /** 默认超时时间(毫秒) */
    public static final long DEFAULT_TIME_OUT = 5000;

    public static final Random RANDOM = new Random();

    /** 锁的超时时间(秒),过期删除 */
    public static final int EXPIRE = 5 * 60;

    private RedisTemplate redisTemplate;


    private String key;

    // 锁状态标志
    private boolean locked = false;

    public RedisLock(String key, RedisTemplate redisTemplate)
    {
        Date date = new Date();
        try
        {   this.redisTemplate = redisTemplate;
        }
        catch (Exception e)
        {
            LOG.error("获取redis连接错误", e);
        }
        this.key = key + "_lock";
    }

    /**
     * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
     *
     * @param timeout 超时时间
     * @return 成功或失败标志
     */
    public boolean lock(long timeout)
    {
        long nano = System.nanoTime();
        timeout *= MILLI_NANO_CONVERSION;
        try
        {
            while ((System.nanoTime() - nano) < timeout)
            {
                if (setNx(this.key,LOCKED))
                {
                    redisTemplate.expire(this.key, EXPIRE, TimeUnit.SECONDS);
                    this.locked = true;
                    // LOG.warn("the key is lock -- " + this.key + " exe param log :\n"
                    // + StackTraceElementUtils.getStackStr(3));
                    return this.locked;
                }
                // 短暂休眠,避免出现活锁
                Thread.sleep(100, RANDOM.nextInt(500));
            }
        }
        catch (Exception e)
        {
            LOG.error(this, e);
            throw new RuntimeException("Locking error", e);
        }
        // LOG.warn(StackTraceElementUtils.getStackStr(3) + " \n lock error " + this.key);
        return false;
    }

    public boolean checkLockExit(long timeout)
    {
        long nano = System.nanoTime();
        timeout *= MILLI_NANO_CONVERSION;
        try
        {
            while ((System.nanoTime() - nano) < timeout)
            {
                RedisConnection connection = getConnection();
                if (connection.exists(redisTemplate.getStringSerializer().serialize(key)))
                {
                    closeRedisConnection(connection);
                    return true;
                }
                closeRedisConnection(connection);
                // 短暂休眠,避免出现活锁
                Thread.sleep(100, RANDOM.nextInt(500));
            }
        }
        catch (Exception e)
        {
            LOG.error(this, e);
            throw new RuntimeException("Locking error", e);
        }
        // LOG.warn(StackTraceElementUtils.getStackStr(3) + " \n lock error " + this.key);
        return false;
    }

    /**
     * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
     *
     * @param timeout 超时时间(毫秒)
     * @param expire 锁的超时时间(秒),过期删除
     * @return 成功或失败标志
     */
    public boolean lock(long timeout, int expire)
    {
        long nano = System.nanoTime();
        timeout *= MILLI_NANO_CONVERSION;
        try
        {
            while ((System.nanoTime() - nano) < timeout)
            {
                if (setNx(this.key, LOCKED))
                {
                    LOG.debug("获取到lock" + this.key);
                    this.redisTemplate.expire(this.key, expire,TimeUnit.SECONDS);
                    this.locked = true;
                    return this.locked;
                }
                // 短暂休眠,避免出现活锁
                Thread.sleep(10);
            }
        }
        catch (Exception e)
        {
            LOG.error("加锁失败", e);
            throw new RuntimeException("Locking error", e);
        }
        return false;
    }

    /**
     * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
     *
     * @return 成功或失败标志
     */
    public boolean lock()
    {
        return lock(DEFAULT_TIME_OUT);
    }

    /**
     * 解锁 无论是否加锁成功,都需要调用unlock 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用
     */
    public void unlock()
    {
        try
        {
            RedisConnection connection =  getConnection();
            connection.del(redisTemplate.getStringSerializer().serialize(this.key));
            closeRedisConnection(connection);
        }
        catch (Exception e)
        {
            LOG.error("解锁错误:" + this.key, e);
        }
    }

    /**
     * 封装设置分布式锁api
     * @param keyStr
     * @param valueStr
     * @return
     */
    private Boolean setNx(String keyStr, String valueStr){
        RedisSerializer<String> redisSerializer = this.redisTemplate.getStringSerializer();
        byte[] key = redisSerializer.serialize(keyStr);
        byte[] value = redisSerializer.serialize(valueStr);
        RedisConnection connection = getConnection();
        boolean  result = connection.setNX(key,value);
        closeRedisConnection(connection);
        return result;
    }

    /**
     * 归还 connection
     * @param connection connection
     */
    private void closeRedisConnection(RedisConnection connection){
        if(connection!=null && !connection.isClosed())
        {
            connection.close();
        }
    }

    /**
     * 获取redis连接
     * @return
     */
    private RedisConnection getConnection(){
        return redisTemplate.getConnectionFactory().getConnection();
    }

表达式解析:

 

/**
 *Spel 表达式解析器
 * @ProjectName: framework_v2_idea2
 * @Package: com.ylm.common.spring
 * @ClassName: SpelUtil
 * @Author: JackWang
 * @CreateDate: 2019/1/8 0008 14:24
 * @UpdateUser: JackWang
 * @UpdateDate: 2019/1/8 0008 14:24
 * @Version: 1.0
 */
public class SpelUtil {

    public static String parse(String spel, Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(spel).getValue(context, String.class);
    }

    /**
     * 支持 #p0 参数索引的表达式解析
     * @param rootObject 根对象,method 所在的对象
     * @param spel 表达式
     * @param method ,目标方法
     * @param args 方法入参
     * @return 解析后的字符串
     */
    public static String parse(Object rootObject,String spel, Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(spel).getValue(context, String.class);
    }

}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值