前言

偶尔看到了spring cache的文章,我去,实现原理基本相同,哈哈,大家可以结合着看看

简介

实际项目中,会遇到很多查询数据的场景,这些数据更新频率也不是很高,一般我们在业务处理时,会对这些数据进行缓存,防止多次与数据库交互。

这次我们讲的是,所有这些场景,通过一个注解即可实现。

实现过程

1、首先我们添加一个自定义注解

package com.bangdao.parking.applets.api.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; /**
* 仅针对查询场景使用,其它需要更新数据的请勿使用,不然重复请求不会进行处理
* 请求参数必须是json格式
*
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheRequest { /**
* 缓存时间,默认60秒,单位:秒
*
* @return
*/
@AliasFor("value")
int expire() default 60; @AliasFor("expire")
int value() default 60; /**
* 是否按用户维度区分
* 比如用户A和用户B先后访问同一个接口,如果该值设置未true,则根据用户区分返回,否则返回用户A的数据
* 场景A,获取用户个人信息,则此值设为true
* 场景B,获取车场数据(与个人无关),则此值可设为false
*
* @return
*/
boolean byUser() default true; /**
* 自定义key,后续便于其它接口清理缓存。若无更新操作,可忽略此配置
* @return
*/
String key() default ""; }

定义两个属性,

①expire,设置缓存内容的过期时间,过期后再次访问,则从数据库查询再次进行缓存,

②byUser,是否根据用户维度区分缓存,有些场景不同用户访问的是相同数据,所以这个是否设置为false,则只缓存一份,更节省缓存空间

③key,不根据参数生成缓存,自定义配置,便于后续有更新操作无法处理,具体可以看下面aop的clearCache方法

2、添加切面,进行数据缓存处理

@Aspect
@Configuration
public class CacheRequestAop { private static final Logger log = LoggerFactory.getLogger(CacheRequest.class); @Autowired
private RedisService redisService; // 这里项目会有个拦截器校验用户登录态,然后会缓存用户信息,根据实际场景获取,如需要,可看我其它博客
@Autowired
private CacheService cacheService; // 此处注解路径,按实际项目开发进行配置
@Pointcut("@annotation(xxx.CacheRequest)")
public void pointCut() {
} @Around("pointCut()")
public Object handler(ProceedingJoinPoint pjp) throws Throwable { log.info("# [BEGIN]请求缓存处理"); // 获取注解对象
CacheRequest annotation = getDeclaredAnnotation(pjp, CacheRequest.class);
long expire = annotation.expire();
boolean byUser = annotation.byUser(); // 请求参数排序
TreeMap<String, String> args = new TreeMap<String, String>();
Object[] objs = pjp.getArgs();
if (objs.length > 0) {
// json序列化工具,大家可自行选择,建议使用springboot的jackson或者google的gson
// 这里默认取第一个参数对象,因为我们默认为请求格式为Json
args = JacksonUtil.jsonToObject(JacksonUtil.marshallToString(objs[0]), new TypeReference<TreeMap<String, String>>() {
});
} if (byUser) {
args.put("userId", cacheService.getUserId());
} ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
args.put("requestUrl", request.getRequestURI()); String sign = DigestUtils.md5Hex(JacksonUtil.marshallToString(args));
log.info("# sign:{}", sign); // 一般项目的返回都会有基类,这里的BaseResult就是
Object result = redisService.get("request:cache:" + sign, BaseResult.class);
// 如果有缓存,则不会进行处理,直接返回缓存结果
if (result != null) {
log.info("# [END]请求返回缓存数据");
return result;
} // 不存在缓存,就进行处理,处理完成在进行缓存
result = pjp.proceed();
redisService.set("request:cache:" + sign, result, expire); log.info("# [END]请求缓存处理");
return result;
} /**
* 获取当前注解对象
*
* @param <T>
* @param joinPoint
* @param clazz
* @return
* @throws NoSuchMethodException
*/
public static <T extends Annotation> T getDeclaredAnnotation(ProceedingJoinPoint joinPoint, Class<T> clazz) throws NoSuchMethodException {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定义的注解信息
T annotation = objMethod.getDeclaredAnnotation(clazz);
// 返回
return annotation;
} public boolean clearCache(String key) {
return clearCache(key, true);
} public boolean clearCache(String key, boolean byUser) {
TreeMap<String, Object> args = new TreeMap<String, Object>();
args.put("key", key);
if (byUser) {
args.put("openId", cacheService.getUserId());
}
String sign = DigestUtils.md5Hex(JacksonUtil.marshallToString(args));
return redisService.delete(RedisKeyPrefixConts.CACHE_REQUEST + sign);
} }

添加切面处理,一般根据三个维度进行缓存(请求地址、用户、请求参数),第一次请求进行返回数据的缓存,后续请求则直接获取缓存数据,不进入接口进行逻辑处理。

有些需要更新信息的场景,需要更新数据后返回最新数据,则可以自定义key,在更新操作时调用clearCache方法即可。

3、项目使用

    // 使用默认配置,过期60S,根据用户维度区分
@CacheRequest
@RequestMapping("/test1")
public void test1() {} // 过期60S,根据用户维度区分
@CacheRequest(60)
@RequestMapping("/test2")
public void test2() {} // 过期60S,不根据用户维度区分
@CacheRequest(expire = 60,byUser = false)
@RequestMapping("/test3")
public void test3() {} // 自定义key,便于后续更新操作可清空缓存,定义key时,说明有更新操作,则只需在业务处理时,注入切面,调用clearCache方法即可
@CacheRequest(expire = 60,key="test4")
@RequestMapping("/test4")
public void test4() {}

