手写redis@Cacheable注解 参数java对象作为key值详解
作者:不懂的浪漫 发布时间:2022-04-26 11:30:36
标签:@Cacheable,参数,对象,key值
1.实现方式说明
本文在---- 手写redis @ Cacheable注解支持过期时间设置 的基础之上进行扩展。
1.1问题说明
@ Cacheable(key = “'leader'+#p0 +#p1 +#p2” )一般用法,#p0表示方法的第一个参数,#p1表示第二个参数,以此类推。
目前方法的第一个参数为Java的对象,但是原注解只支持Java的的基本数据类型。
1.2实现步骤
1.在原注解中加入新的参数,
objectIndexArray表示哪几个角标参数(从0开始)为java对象,objectFieldArray表示对应位置该对象的字段值作为key
2.如何获取参数的对象以及该字段的值
使用的java的反射,拼接get方法获取该字段值。
2.源代码
修改java注解@ExtCacheable,本文中使用@NewCacheable
package com.huajie.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NewCacheable {
String key() default "";
int[] objectIndexArray();
String[] objectFieldArray();
int expireTime() default 1800;//30分钟
}
SpringAop切面NewCacheableAspect
获取AOP整体流程没有任何变化
主要是关键值获取的方式,发生了变化
使用Java的反射技术
完整代码如下:
package com.huajie.aspect;
import com.huajie.annotation.NewCacheable;
import com.huajie.utils.RedisUtil;
import com.huajie.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* redis缓存处理 不适用与内部方法调用(this.)或者private
*/
@Component
@Aspect
@Slf4j
public class NewCacheableAspect {
@Autowired
private RedisUtil redisUtil;
@Pointcut("@annotation(com.huajie.annotation.NewCacheable)")
public void annotationPointcut() {
}
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 获得当前访问的class
Class<?> className = joinPoint.getTarget().getClass();
// 获得访问的方法名
String methodName = joinPoint.getSignature().getName();
// 得到方法的参数的类型
Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
Object[] args = joinPoint.getArgs();
String key = "";
int expireTime = 3600;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
method.setAccessible(true);
// 判断是否存在@ExtCacheable注解
if (method.isAnnotationPresent(NewCacheable.class)) {
NewCacheable annotation = method.getAnnotation(NewCacheable.class);
key = getRedisKey(args, annotation);
expireTime = getExpireTime(annotation);
}
} catch (Exception e) {
throw new RuntimeException("redis缓存注解参数异常", e);
}
log.info(key);
boolean hasKey = redisUtil.hasKey(key);
if (hasKey) {
return redisUtil.get(key);
} else {
Object res = joinPoint.proceed();
redisUtil.set(key, res);
redisUtil.expire(key, expireTime);
return res;
}
}
private int getExpireTime(NewCacheable annotation) {
return annotation.expireTime();
}
private String getRedisKey(Object[] args, NewCacheable annotation) throws Exception{
String primalKey = annotation.key();
// 获取#p0...集合
List<String> keyList = getKeyParsList(primalKey);
for (String keyName : keyList) {
int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
Object parValue = getParValue(annotation, keyIndex, args);
primalKey = primalKey.replace(keyName, String.valueOf(parValue));
}
return primalKey.replace("+", "").replace("'", "");
}
private Object getParValue(NewCacheable annotation, int keyIndex, Object[] args) throws Exception{
int[] objectIndexArray = annotation.objectIndexArray();
String[] objectFieldArray = annotation.objectFieldArray();
if (existsObject(keyIndex, objectIndexArray)) {
return getParValueByObject(args, keyIndex, objectFieldArray);
} else {
return args[keyIndex];
}
}
private Object getParValueByObject(Object[] args, int keyIndex, String[] objectFieldArray) throws Exception {
Class cls = args[keyIndex].getClass();
Method method;
if(objectFieldArray!=null&&objectFieldArray.length>=keyIndex){
method = cls.getMethod("get" + StringUtil.firstCharToUpperCase(objectFieldArray[keyIndex]));
}else{
method = cls.getMethod("get" + StringUtil.firstCharToUpperCase(cls.getFields()[0].getName()));
}
method.setAccessible(true);
log.info(method.getName());
return method.invoke(args[keyIndex]);
}
private boolean existsObject(int keyIndex, int[] objectIndexArray) {
if (objectIndexArray == null || objectIndexArray.length <= 0) {
return false;
}
for (int i = 0; i < objectIndexArray.length; i++) {
if (keyIndex == objectIndexArray[i]) {
return true;
}
}
return false;
}
// 获取key中#p0中的参数名称
private static List<String> getKeyParsList(String key) {
List<String> ListPar = new ArrayList<String>();
if (key.indexOf("#") >= 0) {
int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
int indexNext = 0;
String parName = "";
int indexPre = key.indexOf("#");
if (plusIndex > 0) {
indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
parName = key.substring(indexPre, indexNext);
} else {
parName = key.substring(indexPre);
}
ListPar.add(parName.trim());
key = key.substring(indexNext + 1);
if (key.indexOf("#") >= 0) {
ListPar.addAll(getKeyParsList(key));
}
}
return ListPar;
}
}
3.测试
业务模块使用方法controller
@RequestMapping("queryQuotaTreeData")
@ResponseBody
public List<TreeNode> getTreeData() {
QuotaManage quotaManage = new QuotaManage();
quotaManage.setQuotaName("测试22222");
List<TreeNode> list = this.quotaManageService.queryQuotaTreeData(quotaManage);
return list;
}
实现层objectIndexArray中的{0}表示第0个参数,objectFieldArray中的“quotaName”表示对应对象中的字段名称
@Override
@NewCacheable(key="test+#p0",objectIndexArray = {0},objectFieldArray = {"quotaName"})
public List<TreeNode> queryQuotaTreeData(QuotaManage quotaManage) {
List<TreeNode> returnNodesList = new ArrayList<TreeNode>();
List<TreeNode> nodeList = this.mapper.queryQuotaTreeData();
returnNodesList = treeUtils.getParentList(nodeList);
log.info(nodeList.size()+"");
return returnNodesList;
}
控制台截图拼接的get方法名称和获取的字段值
Redis的截图
来源:https://blog.csdn.net/xiewenfeng520/article/details/84864120


