我的做法:通过切面配合注解的方式使用。
注意:切面不能应用于静态方法,私有方法,注解要被代理对象调用。
1.注解
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisLock {
String name() default "";
String key() default "";
int waitTime() default 5;
int expireTime() default -1;
TimeUnit timeUnit() default TimeUnit.SECONDS;
String notes() default "";
String[] tags() default {""};
}
2.切面
@Aspect
@Component
public class RedisLockAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockAspect.class);
private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";
@Lazy
@Resource
private RedissonClient redissonClient;
/**
* 定义切点
*/
@Pointcut("@annotation(mairuirobot.iwarehousecontrol.framework.functions.iwc.redis.annotation.RedisLock)")
public void redisLockPointcut() {
}
/**
* 环绕通知
*/
@Around("redisLockPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String comment;
// 获取方法上的注解 RedisLock
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
RedisLock redisLock = method.getAnnotation(RedisLock.class);
String name = redisLock.name();
String spel = redisLock.key();
String key = getRedisKey(joinPoint, name, spel);
RLock rLock = redissonClient.getLock(key);
boolean lock = false;
Object result = null;
try {
// long waitTime-获取锁的等待时长;long leaseTime-持有锁的时间,-1代表默认启动看门狗机制;TimeUnit unit-时间单位
lock = rLock.tryLock(redisLock.waitTime(), redisLock.expireTime(), redisLock.timeUnit());
if (!lock) {
comment = String.format("Redis key[%s] lock failed, it's a repeated message", key);
LOGGER.error(comment);
Class<?> clazz = ((MethodSignature) joinPoint.getSignature()).getReturnType();
if (GeneralResponse.class == clazz) {
return GeneralResponse.failure();
}
if (boolean.class == clazz || Boolean.class == clazz) {
return false;
}
return GeneralResponse.failure();
}
result = joinPoint.proceed();
} finally {
if (lock) {
try {
rLock.unlock();
} catch (IllegalMonitorStateException e) {
comment = String.format("Redis key[%s] unlock failed: errMsg = ", key);
LOGGER.error(comment, e);
}
} else {
comment = String.format("Current thread did not acquire the redis key[%s] lock, skipping unlock", key);
LOGGER.error(comment);
}
}
return result;
}
private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
Object[] arguments = joinPoint.getArgs();
String key = REDISSON_LOCK_PREFIX + lockName + ":" + SpelUtil.parse(spel, arguments);
return key;
}
}
3.解析方法(需要根据你实际情况进行修改)
public class SpelUtil {
public static String parse(String spel, Object[] args) {
if (spel == null || spel.isEmpty()) return "";
Map<Object, Object> resMap = new HashMap<>();
String[] split = spel.split("\\.");
Arrays.stream(args).forEach(arg -> {
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(arg));
jsonObject.forEach((key, value) -> {
if (value instanceof JSONObject) {
((JSONObject) value).forEach((k, v) -> {
resMap.put(k, v);
});
} else {
resMap.put(key, value);
}
});
});
try {
String key = split[split.length - 1];
Object value = resMap.get(key);
return (value != null) ? value.toString() : "";
} catch (Exception e) {
throw new RuntimeException("redisson参数解析失败: " + spel, e);
}
}
}
4.注册bean
@Configuration
public class MyRedisRegistration {
@Value("${redisson.address:}")
private String redissonAddress;
@Value("${redisson.password:}")
private String redissonPassword;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress(redissonAddress).setPassword(redissonPassword);
return Redisson.create(config);
}
}
场景:申请电梯资源的时候,会有很多请求进来,导致电梯资源被争夺,这时就需要锁定电梯资源,防止同一个设备被抢占。