当前位置: 首页 > news >正文

使用自定义注解完成redis缓存

1. 添加依赖 

<dependencies><!-- Spring Boot Starter Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Boot Starter AOP for aspect-oriented programming --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
</dependencies>

 2. 创建自定义@Cacheable注解

package com.ldj.demo.anotations;import java.lang.annotation.*;/*** User: ldj* Date: 2025/7/1* Time: 2:11* Description: No Description*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cacheable {/*** 缓存 key,支持 SpEL 表达式,默认为空则使用 类名:方法名*/String key() default "";/*** 缓存过期时间(秒),默认60秒*/int expireTime() default 60;/*** 是否缓存 null 值(防止缓存穿透),默认不缓存*/boolean cacheNull() default false;
}

3. 配置RedisTemplate

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer());return template;}
}

4. 编写AOP切面逻辑

package com.ldj.demo.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Aspect
@Component
public class CacheAspect {private static final Logger logger = LoggerFactory.getLogger(CacheAspect.class);// 线程安全的表达式解析器private static final ExpressionParser PARSER = new SpelExpressionParser();@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 缓存切面入口*/@Around("@annotation(cacheable)")public Object cache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Object[] args = joinPoint.getArgs();// 生成缓存 keyString key = generateCacheKey(method, args, cacheable.key());// 尝试从缓存中获取数据Object cachedData = getDataFromCache(key);if (cachedData != null) {logger.info("Cache hit for key: {}", key);return cachedData;}logger.info("Cache miss for key: {}", key);// 执行原始方法Object result = executeMethod(joinPoint);// 如果结果为 null 且不缓存 null 值,则跳过缓存if (result == null && !cacheable.cacheNull()) {return result;}// 写入缓存cacheResult(key, result, cacheable.expireTime());return result;}/*** 根据方法签名和参数生成缓存 key** @param method 方法* @param args 参数数组* @param keySpEL 自定义的 SpEL 表达式,例如 "#user.id"* @return 生成的 key*/private String generateCacheKey(Method method, Object[] args, String keySpEL) {StandardEvaluationContext context = new StandardEvaluationContext();try {// 没有指定 key,默认用 "类名:方法名"if (keySpEL == null || keySpEL.trim().isEmpty()) {return getDefaultKey(method);}// 获取参数名(如 user)String[] paramNames = getParameterNames(method);// 把每个参数放入上下文,以便在 SpEL 中使用 #user.id 这样的方式访问for (int i = 0; i < args.length; i++) {String paramName = paramNames != null && i < paramNames.length ? paramNames[i] : "arg" + i;context.setVariable(paramName, args[i]);}// 解析并执行 SpEL 表达式,比如 #user.id 或者 #root.method.nameObject value = PARSER.parseExpression(keySpEL).getValue(context);return value != null ? value.toString() : getDefaultKey(method);} catch (Exception e) {logger.error("Failed to evaluate SpEL expression: {} for method: {}", keySpEL, method.getName(), e);return getDefaultKey(method);}}/*** 默认缓存 key:"com.example.service.UserService:getUserInfo"*/private String getDefaultKey(Method method) {return method.getDeclaringClass().getName() + ":" + method.getName();}/*** 获取方法参数名称(用于 SpEL 表达式)*/private String[] getParameterNames(Method method) {return new org.springframework.core.DefaultParameterNameDiscoverer().getParameterNames(method);}/*** 从 Redis 获取缓存数据*/private Object getDataFromCache(String key) {try {return redisTemplate.opsForValue().get(key);} catch (Exception e) {logger.warn("Error retrieving from cache with key: {}", key, e);return null;}}/*** 设置缓存到 Redis*/private void cacheResult(String key, Object result, long expireTime) {try {if (expireTime > 0) {redisTemplate.opsForValue().set(key, result, expireTime, java.util.concurrent.TimeUnit.SECONDS);} else {redisTemplate.opsForValue().set(key, result);}} catch (Exception e) {logger.warn("Failed to cache result with key: {}", key, e);}}/*** 执行目标方法*/private Object executeMethod(ProceedingJoinPoint joinPoint) throws Throwable {try {return joinPoint.proceed();} catch (Throwable throwable) {logger.error("Error executing method", throwable);throw throwable;}}
}

