博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring AOP + Redis + 注解实现redis 分布式锁
阅读量:4071 次
发布时间:2019-05-25

本文共 12859 字,大约阅读时间需要 42 分钟。

关键字: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@Componentpublic 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@Documentedpublic @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
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);    }}

 

转载地址:http://vgwni.baihongyu.com/

你可能感兴趣的文章
Java-IO-java的IO流
查看>>
Java-IO-输入/输出流体系
查看>>
Java实现DES加密解密
查看>>
HTML基础
查看>>
Java IO
查看>>
Java NIO
查看>>
Java大数据:Hbase分布式存储入门
查看>>
大数据学习:Spark RDD操作入门
查看>>
大数据框架:Spark 生态实时流计算
查看>>
大数据入门:Hive和Hbase区别对比
查看>>
大数据入门:ZooKeeper工作原理
查看>>
大数据入门:Zookeeper结构体系
查看>>
大数据入门:Spark RDD基础概念
查看>>
大数据入门:SparkCore开发调优原则
查看>>
大数据入门:Java和Scala编程对比
查看>>
大数据入门:Scala函数式编程
查看>>
【数据结构周周练】002顺序表与链表
查看>>
C++报错:C4700:使用了非初始化的局部变量
查看>>
【数据结构周周练】003顺序栈与链栈
查看>>
C++类、结构体、函数、变量等命名规则详解
查看>>