前言

偶尔看到了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. 这可能是最全面的Java学习路线了

    大家好,我是大彬~ 我本科学的不是计算机,大四开始自学Java,并且拿到了几个互联网中大厂的offer.在学习Java这方面还是比较有经验的,下面我来分享下我整理的Java自学路线. 在这里也提醒学弟 ...

  2. .NET 创建无边框的跨平台应用

    .NET 创建无边框的跨平台应用 在创建了Photino应用程序以后我们发现它自带了一个标题栏,并且非常丑,我们现在要做的就是去掉这个很丑的自带标题栏,并且自定义一个更好看的,下面我们将用Masa B ...

  3. 手把手教你如何在 Linux 上源码安装最新版本 R

    如果你使用的 Linux 系统 GCC 版本太低,又没有 root 权限(即使有 root 权限又担心升级 GCC 带来的风险):同时你又不想额外多安装多一个 Anaconda 或者 Minicond ...

  4. 如何在2023年开启React项目

    在这里,我想给你一个新的React项目入门的简要概述.我想反思一下优点和缺点,反思一下作为一个开发者所需要的技术水平,反思一下作为一个React开发者,每个启动项目都能为你提供哪些功能.最后,你将了解 ...

  5. Python3.9安装

    一.安装python3.9 链接:https://pan.baidu.com/s/1mDkgKt2KSoMrKVxesb76Pg?pwd=ma4n 提取码:ma4n --来自百度网盘超级会员V4的分享 ...

  6. mybatis拦截器实现数据权限

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现. 比如员工打卡记录表,有id,name,dpt_id,company_id等字段,后两个表示 ...

  7. Qt源码阅读(五)-deleteLater

    Qt deleteLater作用及源码分析 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 在本篇文章中,我们将深入分析源码,探讨deleteLater的原理. deleteLater是Qt框架提供的 ...

  8. 【Springboot】过滤器

    Springboot实现过滤器 实现过滤器方式有两种: Filter过滤器具体实现类 通过@WebFilter 注解来配置 1.Filter过滤器具体实现类 1.1 实现Filter @Compone ...

  9. spring-boot-plus2.7.12版本重磅发布,三年磨一剑,兄弟们等久了,感谢你们的陪伴

    Everyone can develop projects independently, quickly and efficiently! spring-boot-plus是一套集成spring bo ...

  10. 【活动回顾】WebRTC服务端工程实践和优化探索

    11月7日,即构和上海GDG技术社区联合举办了实时音视频技术云上技术分享专场,来自即构科技和Bilibili的资深技术专家进行了深度分享.大会吸引了众多开发人员交流.观看,并在活动过程中与分享嘉宾进行 ...