-
-
2、编写切面
-
1、编写注解
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; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RedisLock { String key(); // 并发锁key String value(); // 锁定时长 默认单位秒 long ttl() default 5; }
2、编写切面
import java.lang.reflect.Method; 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.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import com.yun.common.api.redis.RedisService; import com.yun.common.base.response.BusinessException; import com.yun.common.base.utils.AssertUtil; import lombok.extern.slf4j.Slf4j; @Aspect @Component @Slf4j public class RedisLockAspect { @Autowired private RedisService redisService; @Pointcut("execution(public * com.yun.*.app.*.service..*.*(..))") public void appPointCut(){}; @Pointcut("execution(public * com.yun.*.domain..*.*(..))") public void domainPointCut(){}; @Around("(appPointCut() && @annotation(redisLock)) || (domainPointCut() && @annotation(redisLock)))") public Object before(ProceedingJoinPoint pJoinPoint, RedisLock redisLock) throws Throwable { String key = redisLock.key(); String value = redisLock.value(); long time = redisLock.ttl(); AssertUtil.notNull(key, "获取并发锁key为空。", key); AssertUtil.notNull(value, "获取并发锁value为空。", value); value = this.getRedisKey(pJoinPoint, value); String lockKey = key.concat(value); // 1、获取锁 RLock lock = redisService.getRLock(lockKey); // 2、锁定 try { AssertUtil.isTrue(lock.tryLock(time, TimeUnit.SECONDS), "获取并发锁[%s]失败。", lockKey); // 3、业务逻辑 return pJoinPoint.proceed(); } catch (BusinessException e) { log.error("获取锁失败。", e); throw e; }finally { // 4、释放锁 lock.unlock(); } } private String getRedisKey(ProceedingJoinPoint pJoinPoint, String key) { //使用SpringEL表达式解析注解上的key SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(key); //获取方法入参 Object[] parameterValues = pJoinPoint.getArgs(); //获取方法形参 MethodSignature signature = (MethodSignature)pJoinPoint.getSignature(); Method method = signature.getMethod(); DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = nameDiscoverer.getParameterNames(method); if (parameterNames == null || parameterNames.length == 0) { //方法没有入参,直接返回注解上的key return key; } //解析表达式 EvaluationContext evaluationContext = new StandardEvaluationContext(); // 给上下文赋值 for(int i = 0 ; i < parameterNames.length ; i++) { evaluationContext.setVariable(parameterNames[i], parameterValues[i]); } try { Object expressionValue = expression.getValue(evaluationContext); if (expressionValue != null && !"".equals(expressionValue.toString())) { //返回el解析后的key return expressionValue.toString(); }else{ //使用注解上的key return key; } } catch (Exception e) { //解析失败,默认使用注解上的key return key; } } }
3.1、方式一:编码方式
-
注入RedisService
-
应用分布式锁
适用场景:逻辑复杂,长事务场景。
注意:
1、存在第三方调用逻辑时,必须指定超时时间,且超时时间必须小于锁定时间。
2、应尽量提炼业务,缩短锁定范围。
3、合理设置锁定时间,避免出现锁超时的情况。
3.1.1、注入RedisService
@Autowired
protected RedisService redisService;
3.1.2、应用分布式并发锁
// 1、获取锁
RLock lock = redisService.getRLock(key);
// 2、锁定
AssertUtil.isTrue(lock.tryLock(20, TimeUnit.SECONDS), ResultEnum.DATA_LOCKED);
try {
//TODO 3、业务逻辑
} catch (Exception e) {
log.error("业务异常", e);
} finally {
// 4、释放锁
lock.unlock();
}
3.2、方式二:注解方式
-
添加注解
适用场景:逻辑简单,耗时短。
注意:合理设置锁定时间,避免出现锁超时的情况。
如下:
@RedisLock(key = ProductRedisKey.TEST_KEY, value = "#listProductQuery.orderNo", ttl = 20)
@Override
public List<ProductVO> list(ListProductQuery listProductQuery) {
// TODO 业务逻辑
return BeanCopierUtil.copyPropertiesOfList(
productDubboService
.list(BeanCopierUtil.copyProperties(listProductQuery, ListProductDTO.class)),
ProductVO.class);
}
参数解释:
@RedisLock注解可以添加在应用端的 com.yun.*.app.*.service 下
可以添加在业务端的 com.yun.*.domain 下
key 表示锁的目录,比如“system:test:”
value为锁定的具体业务单据号"123456",可以使用el表达式,比如 #listProductQuery.orderNo
ttl为锁定时间,默认为5秒