基于切面和注解的Redis分布式实现

描述
  • 基于SpringEL表达式,动态配置
  • 基于切面,无缝切入
  • 支持获取锁失败时的行为,抛出异常还是继续等待,两种方式的锁,一种等待重试,一种直接退出

https://github.com/shawntime/shawn-common-utils/tree/master/src/main/java/com/shawntime/common/lock

使用方法
@RedisLockable(key = {"#in.activityId", "#in.userMobile"}, expiration = 120, isWaiting = true, retryCount = 2)
@Override
public PlaceOrderOut placeOrder(OrderIn in) {
    // ------
}
代码实现
package com.shawntime.common.lock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * RUNTIME
 * 定义注解
 * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
 * @author shma1664
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLockable {

    String prefix() default "";

    String[] key() default "";

    long expiration() default 120;

    boolean isWaiting() default false; //锁是否等待,默认为不等待

    int retryCount() default -1; // 锁等待重试次数,-1未不限制
}
package com.shawntime.common.lock;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Joiner;
import com.shawntime.common.cache.redis.SpringRedisUtils;
import com.shawntime.common.common.spelkey.KeySpELAdviceSupport;
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.aop.support.AopUtils;
import org.springframework.stereotype.Component;

/**
 * Created by IDEA
 * User: mashaohua
 * Date: 2016-09-28 18:08
 * Desc:
 */
@Aspect
@Component
public class RedisLockInterceptor extends KeySpELAdviceSupport {

    @Pointcut("@annotation(com.shawntime.common.lock.RedisLockable)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {

        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), point.getTarget().getClass());
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        Object target = point.getTarget();
        Object[] arguments = point.getArgs();

        RedisLockable redisLock = targetMethod.getAnnotation(RedisLockable.class);
        long expire = redisLock.expiration();
        String redisKey = getLockKey(redisLock, targetMethod, targetName, methodName, target, arguments);
        boolean isLock;
        if (redisLock.isWaiting()) {
            isLock = waitingLock(redisKey, expire, redisLock.retryCount());
        } else {
            isLock = noWaitingLock(redisKey, expire);
        }
        if (isLock) {
            long startTime = System.currentTimeMillis();
            try {
                return point.proceed();
            } finally {
                long parseTime = System.currentTimeMillis() - startTime;
                if (parseTime <= expire * 1000) {
                    unLock(redisKey);
                }
            }
        } else {
            throw new RuntimeException("您的操作太频繁,请稍后再试");
        }
    }

    private String getLockKey(RedisLockable redisLock, Method targetMethod, String targetName, String methodName,
                              Object target, Object[] arguments) {

        String[] keys = redisLock.key();
        String prefix = redisLock.prefix();
        StringBuilder sb = new StringBuilder("lock.");
        if (StringUtils.isEmpty(prefix)) {
            sb.append(targetName).append(".").append(methodName);
        } else {
            sb.append(prefix);
        }
        if (keys != null) {
            String keyStr = Joiner.on("+ '.' +").skipNulls().join(keys);
            SpELOperationContext context = getOperationContext(targetMethod, arguments, target, target.getClass());
            Object key = generateKey(keyStr, context);
            sb.append("#").append(key);
        }
        return sb.toString();
    }

    /**
     * 加锁
     *
     * @param key    redis key
     * @param expire 过期时间,单位秒
     * @return true:加锁成功,false,加锁失败
     */
    private boolean noWaitingLock(String key, long expire) {

        long value = System.currentTimeMillis() + expire * 1000;
        boolean status = SpringRedisUtils.setNX(key, value);

        if (status) {
            return true;
        }

        long oldExpireTime = SpringRedisUtils.get(key, Long.class);
        if (oldExpireTime < System.currentTimeMillis()) {
            //超时
            long newExpireTime = System.currentTimeMillis() + expire * 1000;
            Long currentExpireTime = SpringRedisUtils.getSet(key, newExpireTime, Long.class);
            if (currentExpireTime == null) {
                return true;
            }
            if (currentExpireTime.longValue() == oldExpireTime) {
                return true;
            }
        }
        return false;
    }

    /**
     * 等待锁
     *
     * @param key    redis key
     * @param expire 过期时间,单位秒
     * @return true:加锁成功,false,加锁失败
     */
    private boolean waitingLock(String key, long expire, int retryCount) {
        int count = 0;
        while (retryCount == -1 || count <= retryCount) {
            if (noWaitingLock(key, expire)) {
                return true;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
        }
        return false;
    }

    private void unLock(String key) {
        SpringRedisUtils.delete(key);
    }

}

转载于:https://my.oschina.net/shma1664/blog/826911

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值