实际项目中经常会存在方法被重复执行的问题,或者同一个资源同时被多个用户操作的问题,如果这个资源不可以同时被多个请求操作,则需要锁住资源。
一般情况下我们会使用synchronized关键字,或者ReentrantLock类,redis等方式来实现锁资源的目的,但是这样做有一个不方便的地方就是,我在每一个方法都要重复的编写很多繁杂的代码
比如:
synchronized(user.userId){
******
}
下面是通过注解的方式实现细粒度锁 注解
/**
* 锁
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/**
* 锁定key 业务键
*/
String[] lockKey() default LOCK_KEY;
/**
* 获取锁失败报错信息
*/
String errorMsg() default "您手速太快了!";
}
自定义异常
public class LockException extends RuntimeException{
public LockException () {
super();
}
public LockException (String message) {
super(message);
}
public LockException (String message, Throwable cause) {
super(message, cause);
}
public LockException (Throwable cause) {
super(cause);
}
protected LockException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Aop切面
@Aspect
@Component
public class LockAspect {
private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
private static ThreadLocal<String> lockKey = new ThreadLocal<>();
private ExpressionParser parser = new SpelExpressionParser();
@Pointcut("@annotation(Lock)")
public void lockFilter() {
}
@Before("lockFilter()")
public void dataFilter(JoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Lock lock = signature.getMethod().getAnnotation(Lock.class);
String keyName = getKeyName(point,lock);
String redisKey = REDIS_LOCK_PREFIX+keyName;
RedisUtils redisUtils = SpringContextUtils.getBean(RedisUtils.class);
boolean result = redisUtils.setNx(redisKey,"1");
if(!result){
throw new LockException(lock.errorMsg());
}
//设置最高20秒的过期时间
redisUtils.expire(redisKey,20, TimeUnit.SECONDS);
try {
lockKey.set(redisKey);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放锁
*/
@AfterReturning("lockFilter()")
public void afterFilter(JoinPoint point){
releaseLock(point);
}
/**
* 释放锁
*/
@After("lockFilter()")
public void after(JoinPoint point){
releaseLock(point);
}
/**
* 释放锁
*/
@AfterThrowing(value = "lockFilter()",throwing = "ex")
public void throwFilter(JoinPoint point,Throwable ex){
if(ex instanceof LockException){
//如果是锁本身的异常不需要释放锁
return;
}
releaseLock(point);
}
private void releaseLock(JoinPoint point){
MethodSignature signature = (MethodSignature) point.getSignature();
Lock lock = signature.getMethod().getAnnotation(Lock.class);
String keyName = getKeyName(point,lock);
String redisKey = REDIS_LOCK_PREFIX+keyName;
RedisUtils redisUtils = SpringContextUtils.getBean(RedisUtils.class);
redisUtils.delete(redisKey);
String key = lockKey.get();
if(!ObjectUtils.isEmpty(key)){
redisUtils.delete(key);
}
}
private String getKeyName(JoinPoint joinPoint,Lock lock){
Object[] args = joinPoint.getArgs();
String[] keys = lock.lockKey();
Method method = getMethod(joinPoint);
List<String> definitionKeyList = new ArrayList<>();
for (String definitionKey : keys) {
if (!ObjectUtils.isEmpty(definitionKey)) {
EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, nameDiscoverer);
Object objKey = parser.parseExpression(definitionKey).getValue(context);
definitionKeyList.add(ObjectUtils.nullSafeToString(objKey));
}
}
return method.getName()+"-"+ StringUtils.join(definitionKeyList,"-");
}
private Method getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method.getDeclaringClass().isInterface()) {
try {
method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),
method.getParameterTypes());
} catch (Exception e) {
e.printStackTrace();
}
}
return method;
}
}
使用
@Override
@Lock(lockKey = {"#user.userId","#user.userName"})
public Integer addUserTest1(User user) throws InterruptedException {
Thread.sleep(1000);
System.out.println("方法addUserTest1被调用");
throw new RuntimeException("有异常");
}