1、pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson,redis客户端,封装了分布式锁实现,也可以使用springboot的方式,不需要自己配置-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.1</version>
</dependency>
2、redis yml文件
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
3、初始化
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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 org.springframework.core.annotation.Order;
/**
* 初始化Redission Client
*/
@Configuration
@Order(value = 3)
@Slf4j
public class RedissionConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.database}")
private int database;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient initRedisson() {
Config config = new Config();
//单机模式自动装配
config.useSingleServer().setAddress("redis://" + host + ":" + port)
.setDatabase(database);
if (StringUtils.isNotEmpty(password)) {
config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database)
.setPassword(password);
} else {
config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database);
}
// 设置全局默认看门狗机续期时间,如果在使用时不设置,则使用全局的,如果全局不设置,则使用默认的30000,单位毫秒
// 为啥要看门狗:
// 如果业务超长,运行期间自动给锁续上新的有效时间,不用担心业务时间长,锁自动过期被删掉,其实就是可重入锁
config.setLockWatchdogTimeout(2000);
log.debug("--------------Redission Client Created------------");
return Redisson.create(config);
}
}
4、lock 注解 哪里用放哪里
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Dislock {
/**
* 分布式锁的key,一般可以用用户id,用户token这种作为唯一的key,达到自己的锁只能被自己解的效果,如#userId
*/
String localKey() default "";
/**
* 业务分类 默认为All不分类,建议分类
*
* @return
*/
String biz() default "ALL";
/**
* 锁等待时间 默认2秒
*
* @return
*/
long waitTime() default 2 * 1000;
/**
* 锁释放时间 默认2秒
*
* @return
*/
long leaseTime() default 2 * 1000;
/**
* 时间格式 默认:毫秒
*
* @return
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
5、解析器
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
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 java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* lock key 解析器
*/
public class LockKeyParser {
/**
* 解析缓存的key
*
* @param proceedingJoinPoint 切面
* @param localKey lock key
* @param biz 业务分类
* @return String
* @throws IllegalAccessException 异常
*/
public static String parse(ProceedingJoinPoint proceedingJoinPoint, String localKey,
String biz) throws IllegalAccessException {
//解析实参
String key = localKey;
Object[] parameterValues = proceedingJoinPoint.getArgs();
MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = nameDiscoverer.getParameterNames(method);
String methodName = method.getName();
if (StringUtils.isEmpty(key)) {
key = methodName + ":";
if (parameterNames != null && parameterNames.length > 0) {
StringBuffer sb = new StringBuffer();
int i = 0;
for(int len = parameterNames.length; i < len; ++i) {
sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]);
}
key += sb.toString();
} else {
key = "redissionLock";
}
return key;
} else {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
if (parameterNames != null && parameterNames.length != 0) {
EvaluationContext evaluationContext = new StandardEvaluationContext();
for(int i = 0; i < parameterNames.length; ++i) {
evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
}
try {
Object expressionValue = expression.getValue(evaluationContext);
return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key;
} catch (Exception var13) {
return key;
}
} else {
return key;
}
}
}
/**
* 获取参数Map集合
*
* @param joinPoint 切面
*
* @return
*/
private static Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {
Object[] paramValues = joinPoint.getArgs();
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> param = new HashMap<>(paramNames.length);
for (int i = 0; i < paramNames.length; i++) {
param.put(paramNames[i], paramValues[i]);
}
return param;
}
/**
* 获取指定参数名的参数值
*
* @param obj
* @param propName
* @return
*
* @throws IllegalAccessException
*/
public static Object getPropValue(Object obj, String propName) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.getName().equals(propName)) {
//在反射时能访问私有变量
f.setAccessible(true);
return f.get(obj);
}
}
return null;
}
}
6、切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
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.stereotype.Component;
/**
* 分布式锁AOP切面类
*/
@Aspect
@Component
public class DislockAspect {
@Autowired
private RedissonClient redissonClient;
/**
* 通过环绕通知实现加锁解锁
* 切入@Dislock注解的point
* @param proceedingJoinPoint
* @return
*
* @throws Throwable
*/
@Around("@annotation(com.xxx.xxx.config.redisson.Dislock)")
public Object arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object object = null;
RLock lock = null;
boolean status = false;
try {
Dislock dislock = getDislockInfo(proceedingJoinPoint);
//生成lockKey,保证锁定资源的唯一性,并且只能解锁自己加的锁
String lockKey = LockKeyParser.parse(proceedingJoinPoint, dislock.localKey(), dislock.biz());
lock = redissonClient.getLock(lockKey);
if (lock != null) {
//试图加锁
status = lock.tryLock(dislock.waitTime(), dislock.leaseTime(), dislock.timeUnit());
if (status) {
// System.out.println("lockKey = " + lockKey);
//如果加锁成功,执行切点业务
object = proceedingJoinPoint.proceed();
}
}
} finally {
//判断是否需要解锁
if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return object;
}
public Dislock getDislockInfo(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(Dislock.class);
}
}