1、编写自定义注解@lock
import java.lang.annotation.*;
/**
* 锁自定义注解
* @author
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于参数或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/**
* 锁key
* @return
*/
String[] key() default {};
}
2、编写AOP切面
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
/**
* 锁aop
*/
@Aspect
@Component
public class LockAspect {
@Autowired
private DistributedRedisLock distributedRedisLock;
/**
* 方法执行前锁,执行结束后释放锁,如果未获取到锁的key则不加锁
* annotation内放自定义注解
*/
@Around("@annotation(com.util.Lock)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
try{
Object object = null;
//获取锁定的key
Lock lockEntity = (((MethodSignature) joinPoint.getSignature()).getMethod()).getAnnotation(Lock.class);
//key是否全部保存成功
boolean acquire = true;
List<String> keyArray = new ArrayList<>();
try{
//循环将注解中传递参数加锁
for (String k:lockEntity.key()) {
String keyBySpeL = getKeyBySpeL(k, joinPoint);
keyArray.add(keyBySpeL);
if(StringUtils.isNotBlank(keyBySpeL)){
//加锁
boolean lockStatus = distributedRedisLock.acquire(keyBySpeL);
if(!lockStatus){
acquire=false;
}
}
}
if(!acquire){
throw new Exception("同时操作一条数据,请稍后重试");
}else{
//执行方法
object = joinPoint.proceed();
}
}catch (Exception e1){
throw new Exception("同时操作一条数据,请稍后重试");
}finally {
//循环将所有锁释放
for (String k:keyArray) {
try{
if(StringUtils.isNotBlank(k)){
distributedRedisLock.release(k);
}
}catch (Exception es){
continue;
}
}
}
return object;
}catch (Exception e){
throw new Exception("同时操作一条数据,请稍后重试");
}
}
/**
* 用于SpEL表达式解析.
*/
private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private final DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 获取缓存的key
* key 定义在注解上,支持SPEL表达式
* @return
*/
public String getKeyBySpeL(String spel, ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
String[] paramNames = defaultParameterNameDiscoverer.getParameterNames(methodSignature.getMethod());
EvaluationContext context = new StandardEvaluationContext();
Object[] args = proceedingJoinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
return String.valueOf(spelExpressionParser.parseExpression(spel).getValue(context));
}
}
3、编写Redisson配置类RedissonConfig
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
* 添加Redisson的配置参数读取类RedissonConfig
*/
@Configuration
public class RedissonConfig {
@Value("${redisson.config}")
public String redissonConfig;
/**
* 读取锁的配置文件
* @return
* @throws IOException
*/
@Bean
public RedissonClient redisson() throws IOException {
// 本例子使用的是yaml格式的配置文件,读取使用Config.fromYAML,如果是Json文件,则使用Config.fromJSON
Config config = Config.fromYAML(redissonConfig);
return Redisson.create(config);
}
}
4、编写Redisson工具类
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* DistributedRedisLock
*/
@Component
public class DistributedRedisLock {
@Autowired
private RedissonClient redissonClient;
private static final String LOCK_TITLE = "redisLock_";
/**
* 加锁
* @param lockName
* @return
*/
public boolean acquire(String lockName){
//声明key对象
String key = LOCK_TITLE + lockName;
//获取锁对象
RLock mylock = redissonClient.getLock(key);
//获取不到锁直接抛异常
try {
//tryLock(等待时间,锁过期,时间单位),
boolean b = mylock.tryLock(0, 1, TimeUnit.MINUTES);
if(!b){
throw new Exception("同时操作数据");
}
} catch (InterruptedException e) {
throw new Exception("同时操作数据");
}
//下面是获取不到锁就等待,一直等到锁释放,根据自己情况考虑使用哪种
//加锁,并且设置锁过期时间,防止死锁的产生
// mylock.lock(2, TimeUnit.MINUTES);
//加锁成功
return true;
}
/**
* 锁的释放
* @param lockName
* @return
*/
public void release(String lockName){
//必须是和加锁时的同一个key
String key = LOCK_TITLE + lockName;
//获取所对象
RLock mylock = redissonClient.getLock(key);
//释放锁(解锁)
mylock.unlock();
}
}
5、编写application.yml文件
#redisson配置文件
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: 111111
clientName: null
database: 7 #选择使用哪个数据库0~15
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
subscriptionsPerConnection: 5
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
dnsMonitoringInterval: 5000
#dnsMonitoring: false
threads: 0
nettyThreads: 0
codec:
class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"
6、在需要加注解的地方使用
@Lock(key = {"#dto.userId","#dto.goodsId"})
public Result<Vo> saveOrUpdate(@Valid Dto dto) {
}