思路使用AOP,实现起来比较简单,直接上代码了:
注解类:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisLock {
int expireTime() default 60;
String key();
String prefix() default "";
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
切面:
@Aspect
@Component
@Slf4j
public class RedisLockInterceptor extends BaseInterceptor{
@Resource
private RedisDistributionLock redisDistributionLock;
@Pointcut("execution(* com.xes.payplatform.ledger.business.service..*(..)); @annotation(RedisLock))")
public void redisLock(){}
@Around("redisLock()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object res = new Object();
Boolean ifAbsent;
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(RedisLock.class)) {
RedisLock annotation = method.getAnnotation(RedisLock.class);
Object[] args = joinPoint.getArgs();
// 获取入参
Map<String, Object> reqMap = convertKeyValue(args[0]);
// 获取key对应的value值
String key = (String)reqMap.get(annotation.key());
// redis key
String redisKey = annotation.prefix() + key;
if (ifAbsent = redisDistributionLock.lock(redisKey, annotation.expireTime())) {
log.info("获取分布式锁成功|redisKey:{}", redisKey);
try {
res = joinPoint.proceed();
return res;
} catch (Exception e) {
throw e;
} finally {
try {
if(ifAbsent) {
redisDistributionLock.unlock(redisKey);
log.info("释放redis锁,key:{}", redisKey);
}
} catch (Exception ex){
log.error("删除分布式锁异常|redisKey:{},错误:{}", redisKey, ex);
}
}
}else {
throw new BusinessException("退费单号加锁失败,30分钟内请勿重复提交");
}
}
return res;
}
/**
* 将对象转换为map
*/
protected static Map<String, Object> convertKeyValue(Object obj){
Map<String, Object> map = new HashedMap();
List<Field> fieldList = new ArrayList<>() ;
// 递归获取所有父类里的属性
Class tempClass = obj.getClass();
while (tempClass != null) {
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass();
}
// 将字段及字段值映射到map中
for (Field f : fieldList) {
f.setAccessible(true);
try {
Object val = f.get(obj);
map.put(f.getName(), val);
}catch (IllegalAccessException e){
e.printStackTrace();
}
}
return map;
}
}
RedisDistributionLock工具类
/**
* 分布式锁工具
*
* @author angsen
*/
@Slf4j
@Component
public class RedisDistributionLock {
/**
* 过期时间:30分钟(秒)
*/
private final static int EXPIRE_TIME = 30 * 60;
/**
* 过期时间:30分钟(秒)
*/
private final static String KEY_PATH_STR = "DistributionLock:";
@Resource
private RedisTemplate redisTemplate;
private static final Long LOCK_RESULT = 1L;
/**
* 获取锁
* 通过eval命令保证代码执行的原子性
*/
public Boolean lock(String redisKey) {
return this.lock(redisKey, EXPIRE_TIME);
}
/**
* 获取锁
* @param redisKey 锁键
* @param expireSeconds 过期时间(秒)
* @return 成功/失败
*/
public Boolean lock(String redisKey, int expireSeconds) {
String lockKey = KEY_PATH_STR + redisKey;
log.info("RedisDistributionLock|lock --> 开始获取.key:{}", lockKey);
String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 0 then return 0 else redis.call('expire',KEYS[1],ARGV[2]) return 1 end";
Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), StringUtils.SPACE,
expireSeconds);
return Objects.equals(LOCK_RESULT,result);
}
/**
* 解锁
* @param redisKey 锁键
*/
public void unlock(String redisKey) {
String lockKey = KEY_PATH_STR + redisKey;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), StringUtils.SPACE);
log.info("RedisDistributionLock|unlock --> 解锁.key:{}, 结果:{}", lockKey, Objects.equals(LOCK_RESULT,result));
}
/**
* 分布式锁
* @param key
* @param value
* @param timeout 超时时间,单位:秒
* @return
*/
public Boolean setNX(String key, String value,long timeout) {
log.info("RedisDistributionLock|setNX --> 开始获取.key:{}", key);
return (Boolean)redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
boolean result=connection.setNX(redisTemplate.getKeySerializer().serialize(key), redisTemplate.getValueSerializer().serialize(value));
if(result){
connection.expire(redisTemplate.getKeySerializer().serialize(key),timeout);
}
log.info("RedisDistributionLock|setNX --> 获取结束.key:{},结果:{}", key, result);
return result;
}
}, true);
}
public void del(String key) {
redisTemplate.delete(key);
log.info("RedisDistributionLock|del --> 解锁.key:{}", key);
}
}
如何使用
@RedisLock(key="refundBillNo", prefix="refundForNormal:", expireTime=30*60)
public BaseResponse refund(T t) {
}