分布式锁AOP实现
分布式锁
分布式锁AOP实现
添加注解类
package cn.rc100.common.security.annotation;
import java.lang.annotation.*;
/**
* @author xxx
* @title: RedisLock
* @description: TODO
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* 加锁业务键
*/
String lockName();
/**
* 加锁业务键后缀
*/
String suffix() default "";
/**
* 获得锁名时拼接前后缀用到的分隔符
*/
String separator() default "_";
/**
* <pre>
* 获取注解的方法参数列表的某个参数对象的某个属性值来作为lockName。因为有时候lockName是不固定的。
* 当param不为空时,可以通过argNum参数来设置具体是参数列表的第几个参数,不设置则默认取第一个。
* </pre>
*/
String param() default "";
/**
* 将方法第argNum个参数作为锁
*/
int argNum() default 0;
/**
* 尝试获取锁的等待时间(单位:毫秒)
* <p>
* 在等待时间内获取锁成功,返回true
* 等待时间结束了,还没有获取到锁那么返回false
* 默认300s
* </p>
*/
String waitTime() default "300000";
/**
* 分布式锁的租约时间(单位:毫秒)
* <p>
* 如果“不想”使用分布式锁的无限续租,则“指定”锁的租约时间
* 如果“想要”使用分布式锁的无限续租,则“无需指定”锁的租约时间(耗时比较久的业务)
* 默认使用无限续租
* </p>
*/
String leaseTime() default "-1";
/**
* 《预留》
* 是否支持降级(默认不支持)
* <p>
* 这里的降级指在`Redis`宕机时,不使用分布式锁,直接调用方法,尽可能保证服务内部业务逻辑的可用性(AP)
* 考虑并发场景下,是否会造成数据的不一致/脏数据问题,假如业务上需要确保一致性,则应该支持降级,否则不应该支持降级操作(CP)
* </p>
*/
boolean degrade() default false;
/**
* 是否忽略加锁失败的异常(默认不忽略)
*/
boolean ignoreFail() default false;
}
Redisson客户端
package cn.rc100.common.security.annotation;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
/**
* @author xxx
* @title: RedissonClientConfig
* @description: TODO
*/
@Configuration
@AllArgsConstructor
public class RedissonClientConfig {
@ApiModelProperty(value = "域名")
private String host;
@ApiModelProperty(value = "密码")
private String pwd;
@ApiModelProperty(value = "端口")
private String port;
private Environment environment;
@Autowired
public RedissonClientConfig(Environment environment) {
this.environment = environment;
}
@PostConstruct
public void init() {
host = environment.getProperty("spring.redis.host");
pwd = environment.getProperty("spring.redis.password");
port = environment.getProperty("spring.redis.port");
}
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(pwd);
return Redisson.create(config);
}
}
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.1</version>
</dependency>
切面
package cn.rc100.common.security.annotation;
import cn.hutool.core.util.ReflectUtil;
import jodd.util.StringUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author xxx
* @title: RedisLockAspect
* @description: TODO
*/
@Aspect
@Slf4j
@Component
@SuppressWarnings({"checkstyle:magicnumber"})
@AllArgsConstructor
public class RedisLockAspect {
private final RedissonClient redissonClient;
@Around("@annotation(cn.rc100.common.security.annotation.RedisLock) && @annotation(redisLock)")
public Object aroundLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("arguments", joinPoint.getArgs());
//所在类
Object[] arguments = joinPoint.getArgs();
final String key = getLockName(redisLock, arguments);
//String key = getLockName(parser, ctx, methodName, redisLock);
Long waitTime = Long.valueOf(!redisLock.waitTime().contains("arguments") ? redisLock.waitTime()
: parser.parseExpression(redisLock.waitTime()).getValue(ctx, String.class));
Long renewTime = Long.valueOf(!redisLock.leaseTime().contains("arguments") ? redisLock.leaseTime()
: parser.parseExpression(redisLock.leaseTime()).getValue(ctx, String.class));
RLock rLock = null;
Boolean isLocked = Boolean.FALSE;
try {
rLock = redissonClient.getLock(key);
if (Objects.isNull(rLock)) {
//降级规则
//if(redisLock.degrade()){
// return degradeHandler(joinPoint, key);
//}
throw new RuntimeException(String.format("RLock 获取失败, key:%s", key));
}
isLocked = tryLock(rLock, key, waitTime, renewTime);
if (isLocked) {
log.info(String.format("成功获取 RedisLock, key: %s ", key));
return joinPoint.proceed();
}
throw new RuntimeException(String.format("RedisLock 获取失败, key: %s", key));
} catch (RuntimeException e) {
if (redisLock.ignoreFail()) {
return null;
}
throw new RuntimeException();
} catch (Throwable throwable) {
log.error(String.format("获取 RedisLock 后业务逻辑执行失败, key: %s, error: %s",
key, throwable));
return null;
} finally {
unlock(isLocked, rLock, key);
}
}
private Boolean tryLock(RLock rLock, String key, Long waitTime, Long leaseTime) {
try {
return leaseTime != -1L ? rLock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)
: rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error(String.format("加 RedisLock 异常, key:%s, e:%s", key, e));
throw new RuntimeException();
}
}
private void unlock(Boolean isLocked, RLock rLock, String key) {
if (isLocked && rLock.isHeldByCurrentThread()) {
rLock.unlock();
log.debug(String.format("释放 RedisLock 成功, key: %s", key));
}
}
public String getLockName(RedisLock redisLock, Object[] args) {
try {
StringBuilder lName = new StringBuilder();
String lockName = redisLock.lockName(),
suffix = redisLock.suffix(),
separator = redisLock.separator(),
param = redisLock.param();
if (StringUtil.isNotEmpty(lockName)) {
lName.append(lockName);
}
if (args.length > 0) {
if (StringUtil.isNotEmpty(param)) {
Object arg;
if (redisLock.argNum() > 0) {
arg = args[redisLock.argNum() - 1];
} else {
arg = args[0];
}
lName.append(separator).append(String.valueOf(getParam(arg, param)));
} else if (redisLock.argNum() > 0) {
lName.append(separator).append(args[redisLock.argNum() - 1].toString());
}
}
if (StringUtil.isNotEmpty(suffix)) {
lName.append(separator).append(suffix);
}
lockName = lName.toString();
return lockName;
} catch (Exception e) {
throw new IllegalArgumentException("Can't get or generate lockName accurately!");
}
}
/**
* 从方法参数获取数据
*
* @param param
* @param arg 方法的参数数组
* @return
*/
public Object getParam(Object arg, String param) {
if (StringUtil.isNotEmpty(param) && arg != null) {
try {
Object result = ReflectUtil.getFieldValue(arg, param);
return result;
} catch (Exception e) {
throw new RuntimeException("", e);
}
}
return null;
}
}
使用示例1:
@RedisLock(lockName = "financialReceipt", param = "financialId", suffix = ".lock")
public R insert(FinancialReceiptRecordDTO receiptRecordDTO) {
//synchronized (this) {
Integer financialId = receiptRecordDTO.getFinancialId();
Financial financial = financialService
.getOne(Wrappers.<Financial>lambdaQuery().eq(Financial::getId, financialId)
.eq(Financial::getDelFlag, false));
if (financial == null) {
return R.failed("未找到当前财务信息!");
}
//应收金额
BigDecimal receivable = financial.getReceivable() == null ? new BigDecimal(0) : financial.getReceivable();
//当前账务信息的:全部实收金额
BigDecimal netReceiptsTotal = financial.getNetReceipts() != null ?
financial.getNetReceipts().add(receiptRecordDTO.getNetReceipts()) : receiptRecordDTO
.getNetReceipts();
if (receivable.compareTo(netReceiptsTotal) == -1) {
return R.failed("总共实收金额已超出应收金额!");
}
List<ContractFileDTO> fileDTOList = receiptRecordDTO.getFileDTOList();
boolean flag = false;
if (fileDTOList != null && fileDTOList.size() > 0) {
//附件有无
flag = true;
}
EplusUser user = SecurityUtils.getUser();
receiptRecordDTO.setCreateDept(user.getDeptId());
receiptRecordDTO.setCreateUser(user.getId());
receiptRecordDTO.setCreateTime(LocalDateTime.now());
Random random = new Random(System.currentTimeMillis());
receiptRecordDTO.setVoucherNo(Math.abs(random.nextLong()));
receiptRecordDTO.setAnyAttachment(flag ? 1 : 0);
//保存实收记录
save(receiptRecordDTO);
//保存附件
contractFileService.saveFile(fileDTOList, receiptRecordDTO.getId());
//修改当前账务信息
financial.setUpdateTime(LocalDateTime.now());
financial.setNetReceipts(netReceiptsTotal);
financial.setCollectionTime(LocalDateTime.now());
financial.setOperatorUser(user.getId());
financial.setAnyAttachment(flag ? 1 : 0);
financial.setCollectionTime(receiptRecordDTO.getCreateTime());
financial.setCollectionAccount(receiptRecordDTO.getCollectionAccount());
return R.ok(financialService.updateById(financial));
//}
}
使用示例2:
@RedisLock(argNum = 1, lockNamePost = ".lock")
public Integer aspect(String i) {
RMap<String, Integer> map = redissonClient.getMap("redisLockTest");
Integer count = map.get("count");
if (count > 0) {
count = count - 1;
map.put("count", count);
}
return count;
}
使用示例3:
@RedisLock(lockName = "stock", waitTime = "200000")
public void insert(Integer id) {
Stock stock = service.getById(id);
Integer num = stock.getNum();
--num;
if (num < 0) {
return;
}
System.out.println("num=" + num);
stock.setNum(num);
stock.updateById();
}
@RedisLock(lockName = "lock", expire = "#arguments[1]")
public Long createXxxWithLock(String key, Long sleepTime, String commLog){
}
参考:
https://www.cnblogs.com/sprinkle/p/8366414.html
https://qimok.cn/1117.html
本文来自博客园,作者:随风笔记,转载请注明原文链接:https://www.cnblogs.com/sfbj/p/15834110.html

浙公网安备 33010602011771号