思路
- 使用Redisson实现分布式锁
- 定义分布式锁注解
- 基于SpEl生成锁的key
分布式锁注解
package com.yohann.boot.common.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
String prefix() default "";
String key() default "";
long waitTime() default -1;
long leaseTime() default -1;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
Redisson配置
package com.yohann.boot.config;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@Configuration
public class RedissonConfig {
@Bean
@Primary
public RedissonClient getRedissonClient(RedisProperties redisProperties) {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase())
.setConnectionPoolSize(100)
.setPingConnectionInterval(2000)
.setRetryAttempts(3)
.setRetryInterval(1000)
.setConnectionMinimumIdleSize(100);
if (StringUtils.isNotBlank(redisProperties.getPassword())) {
serverConfig.setPassword(redisProperties.getPassword());
}
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}
分布式锁切面实现
package com.yohann.boot.config.aspect;
import com.yohann.boot.common.annotation.Lock;
import lombok.extern.slf4j.Slf4j;
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.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
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.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class LockAspect {
@Autowired
private RedissonClient redissonClient;
private ExpressionParser parser = new SpelExpressionParser();
@Pointcut("@annotation(com.yohann.boot.common.annotation.Lock)")
public void lockAspect() {
}
@Around("lockAspect()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
String[] paramNames = signature.getParameterNames();
Lock annotation = method.getAnnotation(Lock.class);
String prefix = annotation.prefix();
if (prefix.isEmpty()) {
String className = pjp.getTarget().getClass().getName();
String methodName = method.getName();
prefix = String.join(":", className, methodName, Arrays.toString(paramNames));
}
String key = annotation.key();
Object[] args = pjp.getArgs();
if (key.length() > 0 && args.length > 0) {
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(context);
key = Objects.toString(value, "");
}
long waitTime = annotation.waitTime(), leaseTime = annotation.leaseTime();
TimeUnit timeUnit = annotation.timeUnit();
String lockName = prefix + key;
RLock lock = redissonClient.getLock(lockName);
log.debug("get lock name: {}", lockName);
boolean tryLock = lock.tryLock(waitTime, leaseTime, timeUnit);
if (!tryLock) {
throw new RuntimeException("get lock:" + lockName + " failed.");
}
try {
return pjp.proceed();
} finally {
lock.unlock();
}
}
}