实际开发,只需要在请求接口添加注解,根据实际场景配置属性即可

4、测试

可看到第二次请求,直接走的缓存返回结果,未进入接口进行逻辑处理。

大家有疑问或更好的建议,可以提出来,楼主看到会第一时间反应,谢谢。

自定义注解,实现请求缓存【Spring Cache】的更多相关文章

  1. SpringBoot 拦截器和自定义注解判断请求是否合法

    应用场景举例: 当不同身份的用户请求一个接口时,用来校验用户某些身份,这样可以对单个字段数据进行精确权限控制,具体看代码注释 自定义注解 /** * 对比请求的用户身份是否符合 * @author l ...

  2. 自定义注解获取请求Header中的值

    前言 这几天开发一个项目,为了方便,前台将当前登陆人的ID和名称放在每个请求的Header中(这里不考虑安全性之类的),这样后台只要需要用到,就直接从Header中get出来就可以了. 后台实现方法 ...

  3. 基于Spring Cache实现二级缓存(Caffeine+Redis)

    一.聊聊什么是硬编码使用缓存? 在学习Spring Cache之前,笔者经常会硬编码的方式使用缓存. 我们来举个实际中的例子,为了提升用户信息的查询效率,我们对用户信息使用了缓存,示例代码如下: @A ...

  4. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  5. Spring-cloud (八) Hystrix 请求缓存的使用

    前言: 最近忙着微服务项目的开发,脱更了半个月多,今天项目的初版已经完成,所以打算继续我们的微服务学习,由于Hystrix这一块东西好多,只好多拆分几篇文章写,对于一般对性能要求不是很高的项目中,可以 ...

  6. Spring Boot 2.X(七):Spring Cache 使用

    Spring Cache 简介 在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 ...

  7. Spring Cache For Redis

    一.概述 缓存(Caching)可以存储经常会用到的信息,这样每次需要的时候,这些信息都是立即可用的. 常用的缓存数据库: Redis   使用内存存储(in-memory)的非关系数据库,字符串.列 ...

  8. JAVA 框架 Spring Cache For Redis.

    一.概述 缓存(Caching)可以存储经常会用到的信息,这样每次需要的时候,这些信息都是立即可用的. 常用的缓存数据库: Redis   使用内存存储(in-memory)的非关系数据库,字符串.列 ...

  9. 自定义缓存管理器 或者 Spring -- cache

    Spring Cache 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 c ...

  10. Spring Cache扩展:注解失效时间+主动刷新缓存

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

随机推荐

  1. vue数组更改页面无法刷新

    今一个图片列表的数组,在新增数据时页面会同步相应,但是进行删除操作时老是无法实现页面及时刷新,使用过vue set也没见效果,纠结半天,哎原来是嵌套对象的坑 一.图片上传时 页面加的图片列表的html ...

  2. 通过nc获取靶机的反弹Shell [靶机实战]

    1.环境 Kali:172.30.1.3/24 靶机(Funbox9):172.30.1.129/24 2.信息收集 通过nmap扫描此主机,我们需要获取到开放的端口以及服务的Banner 1 nma ...

  3. 泰裤辣!!!手摸手教学,如何训练一个你的专属AI歌姬~

    最近在做AIGC的项目,不过是与图片相关的,现在的模型效果可比前几年图片替换效果好多了.之前尝试过用 faceswap 工具来进行人脸替换的,具体可以参看下我之前的这篇文章:https://blog. ...

  4. 前后端分离架构下使用 Sa-Token 完成登录认证

    一.架构分析 目前绝大多数系统都已经采用 "前后端分离" 架构来设计了,传统的Session模式鉴权也不再适合这种架构(或者需要额外写很多的代码来专门适配). Sa-Token 是 ...

  5. CANoe_ Trace 和 Graphics 窗口的介绍和使用

    Canoe是一款用于汽车网络分析和仿真的工具,其中包括Trace和Graphics两个窗口,用于显示和分析CAN网络数据.以下是对Canoe的Trace和Graphics窗口的简要介绍和使用说明: 1 ...

  6. CAPL 脚本基本语句

    CAPL(Communication Access Programming Language)是一种用于汽车通信网络分析和仿真的脚本语言.以下是CAPL脚本的基本语句: 1.变量声明 variable ...

  7. 揭秘 Task.Wait

    目录 简介 背后的实现 Task.Wait 的两个阶段 SpinWait 阶段 BlockingWait 阶段 Task.Wait 可能会导致的问题 可能会导致线程池饥饿 可能会导致死锁 .NET 6 ...

  8. CKS 考试题整理 (15)-镜像扫描ImagePolicyWebhook

    Context cluster 上设置了容器镜像扫描器,但尚未完全集成到cluster 的配置中. 完成后,容器镜像扫描器应扫描并拒绝易受攻击的镜像的使用. Task 注意:你必须在 cluster ...

  9. 生成式预训练Transformer在机器翻译中的应用与挑战

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 5. 优化与改进 6. 结论与展望 机器翻译是人工智能领域的重要应用之一,而生成式预训练Transforme ...

  10. Maven资源大于配置问题

    资源大于配置问题 <!--pom.xml中在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> < ...