描述
- 基于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);
}
}
网友评论