项目-无侵入代码方式使用Redis实现缓存功能
一,情景介绍
公司新需求,在查询接口的manager层要加入redis缓存,只要通过manager层的增删改查方法,统统进行缓存处理。
基于这个需求,我写了一个aop切面,具体实现逻辑如下
ProceedingJoinPoint的操作见文章:
二,思路梳理
大致分为以下几个步骤,不多,很简单
- 自定义注解,凡是在Manager层加该注解的方法,都要进行缓存
- aop切面,切该注解
- 判断操作类型(增删改查),得到key值
- 对操作结果进行缓存
三,代码实践
1,自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
public @interface E2eCache {
//查询条件(查询详情的主键)
String key();
/**
* 方法操作
* 默认为查询操作 新增指定为[add] 更新指定为[edit] 删除指定为[del] 查询指定为[get]
* @return
*/
String operate() default "get";
}
2,切面类
/**
* redis在manager层的切面类,作为缓存机制,达到以下目的
* 1,判断当前环境是否存在redis,如果存在,则加载缓存机制,如果不存在,则按原逻辑进行
* 2,增删改查均加入redis缓存
* 3,考虑对原有代码不侵入的原则
*/
@Aspect
@Component
public class ManagerAspect {
@Value("spring.redis.host")
private String redisHost;
//TODO 模拟redis缓存,调试通过放RedisTemplate
private static Map redisTemplate=new HashMap<>();
private Logger logger= LoggerFactory.getLogger(ManagerAspect.class);
@Pointcut("@annotation(com.dayouzc.rediscache.annotation.E2eCache)")
private void cutMethod(){
}
@Around("cutMethod()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//取注解的操作值
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//判断当前方法是否是在Manager类中
// todo 目前暂时是通过判断当前类是否存在@Repository注解,以及方法名是否包含Manager字符串
Class<?> declaringClass = signature.getMethod().getDeclaringClass();
String declaringClassName = declaringClass.getName();
//如果该方法不是在Manager类,不缓存
if(!declaringClass.isAnnotationPresent(Repository.class) || !declaringClassName.contains("Manager")){
return joinPoint.proceed();
}
// 1,判断是否存在redis环境
if(StringUtils.isBlank("test")){
logger.info("======== 当前环境redis不存在,缓存机制失效 ==========");
//原逻辑处理
return joinPoint.proceed();
}else{
logger.info("======== 当前环境redis正常,开启Manger层缓存 ==========");
//得到request请求
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//2,走redis缓存
E2eCache annotation = signature.getMethod().getAnnotation(E2eCache.class);
//操作类型
String operate = annotation.operate();
//key的名称
String id = annotation.key();
//方法名
String methodName = joinPoint.getSignature().getName();
//原方法返回值类型
Class returnType = signature.getReturnType();
if(operate.equals("get")){
//查询详情
JSONObject jsonObject = getByArgs(joinPoint, request);
//根据返回值类型返回对应的实体对象
Object object = JSONObject.toJavaObject(jsonObject, returnType);
return object;
}else if(operate.equals("add")){
//新增
JSONObject jsonObject = addOperation(joinPoint, request);
Object object = JSONObject.toJavaObject(jsonObject, returnType);
return object;
}else if(operate.equals("edit")){
//修改
JSONObject jsonObject = editOperate(joinPoint, request);
Object object = JSONObject.toJavaObject(jsonObject, returnType);
return object;
}else if(operate.equals("del")){
//删除
delOperate(joinPoint,request);
return null;
}else{
//原逻辑处理
return joinPoint.proceed();
}
}
}
/**
* 处理删除
* @param joinPoint
* @param request
*/
private void delOperate(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
//删除缓存
String key = ManagerCacheUtil.createKey(joinPoint, request);
//判断缓存中是否有数据
//todo 这里map提代redis
boolean hasKey = redisTemplate.containsKey(key);
//删除旧的缓存
if(hasKey){
logger.info("======== 删除旧缓存数据 ==========");
redisTemplate.remove(key);
}
}
/**
* 处理更新
* @param joinPoint
* @param request
*/
private JSONObject editOperate(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
//方法返回值
Object object = null;
JSONObject result = null;
try {
//执行方法
object = joinPoint.proceed();
result=(JSONObject) JSONObject.toJSON(object);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
String key = ManagerCacheUtil.createKey(joinPoint,object);
//判断缓存中是否有数据
//todo 这里map提代redis
boolean hasKey = redisTemplate.containsKey(key);
//删除旧的缓存
if(hasKey){
logger.info("======== 删除旧缓存数据 ==========");
redisTemplate.remove(key);
}
//存储redis
logger.info("======== 新数据更新到redis缓存 ==========");
redisTemplate.put(key,JSONObject.toJSONString(result));
return result;
}
/**
* 处理查询
* @param joinPoint
* @param request
*/
private JSONObject getByArgs(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
//得到缓存key
String key = ManagerCacheUtil.createKey(joinPoint, request);
//判断缓存中是否有数据
//todo 这里map提代redis
boolean hasKey = redisTemplate.containsKey(key);
JSONObject jsonObject = null;
//如果存在key,则取缓存数据并返回
if(hasKey){
logger.info("======== 缓存数据存在,返回缓存数据 ==========");
String value = (String) redisTemplate.get(key);
//返回
return JSONObject.parseObject(value);
}else{
logger.info("======== 无缓存数据 ==========");
try {
jsonObject=(JSONObject) JSONObject.toJSON(joinPoint.proceed());
//存储新的查询结果
logger.info("======== 新数据更新到redis缓存 ==========");
redisTemplate.put(key,JSONObject.toJSONString(jsonObject));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return jsonObject;
}
}
/**
* 处理新增
* @param joinPoint
*/
private JSONObject addOperation(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
//方法返回值
Object object = null;
JSONObject result = null;
try {
//执行方法
object = joinPoint.proceed();
result=(JSONObject) JSONObject.toJSON(object);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
String key = ManagerCacheUtil.createKey(joinPoint,object);
//存储redis
logger.info("======== 新数据更新到redis缓存 ==========");
redisTemplate.put(key,JSONObject.toJSONString(result));
return result;
}
}
3,生成key的工具类
public class ManagerCacheUtil {
private static Logger logger= LoggerFactory.getLogger(ManagerCacheUtil.class);
/**
* 生成key 此方法供查询和删除方法使用
* 定义key的生成规则
* TODO 目前生成规则:manager类名 + id值(查询条件)
* @param joinPoint
* @param request
*/
public static String createKey(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
E2eCache annotation = signature.getMethod().getAnnotation(E2eCache.class);
//key的名称
String id = annotation.key();
// 下面两个数组中,参数值和参数名的个数和位置是一一对应的
Object[] objects = joinPoint.getArgs(); // 参数值
String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); // 参数名
String key_value = StringUtils.EMPTY;
if(argNames!=null && argNames.length>0){
//如果参数名不在参数名列表中,1,该参数名在对象中 2,key值不存在
if(!Arrays.asList(argNames).contains(id)){
//遍历参数列表中的参数
for(Object obj:objects){
if(obj instanceof String){
continue;
}else if(obj instanceof Integer){
continue;
}
key_value = fromObjectToGetKeyStringValue(obj, id);
if(!StringUtils.equals(key_value,"find not parameter")){
//在实体类中找到了参数,并得到了参数值result
break;
}
}
if(StringUtils.isEmpty(key_value) || StringUtils.equals(key_value,"find not parameter")){
throw new RuntimeException("未找到key值对应的参数");
}
}else{
//参数名为正常的String或Integer
//查找key所在数组的索引
int index=0;
for(int i=0;i aClass = id_value.getClass();
//所有属性
Field[] declaredFields = aClass.getDeclaredFields();
for(Field field:declaredFields){
if(StringUtils.equals(keyName,field.getName())){
//找到了匹配的属性
Method method = null;
//属性首字母大写,拼接方法名
String methodName = "get" + captureName(keyName);
try {
//执行get方法得到属性值
method=aClass.getMethod(methodName);
Object fieldValue = method.invoke(id_value);
//递归调用
String fieldVal = fromObjectToGetKeyStringValue(fieldValue, keyName);
return fieldVal;
} catch (NoSuchMethodException e) {
logger.error("没有找到对应的get方法",e);
} catch (IllegalAccessException e) {
logger.error("执行反射方法取属性值错误",e);
} catch (InvocationTargetException e) {
logger.error("执行反射方法取属性值错误",e);
}
}
}
return "find not parameter";
}else{
return "find not parameter";
}
}
//首字母大写
private static String captureName(String name) {
name = name.substring(0, 1).toUpperCase() + name.substring(1);
return name;
}
}
四,测试
列出测试代码,和测试结果
Manager层
@E2eCache(key = "linkId",operate = "get")
public EbCmservLink getById(EbCmservLink ebCmservLink){
return testMapper.getLinkDetail(ebCmservLink.getLinkId());
}
@E2eCache(key = "linkId",operate = "add")
public EbCmservLink addLink(EbCmservLink ebCmservLink){
ebCmservLink.setLinkId(UUID.randomUUID().toString().replaceAll("-",""));
ebCmservLink.setCmId("test2345235");
ebCmservLink.setCmName("中医理疗测试胶囊");
ebCmservLink.setCminfoId("test9asdfu0sdf9");
ebCmservLink.setCminfoName("test商品实例");
int i = testMapper.addLinkDetail(ebCmservLink);
if(i>0){
return ebCmservLink;
}else{
return null;
}
}
@E2eCache(key = "linkId",operate = "edit")
public EbCmservLink updateLink(EbCmservLink ebCmservLink){
int i = testMapper.updateLinkDetail(ebCmservLink);
if(i>0){
return testMapper.getLinkDetail(ebCmservLink.getLinkId());
}else{
return null;
}
}
Controller层
@RestController
public class TestController {
@Autowired
private TestManager manager;
@GetMapping("/get")
public EbCmservLink get(String linkId){
EbCmservLink link=new EbCmservLink();
link.setLinkId(linkId);
return manager.getById(link);
}
@GetMapping("/insert")
public EbCmservLink insert(EbCmservLink ebCmservLink){
EbCmservLink link = manager.addLink(ebCmservLink);
return link;
}
@GetMapping("/update")
public EbCmservLink update(EbCmservLink ebCmservLink){
EbCmservLink link = manager.updateLink(ebCmservLink);
return link;
}
}