1 自定义注解
package com.grm.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* desc: 自定义分布式锁注解,实现接口重复提交(幂等性)
*
* @author gaorimao
* @since 2021-04-30
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLockAnnotation {
//redis锁前缀
String prefix() default "";
//redis锁过期时间
int expire() default 5;
//redis锁过期时间单位
TimeUnit timeUnit() default TimeUnit.SECONDS;
//redis key分隔符
String delimiter() default ":";
}
import java.lang.annotation.*;
/**
* desc: 自定义key规则注解
* 由于redis的key可能是多层级结构,例如 redistest:demo1:token:kkk这种形式,因此需要自定义key的规则。
*
* @author gaorimao
* @since 2021-04-30
*/
@Target({ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParamAnnotation{
String name() default "";
}
2 定义key生成策略
import org.aspectj.lang.ProceedingJoinPoint;
/**
* desc: 定义key生成策略接口
*
* @author gaorimao
* @since 2021-04-30
*/
public interface CacheKeyGeneratorService {
/**
* 获取AOP参数,生成指定缓存Key
*
* @param joinPoint 切点
* @return lockKey
*/
String getLockKey(ProceedingJoinPoint joinPoint);
}
实现层
package com.grm.service.impl;
import com.grm.annotation.CacheLockAnnotation;
import com.grm.annotation.CacheParamAnnotation;
import com.grm.service.CacheKeyGeneratorService;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* desc: 获取缓存key
*
* @author gaorimao
* @since 2021-04-30
*/
@Service
public class CacheKeyGeneratorServiceImpl implements CacheKeyGeneratorService {
@Override
public String getLockKey(ProceedingJoinPoint joinPoint) {
//获取连接点的方法签名对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//Method对象
Method method = methodSignature.getMethod();
//获取Method对象上的注解对象
CacheLockAnnotation cacheLock = method.getAnnotation(CacheLockAnnotation.class);
//获取方法参数
Object[] args = joinPoint.getArgs();
//获取Method对象上所有的注解
Parameter[] parameters = method.getParameters();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parameters.length; i++) {
CacheParamAnnotation cacheParams = parameters[i].getAnnotation(CacheParamAnnotation.class);
//如果属性不是CacheParamAnnotation注解,则不处理
if (cacheParams == null) {
continue;
}
//如果属性是CacheParamAnnotation注解,则拼接 连接符(:)+ CacheParam
sb.append(cacheLock.delimiter()).append(args[i]);
}
//如果方法上没有加CacheParamAnnotation注解
if (StringUtils.isEmpty(sb.toString())) {
//获取方法上的多个注解(为什么是两层数组:因为第二层数组是只有一个元素的数组)
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//循环注解
for (int i = 0; i < parameterAnnotations.length; i++) {
Object object = args[i];
//获取注解类中所有的属性字段
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
//判断字段上是否有CacheParamAnnotation注解
CacheParamAnnotation annotation = field.getAnnotation(CacheParamAnnotation.class);
//如果没有,跳过
if (annotation == null) {
continue;
}
//如果有,设置Accessible为true(为true时可以使用反射访问私有变量,否则不能访问私有变量)
field.setAccessible(true);
//如果属性是CacheParamAnnotation注解,则拼接 连接符(:)+ CacheParam
sb.append(cacheLock.delimiter()).append(ReflectionUtils.getField(field, object));
}
}
}
//返回指定前缀的key
return cacheLock.prefix() + sb.toString();
}
}
3 AOP切面
package com.grm.aspect;
import com.grm.annotation.CacheLockAnnotation;
import com.grm.enums.ErrorEnum;
import com.grm.exception.BusinessException;
import com.grm.service.CacheKeyGeneratorService;
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.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import java.lang.reflect.Method;
/**
* @author gaorimao
*/
@Aspect
@Configuration
@Slf4j
public class CacheLockAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private CacheKeyGeneratorService cacheKeyGenerator;
@Pointcut("@annotation(com.grm.annotation.CacheLockAnnotation)")
public void pointcut() {}
@Around("pointcut()")
public Object cacheLockAspect(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CacheLockAnnotation cacheLock = method.getAnnotation(CacheLockAnnotation.class);
if (StringUtils.isEmpty(cacheLock.prefix())) {
throw new BusinessException(ErrorEnum.PREFIX_CAN_NOT_EMPTY);
}
//获取自定义key
String lockKey = cacheKeyGenerator.getLockKey(joinPoint);
log.info("[CacheLockAspect] lockKey={}",lockKey);
Boolean success = stringRedisTemplate.execute(
/*
* 基于redis的分布式同步锁版本,保证了加锁与设置超时时间两个动作的原子性
* @param key 使用key来当锁,唯一
* @param value 解锁依据
* @param time 超时时间,时间过后,key将会自动删除,避免死锁
* @param RedisStringCommands.SetOption 枚举 为NX,XX设置命令参数。
* SET_IF_ABSENT----------NX-------SETNX命令(SET if Not exists)----当且仅当key不存在,将key的值设为value,并返回1;若给定的key已经存在,则SETNX不做任何动作,并返回0
* SET_IF_PRESENT---------XX-------SETXX命令----仅当key存在时,set才会生效
* UPSERT-----------------不要设置任何其他命令参数。
* Boolean set(byte[] key, byte[] value, Expiration time, RedisStringCommands.SetOption var4);
*/
(RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(cacheLock.expire(), cacheLock.timeUnit())
, RedisStringCommands.SetOption.SET_IF_ABSENT));
if (!success) {
throw new BusinessException(ErrorEnum.MORE_REPEAT_REQUEST);
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throw new BusinessException(ErrorEnum.SEVER_ERROR);
}
}
}
4 测试
/**
* 没有加前缀prefix,测试会报错
*
* @param token
* @return
*/
@PostMapping(value = "/cacheLock")
@CacheLockAnnotation()
public Result cacheLock(String token) {
return Result.success("sucess=====" + token);
}
@PostMapping(value = "/cacheLock1")
@CacheLockAnnotation(prefix = "redisLock.test", expire = 20)
public Result cacheLock1(String token) {
return Result.success("sucess=====" + token);
}
@PostMapping(value = "/cacheLock2")
@CacheLockAnnotation(prefix = "redisLock.test", expire = 20)
public Result cacheLock2(@CacheParamAnnotation(name = "token") String token) {
return Result.success("sucess=====" + token);
}