声明式注解解决分布式锁问题之自定义注解
首先需要自定义一个注解 (仿照这@Transactional这个注解写的)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface GmallCache {
/**
* 将使用此注解的中的前缀后缀等参数获取到。
*/
String prefix() default ":cache";
String suffix() default ":info";
}
对应注解上各个元注解的作用如下:
@Target({ElementType.TYPE, ElementType.METHOD}) :可以使用在类上和方法上。
@Retention(RetentionPolicy.RUNTIME) 生命周期 : SOURCE 将注解保留在源文件中
CLASS: 保留在编译期间 RUNTIME: 可以将注解保留在jvm中 @Inherited :可以继承
@Documented :注解上如果添加这个元注解的话我们生成文档的时候会显示该注解
其次定义一个切面类
@Aspect //标志这个类是一个切面
@Component //因为没有用到@Bean所以使用本注解
public class GmallCacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 先查缓存
* 有返回,没有查数据库
* 查数据库先获取锁
* 有 查返回 没有 睡眠自旋
* 兜底方法
*
* @param joinPoint 用来获取代理类和被代理类的信息
* @return
* @throws Throwable
*/
@Around("@annotation(com.atguigu.gmall.common.cache.GmallCache)")
public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = new Object[0];
Object object =null;
try {
object = new Object();
//获取到方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//很方法签名获取到对应注解中的参数信息
GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);
String prefix = gmallCache.prefix();
String suffix = gmallCache.suffix();
args = joinPoint.getArgs();
String key = prefix+ Arrays.toString(args)+suffix;
Class returnType = signature.getReturnType();
//去查询redis,但是得确定我们查询结果后的返回值类型
object = this.cacheHit(key, returnType);
if (object==null){
String lockKey = prefix + Arrays.toString(args) + RedisConst.SKUKEY_SUFFIX;
RLock lock = redissonClient.getLock(lockKey);
boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
if (flag){
try {
//拿到锁执行查询数据库
Object proceed = joinPoint.proceed(args);
if (proceed!=null){//将数据库中查到的数据写入到redis中
redisTemplate.opsForValue().set(key,JSON.toJSON(proceed),RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
return proceed;
}else {
Object nullobject = returnType.newInstance();
redisTemplate.opsForValue().set(key,JSON.toJSON(nullobject),RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
return nullobject;
}
} finally {
lock.unlock();
}
}else {
//没有拿到锁的线程执行
Thread.sleep(100);
return this.cacheAroundAdvice(joinPoint);
}
}else {
//缓存中有数据直接返回
return object;
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//兜底方法 如果在查询redis的时候出现了问题直接区调用被代理的方法 查询数据库
return joinPoint.proceed(args);
}
//本方法是去根据制定的key去缓存中去查询数据
private Object cacheHit(String key, Class returnType) {
String strObject = (String) redisTemplate.opsForValue().get(key);
if (StringUtil.isEmpty(strObject)){
Object object = JSON.parseObject(strObject, returnType);
return object;
}
return null;
}
}
最后在业务层的代码中使用(在不改变原来的代码的基础上添加了分布式锁的功能实现的AOP的面向切面编程,并且使用的是注解的方式进行匹配,可以在多个方法上进行复用)
//这里使用我们自定义的注解在我们使用了本注解以后将对应切面中满足的环绕通知的功能加进来
@GmallCache(prefix = "SkuInfo:",suffix = ":info")
public SkuInfo getSkuInfo(Long skuId) {
SkuInfo skuInfo = this.getSkuInfoDB(skuId);
// SkuInfo skuInfo = this.getSkuRediss(skuId);
return skuInfo;
}