-
-
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秒