AOP切面编程+布隆过滤器+加锁 最终版
因为布隆过滤器是村商品的id信息,所以其他的业务不一定用得到,所以注解里面自定义一个boolean值来决定是否开启布隆过滤器,且把非核心业务代码抽到一个切面类中
package com.atguigu.aop;
import com.atguigu.constant.RedisConst;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @ClassName ShopCacheAspect
* @Author dx
* @Date 2023/7/17 13:43
*/
@Component
@Aspect
public class ShopCacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RBloomFilter rBloomFilter;
@Around("@annotation(com.atguigu.aop.ShopCache)")
public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//1.获取到目标方法上面的参数的第一个
Object[] prams = joinPoint.getArgs();
Object firstParam = prams[0];
//获取目标方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = methodSignature.getMethod();
//拿到目标方法上面的注解
ShopCache shopCache = signatureMethod.getAnnotation(ShopCache.class);
String prefix = shopCache.value();
String cacheKey=prefix+":"+firstParam;
//减小锁的粒度
String lockKey="lock-"+firstParam;
//因为是查询的操作,不改变数据库不是增删改 所以可以使用本地锁
Object objectCache = redisTemplate.opsForValue().get(cacheKey);
if (objectCache==null){
//保证拿到的锁是同一个
synchronized (lockKey.intern()){
if (objectCache == null) {
//是否开启布隆过滤器
boolean bloom = shopCache.bloom();
Object FromDb=null;
if (bloom){//为true 开启布隆过滤器
boolean flag = rBloomFilter.contains(firstParam);
if (flag){
FromDb = joinPoint.proceed();
//把数据放入redis
redisTemplate.opsForValue().set(cacheKey, FromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
return FromDb;
}
}else {
FromDb = joinPoint.proceed();
//把数据放入redis
redisTemplate.opsForValue().set(cacheKey, FromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
return FromDb;
}
}
}
}
return objectCache;
}
}
自定义注解
package com.atguigu.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//运行时使用
@Retention(RetentionPolicy.RUNTIME)
//在方法和类上可用使用该注解
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface ShopCache {
//定义属性值
String value() default "cache";
//定义一个布尔类型值
boolean bloom() default true;
}
业务代码
//根据skuId查询商品的基本信息 先走redis 没有再走数据库
//走redis的代码抽取到一个切面类中(AOP的思想) @ShopCache设置了属性value 为了区分不同的方法的不同的缓存key
@ShopCache(value = "skuInfo")
@Override
public SkuInfo getSkuInfo(Long skuId) {
SkuInfo skuInfo = getSkuInfoFromDb(skuId);
return skuInfo;
}
/**
* 数据库查询
* @param skuId
* @return
*/
private SkuInfo getSkuInfoFromDb(Long skuId) {
SkuInfo skuInfo = this.getById(skuId);
if (skuInfo!=null){
LambdaQueryWrapper<SkuImage> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SkuImage::getSkuId, skuId);
List<SkuImage> imageList = skuImageService.list(wrapper);
skuInfo.setSkuImageList(imageList);
}
return skuInfo;
}
如果value值设置的为 @ShopCache(value = “skuInfo:#{#params}”)
则需要El表达式来解析然后拿到拼接拿到key 当然这是少数情况
public String getExpressionValue(String key,Object[] methodParams){
//a.获取一个spring表达式解析器
SpelExpressionParser elParser = new SpelExpressionParser();
//b.利用解析器解析表达式
Expression expression = elParser.parseExpression(key, new TemplateParserContext());
//c.准备一个计算环境
StandardEvaluationContext context = new StandardEvaluationContext();
//d.设置表达式的值
context.setVariable("params",methodParams);
//e.表达式解析之后结果
String retVal = expression.getValue(context, String.class);
return retVal;
}