参考:使用redis+切面完成分布式锁
使用了普通的spring-data-redis,没使用redission,所以没有延迟时间实现,可以查看原文使用redission的实现
- SpEL工具类,用于解析SpEL表达式
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
/**
* @author SYM
* @date 2022/6/6 9:09
* @Description EpEL工具类
*/
public class SpelUtils {
/**
* 支持 #p0 参数索引的表达式解析
* @param rootObject 根对象,method 所在的对象
* @param spel 表达式
* @param method ,目标方法
* @param args 方法入参
* @return 解析后的字符串
*/
public static String parse(Object rootObject,String spel, Method method, Object[] args) {
if (StringUtils.isEmpty(StringUtils.trim(spel))) {
return StringUtils.EMPTY;
}
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
if (StringUtils.isEmpty(paraNameArr)) {
return spel;
}
//使用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);
}
}
- 自定义注解
import java.lang.annotation.*;
/**
* @author SYM
* @date 2022/6/6 9:08
* @Description Redis分布式锁-注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* redis锁 名字
*/
String lockName() default "";
/**
* redis锁 key 支持spel表达式
*/
String key();
/**
* 过期时间(毫秒)
*/
long expire() default 5000;
/**
* 重试次数
* @return
*/
int numOfRetry() default 1;
/**
* 重试间隔时间(毫秒)
*/
long retryInterval() default 100;
}
- 切面
import com.ruoyi.common.core.utils.SpelUtils;
import com.ruoyi.common.redis.annatation.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author SYM
* @date 2022/6/6 9:09
* @Description Redis分布式锁-切面
*/
@Aspect
@Component
public class RedisLockAspect {
@Resource
private RedisTemplate redisTemplate;
private static final String REDIS_LOCK_PREFIX = "redis_lock:";
@Pointcut("@annotation(redisLock)")
public void pointCut(RedisLock redisLock) {
}
@Around("pointCut(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
Object result = null;
String spel = redisLock.lockName();
String key = redisLock.key();
Long lockValue = UUID.randomUUID().getMostSignificantBits();
String lockKey = getRedisKey(joinPoint, key, spel);
// 上锁
Boolean locked = tryLock(lockKey, lockValue, redisLock.expire());
if (!locked && redisLock.numOfRetry() > 0 && redisLock.retryInterval() > 0) {
// 重试
for (int i = 0; i < redisLock.numOfRetry(); i++) {
TimeUnit.MILLISECONDS.sleep(redisLock.retryInterval());
locked = tryLock(lockKey, lockValue, redisLock.expire());
if(locked){
break;
}
}
}
try {
if (locked) {
//执行方法
result = joinPoint.proceed();
} else {
throw new RuntimeException("该资源正在被操作");
}
} finally {
if(locked){
// 释放锁
RedisScript<Boolean> script = RedisScript.of("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end;", Boolean.class);
redisTemplate.execute(script, Arrays.asList(lockKey), lockValue);
}
}
return result;
}
/**
* 尝试上锁
*
* @param lockKey
* @param lockValue
* @param expire
* @return
*/
private Boolean tryLock(String lockKey, Long lockValue, Long expire) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expire, TimeUnit.MILLISECONDS);
}
/**
* 将spel表达式转换为字符串
*
* @param joinPoint 切点
* @return redisKey
*/
private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
Object target = joinPoint.getTarget();
Object[] arguments = joinPoint.getArgs();
return REDIS_LOCK_PREFIX + lockName + ":" + SpelUtils.parse(target, spel, targetMethod, arguments);
}
}

本文介绍了如何在Spring Boot应用中利用Spring Data Redis和自定义注解实现分布式锁。通过SpEL表达式解析锁的key,使用RedisTemplate的setIfAbsent方法尝试加锁,并在方法执行后释放锁,提供了重试机制来应对锁获取失败的情况。
1251

被折叠的 条评论
为什么被折叠?



