Spring aop优雅实现redis分布式锁 aop应用red
redis分布式锁切面实现
# 说明
网上找了一部分aop实现分布式锁的设计,感觉都不是特别好用,就自己写了一份。可以满足绝大部分分布式锁需求,请直接看代码,方法名很明了,注释也很明了啦,希望您看到不足的地方或者有不同见解的,还请再评论里回复,我会十分高兴和您探讨,十分感谢。
# @DistributedLock
注解定义
```java
import org.springframework.core.annotation.AliasFor;
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;
import java.util.concurrent.TimeUnit;
/**
* @author Zl
* @date 2019/8/2
* @since
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/**
* 过期时间
* 时间按时间单位换算
* @return
*/
long expire() default 3;
/**
* 等待时长
* 时间按时间单位换算
* 当为0时,不等待 默认不等待
*
* @return
*/
long waitTime() default 0;
/**
* 时间单位 默认为秒
*
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* springEl表达式
* 为空时取方法名称锁方法
*
* @return
*/
String key() default "";
/**
* 定义lock作用域,避免key重复
* 为空时取类完整包名
*
* @return
*/
String lockName() default "";
/**
* 异常i18n编码定义
* 用于获取失败后做异常信息抛出
* @return
*/
@AliasFor("errorCode")
String value();
@AliasFor("value")
String errorCode() default "";
}
```
# RedisLockAspect
redis分布式锁切面处理
```java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.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.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @author Zl
* @date 2019/8/2
* @since
*/
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
private ExpressionParser parser = new SpelExpressionParser();
@Autowired
private RedisLockUtils redisLockUtils;
@Pointcut("@annotation(com.ztesoft.zsmart.nros.base.annotation.DistributedLock)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
String className = point.getTarget().getClass().getName();
Object[] args = point.getArgs();
String[] paramNames = signature.getParameterNames();
//参数写入SpringEl域中
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
//获取切面注解
DistributedLock lock = method.getAnnotation(DistributedLock.class);
TimeUnit timeUnit = lock.timeUnit();
//redis key过期时间
long expire = timeUnit.toMillis(lock.expire());
//获取锁等待时间
long waitTime = timeUnit.toMillis(lock.waitTime());
//业务i18n异常编码
String errorCode = lock.value();
//key为空时锁方法,否则按SpringEl表达式取值
String key = StringUtils.isEmpty(lock.key()) ? method.getName() : parser.parseExpression(lock.key()).getValue
(context, String.class);
//作用域为空时取className
String lockName = StringUtils.isEmpty(lock.lockName()) ? className : lock.lockName();
//构造redisKey
String redisKey = lockName + "#" + key;
try {
if (redisLockUtils.setLock(redisKey, expire, waitTime)) {
log.info("获取分布式锁成功,class={},method={},key={}", className, method, redisKey);
//执行方法
return point.proceed();
}
}
catch (Exception e) {
log.error("获取分布式锁错误,class={},method={},key={}", className, method, redisKey);
ExceptionHandler.publish(errorCode, "", e);
}
finally {
redisLockUtils.releaseLock(redisKey);
}
log.info("获取分布式锁失败,class={},method={},key={}", className, method, redisKey);
//失败处理逻辑 此处抛出异常
ExceptionHandler.publish(errorCode);
return null;
}
}
```
# RedisLockUtils
与redis的交互域
```java
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* redis 分布式锁Utils
*
* @author Zl
* @date 2019/8/2
* @since
*/
@Slf4j
@Component
public class RedisLockUtils {
private static final DefaultRedisScript<String> UNLOCK_LUA;
static {
//构造脚本
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
DefaultRedisScript<String> script = new DefaultRedisScript<>();
script.setScriptText(sb.toString());
UNLOCK_LUA = script;
}
private static final String LOCK_VALUE = "1";
private static final String LOCK_HEARD = "lock:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean setLock(String key, String value, long expire) {
if (StringUtils.isBlank(value)) {
value = LOCK_VALUE;
}
return setNx(buildKey(key), value, expire);
}
public boolean setLock(String key, long expire) {
return setNx(key, null, expire);
}
public boolean setLock(String key, long expire, long waitTime) {
return setLock(key, null, expire, waitTime);
}
public boolean setLock(String key, String value, long expire, long waitTime) {
if (waitTime == 0L) {
return setLock(key, value, expire);
}
long start = System.currentTimeMillis();
while (true) {
//检测是否超时
if (System.currentTimeMillis() - start > waitTime) {
return false;
}
if (setLock(key, value, expire)) {
return Boolean.TRUE;
}
}
}
public Optional<String> getLockValue(String key) {
String o = redisTemplate.opsForValue().get(buildKey(key));
return Optional.ofNullable(o);
}
public boolean releaseLock(String key) {
return releaseLock(key, LOCK_VALUE);
}
public boolean releaseLock(String key, String value) {
try {
Object execute = redisTemplate.execute(
(RedisConnection connection) -> connection.eval(
UNLOCK_LUA.getScriptAsString().getBytes(),
ReturnType.INTEGER,
1,
buildKey(key).getBytes(),
value.getBytes())
);
return execute.equals(1L);
} catch (Exception e) {
log.error("release lock occured an exception", e);
} finally {
}
return false;
}
/**
* @param key key值
* @param value value值
* @param expiredTime 毫秒
* @return
*/
private boolean setNx(String key, String value, long expiredTime) {
Boolean resultBoolean = null;
try {
resultBoolean = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
Object nativeConnection = connection.getNativeConnection();
String redisResult = "";
@SuppressWarnings("unchecked")
RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
//lettuce连接包下序列化键值,否知无法用默认的ByteArrayCodec解析
byte[] keyByte = stringRedisSerializer.serialize(key);
byte[] valueByte = stringRedisSerializer.serialize(value);
// lettuce连接包下 redis 单机模式setnx
if (nativeConnection instanceof RedisAsyncCommands) {
RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;
//同步方法执行、setnx禁止异步
redisResult = commands
.getStatefulConnection()
.sync()
.set(keyByte, valueByte, SetArgs.Builder.nx().px(expiredTime));
}
// lettuce连接包下 redis 集群模式setnx
if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
redisResult = clusterAsyncCommands
.getStatefulConnection()
.sync()
.set(keyByte, keyByte, SetArgs.Builder.nx().px(expiredTime));
}
//返回加锁结果
return "OK".equalsIgnoreCase(redisResult);
});
} catch (Exception e) {
e.printStackTrace();
}
return resultBoolean != null && resultBoolean;
}
private String buildKey(String key) {
return LOCK_HEARD + key;
}
}
```
# 应用
```java
/**
* 锁className+#+test
*/
@DistributedLock("*CENTER-100001")
public void test(){}
/**
* 锁className+#+test1
* key过期设置为100毫秒
*/
@DistributedLock(value = "*CENTER-100001",expire = 100,timeUnit = TimeUnit.MILLISECONDS)
public void test1(){}
/**
* 锁className+#+test2
* 轮询10秒获取
*/
@DistributedLock(value = "*CENTER-100001",waitTime = 10)
public void test2(){}
/**
* 锁testNamespace+#+test3
*/
@DistributedLock(value = "*CENTER-100001",lockName = "testNamespace")
public void test3(){}
/**
* 锁className+#+id
*/
@DistributedLock(value = "*CENTER-100001",key = "#id")
public void test(String id){}
/**
* 锁className+#+id
*/
@DistributedLock(value = "*CENTER-100001",key = "#o.id")
public void test(Object o){}
```
如上,value与errorCode 按具体项目修改实现,有固定放回格式的可以采用返回错误返回值,这里时抛出异常信息构造i18nMassage。也可以考虑el表达式取值后加上方法名。