经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
手写redis@Cacheable注解?参数java对象作为key值详解
来源:jb51  时间:2022/1/3 14:29:28  对本文有异议

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

  1. package com.huajie.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target({ ElementType.METHOD })
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface NewCacheable {
  9. String key() default "";
  10. int[] objectIndexArray();
  11. String[] objectFieldArray();
  12. int expireTime() default 1800;//30分钟
  13. }

SpringAop切面NewCacheableAspect

获取AOP整体流程没有任何变化

主要是关键值获取的方式,发生了变化

使用Java的反射技术

完整代码如下:

  1. package com.huajie.aspect;
  2. import com.huajie.annotation.NewCacheable;
  3. import com.huajie.utils.RedisUtil;
  4. import com.huajie.utils.StringUtil;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Pointcut;
  10. import org.aspectj.lang.reflect.MethodSignature;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Component;
  13. import java.lang.reflect.Method;
  14. import java.util.ArrayList;
  15. import java.util.List;
  16. /**
  17. * redis缓存处理 不适用与内部方法调用(this.)或者private
  18. */
  19. @Component
  20. @Aspect
  21. @Slf4j
  22. public class NewCacheableAspect {
  23. @Autowired
  24. private RedisUtil redisUtil;
  25. @Pointcut("@annotation(com.huajie.annotation.NewCacheable)")
  26. public void annotationPointcut() {
  27. }
  28. @Around("annotationPointcut()")
  29. public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  30. // 获得当前访问的class
  31. Class<?> className = joinPoint.getTarget().getClass();
  32. // 获得访问的方法名
  33. String methodName = joinPoint.getSignature().getName();
  34. // 得到方法的参数的类型
  35. Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
  36. Object[] args = joinPoint.getArgs();
  37. String key = "";
  38. int expireTime = 3600;
  39. try {
  40. // 得到访问的方法对象
  41. Method method = className.getMethod(methodName, argClass);
  42. method.setAccessible(true);
  43. // 判断是否存在@ExtCacheable注解
  44. if (method.isAnnotationPresent(NewCacheable.class)) {
  45. NewCacheable annotation = method.getAnnotation(NewCacheable.class);
  46. key = getRedisKey(args, annotation);
  47. expireTime = getExpireTime(annotation);
  48. }
  49. } catch (Exception e) {
  50. throw new RuntimeException("redis缓存注解参数异常", e);
  51. }
  52. log.info(key);
  53. boolean hasKey = redisUtil.hasKey(key);
  54. if (hasKey) {
  55. return redisUtil.get(key);
  56. } else {
  57. Object res = joinPoint.proceed();
  58. redisUtil.set(key, res);
  59. redisUtil.expire(key, expireTime);
  60. return res;
  61. }
  62. }
  63. private int getExpireTime(NewCacheable annotation) {
  64. return annotation.expireTime();
  65. }
  66. private String getRedisKey(Object[] args, NewCacheable annotation) throws Exception{
  67. String primalKey = annotation.key();
  68. // 获取#p0...集合
  69. List<String> keyList = getKeyParsList(primalKey);
  70. for (String keyName : keyList) {
  71. int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
  72. Object parValue = getParValue(annotation, keyIndex, args);
  73. primalKey = primalKey.replace(keyName, String.valueOf(parValue));
  74. }
  75. return primalKey.replace("+", "").replace("'", "");
  76. }
  77. private Object getParValue(NewCacheable annotation, int keyIndex, Object[] args) throws Exception{
  78. int[] objectIndexArray = annotation.objectIndexArray();
  79. String[] objectFieldArray = annotation.objectFieldArray();
  80. if (existsObject(keyIndex, objectIndexArray)) {
  81. return getParValueByObject(args, keyIndex, objectFieldArray);
  82. } else {
  83. return args[keyIndex];
  84. }
  85. }
  86. private Object getParValueByObject(Object[] args, int keyIndex, String[] objectFieldArray) throws Exception {
  87. Class cls = args[keyIndex].getClass();
  88. Method method;
  89. if(objectFieldArray!=null&&objectFieldArray.length>=keyIndex){
  90. method = cls.getMethod("get" + StringUtil.firstCharToUpperCase(objectFieldArray[keyIndex]));
  91. }else{
  92. method = cls.getMethod("get" + StringUtil.firstCharToUpperCase(cls.getFields()[0].getName()));
  93. }
  94. method.setAccessible(true);
  95. log.info(method.getName());
  96. return method.invoke(args[keyIndex]);
  97. }
  98. private boolean existsObject(int keyIndex, int[] objectIndexArray) {
  99. if (objectIndexArray == null || objectIndexArray.length <= 0) {
  100. return false;
  101. }
  102. for (int i = 0; i < objectIndexArray.length; i++) {
  103. if (keyIndex == objectIndexArray[i]) {
  104. return true;
  105. }
  106. }
  107. return false;
  108. }
  109. // 获取key中#p0中的参数名称
  110. private static List<String> getKeyParsList(String key) {
  111. List<String> ListPar = new ArrayList<String>();
  112. if (key.indexOf("#") >= 0) {
  113. int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
  114. int indexNext = 0;
  115. String parName = "";
  116. int indexPre = key.indexOf("#");
  117. if (plusIndex > 0) {
  118. indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
  119. parName = key.substring(indexPre, indexNext);
  120. } else {
  121. parName = key.substring(indexPre);
  122. }
  123. ListPar.add(parName.trim());
  124. key = key.substring(indexNext + 1);
  125. if (key.indexOf("#") >= 0) {
  126. ListPar.addAll(getKeyParsList(key));
  127. }
  128. }
  129. return ListPar;
  130. }
  131. }

3.测试

业务模块使用方法controller

  1. @RequestMapping("queryQuotaTreeData")
  2. @ResponseBody
  3. public List<TreeNode> getTreeData() {
  4. QuotaManage quotaManage = new QuotaManage();
  5. quotaManage.setQuotaName("测试22222");
  6. List<TreeNode> list = this.quotaManageService.queryQuotaTreeData(quotaManage);
  7. return list;
  8. }
  1.  

实现层objectIndexArray中的{0}表示第0个参数,objectFieldArray中的“quotaName”表示对应对象中的字段名称

  1. @Override
  2. @NewCacheable(key="test+#p0",objectIndexArray = {0},objectFieldArray = {"quotaName"})
  3. public List<TreeNode> queryQuotaTreeData(QuotaManage quotaManage) {
  4. List<TreeNode> returnNodesList = new ArrayList<TreeNode>();
  5. List<TreeNode> nodeList = this.mapper.queryQuotaTreeData();
  6. returnNodesList = treeUtils.getParentList(nodeList);
  7. log.info(nodeList.size()+"");
  8. return returnNodesList;
  9. }
  1.  

控制台截图拼接的get方法名称和获取的字段值

Redis的截图

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号