猜你喜欢
- 补充知识:正定矩阵奇异矩阵严格对角占优要理解Gauss消去法,首先来看一个例子:从上例子可以看出,高斯消去法实际上就是我们初中学的阶二元一次
- Android开发,触控无处不在。对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑。View事件的分发机制,不仅在做业务需求中会碰到
- 传输层安全性协议(英语:Transport Layer Security,缩写作 TLS),及其前身安全套接层(Secure Sockets
- java 读取网页内容的实例详解import java.io.BufferedReader; import java.io.IOExcept
- 一、前言最近接到一个任务,需要爬取五级行政区划的所有数据(大概71万条数据在),需要爬取的网站:行政区划 - 行政区划代码查询 发
- @Profile注解详解@Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;开发环境develop
- 本文实例讲述了Android通过应用程序创建快捷方式的方法。分享给大家供大家参考。具体如下:Android 快捷方式是桌面最基本的组件。它用
- 一、为什么需要服务网关:1、什么是服务网关:
- 这个是由于快捷键冲突造成的:所以可以查应用比如:1)搜狗输入法中设置的语句2)QQ音乐的快捷键3)有道词典的快键键把上面找的快键键删除,那么
- 1. java 执行shelljava 通过 Runtime.getRuntime().exec() 方法执行 shell 的命令或 脚本,
- 前言结果映射指的是将数据表中的字段与实体类中的属性关联起来,这样 MyBatis 就可以根据查询到的数据来填充实体对象的属性,帮助我们完成赋
- 引言对使用 lombok 还是有很多争议的,有些公司不建议使用,有些公司又大量使用。我们的想法是:可以使用,但是不要滥用。什么是 lombo
- 今天弄了一个多小时,写了一个GPS获取地理位置代码的小例子,包括参考了网上的一些代码,并且对代码进行了一些修改,希望对大家的帮助。具体代码如
- 前言在工作总常常需要用到缓存,而redis往往是首选,但是短期的数据缓存一般我们还是会用到本地缓存。本文提供一个我在工作中用到的缓存工具,该
- 背景最近再做一个需求,就是对站点的一些事件进行埋点,说白了就是记录用户的访问行为。那么这些数据怎么保存呢,人家点一下保存一下?显然不合适,肯
- SpringBoot整合junitSpringBoot整合junit①还是一样,我们首先创建一个SpringBoot模块。由于我们并不测试前
- 本文通过优化买票的重复流程来说明享元模式,为了加深对该模式的理解,会以String和基本数据类型的包装类对该模式的设计进一步说明。读者可以拉
- 使用 LogcatLogcat是日常开发的重要组成部分。如果您看到其中一个“强制关闭”或&l
- 学习目的:1、掌握在Android中如何建立EditText2、掌握EditText的常用属性3、掌握EditText焦点的事件、按键的事件
- java.util.concurrent.ExecutionException错误信息,这里给出解决方案,大家根据具体要求更改。SEVERE