5. 使用自定义@Cacheable注解

package com.ldj.demo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ldj.demo.anotations.Cacheable;
import com.ldj.demo.mapper.UserInfoMapper;
import com.ldj.demo.model.entity.UserInfoDTO;
import com.ldj.demo.model.entity.UserInfoEntity;
import com.ldj.demo.service.UserInfoService;
import org.springframework.stereotype.Service;/*** User: ldj* Date: 2025/6/19* Time: 14:57* Description: No Description*/
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfoEntity> implements UserInfoService {// 使用 SpEL 表达式从 UserInfoDTO 提取 id 作为 key@Override@Cacheable(key = "#user.id", cacheNull = false, expireTime = 60)public String getUserInfo(UserInfoDTO user) {return "User info fetched for: " + user.getUsername();}
}
package com.ldj.demo;import com.ldj.demo.model.entity.UserInfoDTO;
import com.ldj.demo.service.UserInfoService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.assertEquals;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;@SpringBootTest
public class DemoApplicationTests {@Autowiredprivate UserInfoService userService;@Autowiredprivate StringRedisTemplate redisTemplate;@BeforeEachpublic void setUp() {redisTemplate.getConnectionFactory().getConnection().flushDb();}@Testpublic void testCacheWithUserInfoDTO() {// 创建用户信息对象UserInfoDTO user = new UserInfoDTO(123243578798L, "john_doe");// 第一次调用方法,应该执行实际的方法逻辑并缓存结果String firstCallResult = userService.getUserInfo(user);// 第二次使用相同的参数调用方法,期望直接从缓存中获取数据String secondCallResult = userService.getUserInfo(user);// 验证两次调用的结果相同,表明缓存机制工作正常assertEquals(firstCallResult, secondCallResult, "The results should be the same as it's cached.");}}

http://www.dtcms.com/a/263382.html

相关文章:

  • Windows Excel文档办公工作数据整理小工具
  • SpringCloud系列(43)--搭建SpringCloud Config客户端
  • Install Ubuntu 24.04 System
  • SpringCloud系列(42)--搭建SpringCloud Config分布式配置总控中心(服务端)
  • ProPlus2024Retail 安装教程(详细步骤+激活方法)- 最新版安装包下载与使用指南
  • mysql运维语句
  • window显示驱动开发—在注册表中设置 DXGI 信息
  • SCAU期末笔记 - 操作系统 选填题
  • 【机器学习第四期(Python)】LightGBM 方法原理详解
  • 跨主机用 Docker Compose 部署 PostgreSQL + PostGIS 主从
  • [特殊字符]【联邦学习实战】用 PyTorch 从 0 搭建一个最简单的联邦学习系统(含完整代码)
  • 编程新手之环境搭建:node python
  • [论文阅读] Neural Architecture Search: Insights from 1000 Papers
  • 创客匠人解析知识变现赛道:从 IP 孵化到商业闭环的核心策略
  • xilinx axi datamover IP使用demo
  • 【STM32HAL-第1讲 基础篇-单片机简介】
  • C#数字格式化全解析:从基础到进阶的实战指南
  • 腾讯云空间,高性能显卡云,安装xinference报错,pip install 空间不够用了
  • leedcode:找到字符串中所有字母异位词
  • 04密码加密
  • 中钧科技参加中亚数字经济对话会,引领新疆企业数字化新征程!
  • 【Teensy】在ArduinoIDE中配置Teensy4.1
  • LoRA 实战指南:NLP 与 CV 场景的高效微调方法全解析
  • 非常详细版: dd.device.geolocation 钉钉微应用获取定位,移动端 PC端都操作,Vue实现钉钉微应用获取精准定位并渲染在地图组件上
  • 强化学习概述及学习流程
  • 视频讲解:门槛效应模型Threshold Effect分析数字金融指数与消费结构数据
  • spring-ai 工作流
  • LG 将正式终止手机相关服务,彻底告别手机市场
  • 机器人、灵巧手动捕方案 | 突破底层适配,动捕数据直通仿真平台
  • 【科研绘图系列】R语言绘制世界地图分布(world map)