公告:欢迎访问,查看更多资源请点我^.^!            点我关闭广告
AOP实现Redis注解缓存
2018-12-21 18:39:58
1316人阅读
评论(2)
分类:NoSql

    之前写了一篇memcached注解缓存,最近公司使用的是redis也用到了缓存,但是redis与mem稍有区别,特意写下来,避免一些坑!

    

    首先改写了@Cached注解:

import com.common.redis.RedisDBEnum;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cached {
int expire() default 60;//second

@AliasFor("key")
String value() default "";

@AliasFor("value")
String key() default "";

RedisDBEnum database() default RedisDBEnum.SYS;
}


    切面的修改:(坑就在这里,因为我之前Json转换接触的不多,而公司使用的redis存储的主类型为string类型,所以会有Json与Java对象之间的转换问题,这也是与上次memcached缓存的区别之处)

import com.alibaba.fastjson.JSON;
import com.common.redis.RedisDBEnum;
import com.environment.ds.annotation.Cached;
import com.environment.redis.impl.RedisRepositoryImpl;
import org.apache.commons.lang3.StringUtils;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Aspect
@Component
public class CacheAop {

@Autowired
private RedisRepositoryImpl redis;
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer =
new LocalVariableTableParameterNameDiscoverer();

@Pointcut("@annotation(com.ymt.crmservice.environment.ds.annotation.Cached)")
public void cachedAspect() {
}

@Around(value = "cachedAspect()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
// 获取方法名
Signature signature = pjp.getSignature();
String methodName = signature.getName();
Object[] args = pjp.getArgs();
//获取方法的注解
Method targetMethod = ((MethodSignature) signature).getMethod();
Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(methodName,
targetMethod.getParameterTypes());
Map<String, Object> params = new HashMap<>();
params.put("methodName", methodName);
params.put("fullName", targetMethod.getDeclaringClass().getName());
params.put("simpleName", targetMethod.getDeclaringClass().getSimpleName());
Cached cached = realMethod.getAnnotation(Cached.class);
String[] paramList = discoverer.getParameterNames(targetMethod);
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(params);
for (int len = 0; len < paramList.length; len++) {
context.setVariable(paramList[len], args[len]);
}
Expression expression = parser.parseExpression(cached.key());
String key = expression.getValue(context, String.class);
int saveTime = cached.expire();
RedisDBEnum database = cached.database();
Type type = realMethod.getGenericReturnType();
Object proceed;
String str = redis.get(RedisDBEnum.SYS, key);
//命中缓存
if (StringUtils.isNotBlank(str)) {
Class returnClass = realMethod.getReturnType();
// 返回值数据类型是否为List
if (realMethod.getReturnType() == List.class) {//List
//为List则获取泛型的实际类型
if (type instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
returnClass = Class.forName(actualTypeArguments[0].getTypeName());
}
proceed = JSON.parseArray(str, returnClass);
} else {//其他数据类型
proceed = JSON.parseObject(str, returnClass);
}
return proceed;
}
//未命中缓存
proceed = pjp.proceed(args);
if (proceed != null) {
redis.set(database, key, JSON.toJSONString(proceed), saveTime);
}
return proceed;
}
}


    改写后支持选择redis库、设置存储时间,有一些多余代码是为了支持spel表达式,使用方法如下:

    @Cached(database = RedisDBEnum.SYS, expire = 100, 
key = "#fullName + ':' + #methodName + ':' + #dictTypeCode + ':' + #parentCode")
@Override
public List<Data> list(String dictTypeCode, String parentCode) {
// TODO 具体实现
return null;
}


分享一下:
赞一下(3)
博主资料
博主头像
zc521106
文章:34
浏览:19899
文章分类
Java(12)
NoSql(112)
数据库(12)
前端(12)
阅读排行
Java基础知识
(12)
NoSql应用
(112)
数据库Oracle语法
(12)
前端常用工具类
(12)
java web项目
(12)
linux安装mysql
(12)
评论区
这篇文章怎么样?写点评论吧!
姓名:
邮箱:
有回复时通知我:
发表
回复【10.20.30.32】:
高女士的关系采访中在
2019-04-23 23:05:42
这里需要注意 redis缓存的击穿问题 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,就需要考虑缓存被“击穿”的问题了! 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 如何解决:业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
2019-01-27 17:19:43
* 以上用户言论只代表其个人观点,不代表iBlog网站的观点或立场,如有任何疑问请随时联系管理员...