AOP结合redis实现自动存取
作者:互联网
Redis自定义注解@RedisSimpleCache
注解的作用
在使用过程中,不需要进行Redis中是否有数据的判断,就如同正常的查询数据库一般,当内容在数据库中不存在的情况下,会执行代码,在得到结果之后,再将数据存储中Redis中
注解的实现
使用Aop的技术,在方法中执行前查询数据,发现数据没有存在于数据库中,执行方法后,将数据存储中Redis的
具体的实现步骤
- AOP拦截注解过的方法后,先去校验中key是否存在,
- 存在的话,执行后续方法,判断方法返回的类型,来确定序列化以及反序列化的规则
- 将Redis的key组装起来
- 去redis中查询数据,判断数据有无
- 在没有数据的情况下,就执行方法,将数据得到后,将结果保存到redis中
- 在有数据的情况下,就直接返回Redis中数据即可。
具体实现
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisSimpleCache {
/**
* 过期时间 单位为秒
* @return
*/
int expire() default 86400;
/**
* redis 的key
* @return
*/
String redisKey() default "";
/**
* 在请求后是否刷新过期时间 true为刷新 false 为不刷新
* @return
*/
boolean reCalFlag() default false;
}
/**只用来标记Key*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RedisParamKey {
}
AOP实现
@Aspect
@Slf4j
@Component
public class SimpleCacheAopAdvice extends RedisBaseService {
public static final String BLAKE = ":";
public Random random;
@Pointcut(value = "@annotation(com.xixi.myredis.tool.annotation.RedisSimpleCache)")
public void test(){
}
public SimpleCacheAopAdvice() {
this.random = new Random();
}
/**
* 需要注意的问题 为了防止缓存雪崩,在设置过期时间的时候,要添加一个随机时间
* 为了防止 两个请求参数相同,都没有在redis中找到数据,同时去执行查询,
* 在查询之后,需要再次判断是否存在缓存 存在就不将数据加入缓存中
* 后续需要考虑为了防止缓存穿透的问题 要进行一个setnx的 互斥锁
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "test()")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
//1. 先去校验注解 RedisKey 是否存在 是否只有一个
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
log.info("执行方法:{}",method.getName());
List paramKey = getParamKey(method, joinPoint);
if(paramKey.size()>1){
log.error("{} 不支持多个RedisParamKey注解,将直接执行方法",method);
return joinPoint.proceed();
}
RedisSimpleCache annotation = method.getAnnotation(RedisSimpleCache.class);
int expire = annotation.expire();
String redisKey = annotation.redisKey();
boolean reCalFlag = annotation.reCalFlag();
//2.得到返回类型 来确定 序列化的规则 对于 List 与Map 要进行特别的数据,
Type genericReturnType = method.getGenericReturnType();
boolean assignableFrom = genericReturnType.getClass().isAssignableFrom(Class.class);
MyRedisSerializer myRedisSerializer = null;
if(!assignableFrom){
ResolvableType resolvableType = ResolvableType.forMethodReturnType(method);
//可能为List 或者 map
if(genericReturnType.getClass().isAssignableFrom(List.class)){
Class<?> aClass = resolvableType.resolveGeneric(0);
myRedisSerializer = new MyRedisSerializer(List.class);
myRedisSerializer.setClassList(Lists.newArrayList(aClass));
}else if(genericReturnType.getClass().isAssignableFrom(Map.class)){
myRedisSerializer = new MyRedisSerializer(Map.class, CommonConstants.MAP);
Class<?> keyClass = resolvableType.resolveGeneric(0);
Class<?> valueClass = resolvableType.resolveGeneric(0);
myRedisSerializer.setClassList(Lists.newArrayList(keyClass,valueClass));
}else {
myRedisSerializer = new MyRedisSerializer(resolvableType.resolve());
}
}else {
String typeName = genericReturnType.getTypeName();
myRedisSerializer = new MyRedisSerializer(Class.forName(typeName));
}
//3. 组装去redis 查询的key
StringBuffer sb = new StringBuffer();
sb.append(redisKey).append(BLAKE);
if(!paramKey.isEmpty() && paramKey.get(0)!=null){
sb.append(paramKey.get(0));
}
log.info("查询的key为 {}",sb.toString());
try {
return read(joinPoint,sb.toString(),expire+random.nextInt(1000),reCalFlag,myRedisSerializer);
} catch (Exception e) {
log.error("查询 key:{} ,出错了",sb.toString(),e.getMessage());
return joinPoint.proceed();
}
}
/**
* 得到带有RedisParamKey 注解的参数
* @param method
* @param joinPoint
* @return
*/
private List getParamKey(Method method,ProceedingJoinPoint joinPoint) {
List list = new ArrayList();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i=0; i<parameterAnnotations.length;i++){
for (int j =0;j< parameterAnnotations[i].length;j++){
if(parameterAnnotations[i][j] instanceof RedisParamKey){
Object[] args = joinPoint.getArgs();
list.add(args[i]);
}
}
}
return list;
}
}
/**
* 去redis中查询数据,没有数据的话,将结果查询出来,再放入到redis中。
* @description:
* @author: shengchengchao
* @date 2021/3/24
* @return
*/
public Object read(ProceedingJoinPoint joinPoint, String key, int expire, boolean reCalFlag, MyRedisSerializer myRedisSerializer) throws Throwable {
//先去redis中去查询
//使用Pipeline可以批量执行redis命令,防止多个命令建立多个连接
byte[] serialKey = RedisSerialUtils.serial(key);
List redisResult = redisTemplate.executePipelined((RedisCallback) conn -> {
byte[] bytes = conn.get(serialKey);
return null;
}, null);
Random random =new Random();
byte[] bytes = (byte[]) redisResult.get(0);
//没有缓存
if(bytes ==null ){
log.info("没有找到缓存,执行方法");
Object resultSave = getResultSave(joinPoint, serialKey, expire, reCalFlag, myRedisSerializer);
log.info("已得到结果并存入redis中");
return resultSave;
}else if(bytes.length==1 && bytes[0] ==0 ){
//缓存为null
return null;
}else {
//有缓存 将数据反序列化
try {
Object result = deserializeResult(myRedisSerializer, bytes);
if(reCalFlag){
redisTemplate.expire(serialKey,expire+random.nextInt(100), TimeUnit.SECONDS);
}
return result;
} catch (Exception e) {
log.error("从redis中反序列化出错",e);
return joinPoint.proceed();
}
}
}
/**
* 执行数据 并将结果保存到redis中
* @param joinPoint
* @param key
* @param expire
* @param reCalFlag
* @param myRedisSerializer
* @return
*/
private Object getResultSave(ProceedingJoinPoint joinPoint, byte[] serialKey, int expire, boolean reCalFlag, MyRedisSerializer myRedisSerializer) {
try {
Object proceed = joinPoint.proceed();
if(proceed !=null){
//再次查找redis 确定结果
List redisResult = redisTemplate.executePipelined((RedisCallback) conn -> {
byte[] bytes = conn.get(serialKey);
return null;
}, null);
byte[] bytes = (byte[]) redisResult.get(0);
//找到结果的话 进行反序列化
if(bytes !=null){
Object deserialize = deserializeResult(myRedisSerializer,bytes);
return deserialize;
}else {
//将数据存储到redis中
saveRedis(proceed,myRedisSerializer,serialKey,expire);
}
}
return proceed;
} catch (Throwable throwable) {
log.error("出现错误",throwable.getMessage());
}
return null;
}
/**
* 保存数据到redis
* @param result
* @param myRedisSerializer
*/
private void saveRedis(Object result, MyRedisSerializer myRedisSerializer,byte[] serialKey,int expire) {
if(result!=null){
//判断数据的类型 是否为null
byte[] serialize = myRedisSerializer.serialize(result);
redisTemplate.executePipelined((RedisCallback)con->{
con.set(serialKey,serialize);
if(expire!=CommonConstants.permanent){
con.expire(serialKey,expire);
}
return null;
});
}
}
/**
* 反序列化 数据
* @param myRedisSerializer 自定义的序列化
* @param bytes 结果
* @return
*/
private Object deserializeResult(MyRedisSerializer myRedisSerializer,byte[] bytes){
if(CommonConstants.MAP.equals(myRedisSerializer.getTypeName())){
List<Class> classList = myRedisSerializer.getClassList();
return MyFastJsonUtil.json2Map(new String(bytes), classList.get(0),classList.get(1));
}else {
return myRedisSerializer.deserialize(bytes);
}
}
具体使用
只需要在方法中添加上两个注解就可以完成,将数据存储到Redis,再次访问后,将先去查询数据库中数据。
@Service
public class TestService {
@RedisSimpleCache(redisKey = "test",expire = 100)
public String test(@RedisParamKey String k1 ){
System.out.println("进入查询");
return "aaa";
}
}
标签:return,myRedisSerializer,joinPoint,redis,expire,AOP,null,存取 来源: https://blog.csdn.net/xichen97/article/details/115338312