近日在项目上,需要用到分布式锁,那肯定redis实现了,后来一想干脆写个切面吧,后期方便使用,于是查看了很多文章,然后照猫画虎的实现了,废话不多说,直接上代码!
maven使用包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.Order;
/**
* redis锁注解
*
* @author ssh
* @date 2022/11/01
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Order(value = 10)
public @interface RedisLock {
// 锁前缀
String lockPrefix() default "";
// 方法参数名(用于取参数名的值与锁前缀拼接成锁名),尽量不要用对象map等,对象会toString后与锁前缀拼接
String lockParameter() default "";
// 尝试加锁,最多等待时间(毫秒)
long lockWait() default 50L;
// 自动解锁时间 (毫秒)
long autoUnlockTime() default 2000L;
// 重试次数
int retryNum() default 3;
// 重试等待时间 (毫秒)
long retryWait() default 50L;
}
切面实现
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
/**
* Description: 分布式锁
* <p>
* 先获取锁, 获取不到则继续等待(指定时间), 失败次数(指定)次后跳出, 消费降级(抛出,系统繁忙稍后再试) 如果没有重试次数,方法返回null 记得捕获NP 当重试次数有, 但是重试间隔时间没写, 默认200ms 间隔
* </p>
*
* @author ssh
*/
@Aspect
@Slf4j
@Order(10)
public class RedisLockAspect {
private SpelExpressionParser spelParser = new SpelExpressionParser();
private static final String LOCK_NAME = "lockName";
private static final String lOCK_WAIT = "lockWait";
private static final String AUTO_UNLOCK_TIME = "autoUnlockTime";
private static final String RETRY_NUM = "retryNum";
private static final String RETRY_WAIT = "retryWait";
/**
* redis工具类
*/
@Resource
private RedissonClient redissonClient;
@Around("@annotation(com.biyao.bim.console.redisLock.RedisLock) && @annotation(redisLock)")
public Object lockAroundAction(ProceedingJoinPoint proceeding, RedisLock redisLock) throws Throwable {
// 获取注解中的参数
Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding, redisLock);
String lockName = (String) annotationArgs.get(LOCK_NAME);
Assert.notNull(lockName, "分布式,锁名不能为空");
int retryNum = (int) annotationArgs.get(RETRY_NUM);
long retryWait = (long) annotationArgs.get(RETRY_WAIT);
long lockWait = (long) annotationArgs.get(lOCK_WAIT);
long autoUnlockTime = (long) annotationArgs.get(AUTO_UNLOCK_TIME);
// 获取锁
RLock lock = redissonClient.getLock(lockName);
try {
boolean res = lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS);
if (res) {
// 执行主逻辑
log.info(String.format("{%s}加锁成功", lockName));
return proceeding.proceed();
} else {
// 如果重试次数为零, 则不重试
if (retryNum <= 0) {
log.info(String.format("{%s}已经被锁, 不重试", lockName));
throw new BaseException(ResultCode.SYSTEM_BUSY);
}
if (retryWait == 0) {
retryWait = 200L;
}
// 设置失败次数计数器, 当到达指定次数时, 返回失败
int failCount = 1;
while (failCount <= retryNum) {
// 等待指定时间ms
Thread.sleep(retryWait);
if (lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS)) {
// 执行主逻辑
return proceeding.proceed();
} else {
log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", lockName, failCount, retryNum,
retryWait));
failCount++;
}
}
throw new BaseException(ResultCode.SYSTEM_BUSY);
}
} catch (Throwable throwable) {
log.error(String.format("执行分布式锁发生异常锁名:{%s},异常名称:{%s}", lockName, throwable.getMessage()), throwable);
throw throwable;
} finally {
lock.unlock();
log.info(String.format("{%s}释放锁成功", lockName));
}
}
/**
* 获取锁参数
*
* @param proceeding
* @return
*/
private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding, RedisLock redisLock) throws Exception {
//获取方法的参数名和参数值
MethodSignature methodSignature = (MethodSignature) proceeding.getSignature();
List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
List<Object> paramList = Arrays.asList(proceeding.getArgs());
//将方法的参数名和参数值一一对应的放入上下文中
EvaluationContext ctx = new StandardEvaluationContext();
for (int i = 0; i < paramNameList.size(); i++) {
ctx.setVariable(paramNameList.get(i), paramList.get(i));
}
// 解析SpEL表达式获取结果
String value = spelParser.parseExpression(redisLock.lockParameter()).getValue(ctx).toString();
// //获取 sensitiveOverride 这个代理实例所持有的 InvocationHandler
// InvocationHandler invocationHandler = Proxy.getInvocationHandler(redisLock);
// // 获取 invocationHandler 的 memberValues 字段
// Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
// // 因为这个字段是 private final 修饰,所以要打开权限
// hField.setAccessible(true);
// // 获取 memberValues
// Map memberValues = (Map) hField.get(invocationHandler);
// // 修改 value 属性值
// memberValues.put("userType", Integer.parseInt(value));
// 解析SpEL表达式获取结果
Map<String, Object> result = new HashMap<String, Object>();
result.put(LOCK_NAME, redisLock.lockPrefix() + value);
result.put(lOCK_WAIT, redisLock.lockWait());
result.put(AUTO_UNLOCK_TIME, redisLock.autoUnlockTime());
result.put(RETRY_NUM, redisLock.retryNum());
result.put(RETRY_WAIT, redisLock.retryWait());
return result;
}
}
注解使用
这里用的是spel表达式哦
@Override
@Transactional(rollbackFor = RuntimeException.class)
@RedisLock(lockPrefix = "bim:console:group:lock:", lockParameter = "#groupMemberOperationParam.getGroupId()")
public void groupAddMember(GroupMemberOperationParam groupMemberOperationParam) {
}
大体就是这样,因为公司规定,已经把公司域名的包删除了,大家粘贴的时候将报错地方换成自己的使用就好!