前言

偶尔看到了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. App性能测试之iTest

    本文主要介绍下App性能测试工具iTest_V4.7的使用. 功能简介 1.监控Andorid系统(支持手机,平板,电视,车机等智能终端设备)以及应用app的cpu.内存.流量.电池.帧率.页面耗时等 ...

  2. 如何使用Stable Diffusion生成艺术二维码?

    硬件准备 物理内存:至少16G(8G直接安装阶段就卡死) N卡:此处我使用GTX 1660 6G (2019年双12购买) 操作系统 windows 11 软件准备 网络要通畅 git: https: ...

  3. Spark架构与运行流程

    1. 阐述Hadoop生态系统中,HDFS, MapReduce, Yarn, Hbase及Spark的相互关系. 2. Spark已打造出结构一体化.功能多样化的大数据生态系统,请简述Spark生态 ...

  4. 前端vue自定义table 表格 表格组件 Excel组件

    快速实现vue uni-app自定义table 表格 表格组件 Excel组件,扩充性好,可切换四宫格 九宫格 十二宫格; 下载完整代码请访问uni-app插件市场地址:https://ext.dcl ...

  5. WakaTime Readme Stats-开源项目翻译

    寻找不同语言和地区的翻译 #23 Readme中添加了功能标志的开发指标 眼前一亮的Readme统计数据 你是早起的还是夜间的? 你一天中什么时候工作效率最高? 你用什么语言编写代码? 让我们在你的个 ...

  6. influxdb 中得 fields 与 tag 区别总结

    本位为博主原创,转载请注明出处: 1.Field与Tag说明 在 InfluxDB 表结构中,field 和 tag 是用于存储数据的两种不同类型. Field(字段): Field 用于存储实际的数 ...

  7. VSCode 编辑器的基本配置

    VSCode 编辑器的基本配置 在正式开始本文的内容之前,请允许我先做一些自我介绍: 严格来说,我是个自由职业者,经常会参与一些计算机专著的写作与翻译工作(主要作品如下图所示),业余偶尔也会有一些机会 ...

  8. Java扩展Nginx之二:编译nginx-clojure源码

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 为什么要编译nginx-clojure源码 作为< ...

  9. NodeJS使用npm安装vue脚手架

    开发环境准备:Windows10.Windows11 NodeJS,安装官网最新LTS版即可 下载地址:https://nodejs.org/安装一路下一步,默认即可 ================ ...

  10. 【Docker】迷你使用手册

    一.安装与配置 安装: # Centos7 yum install docker 启动 & 设为开机启动: systemctl start docker.service systemctl e ...