Redis+自定義注解+AOP實(shí)現(xiàn)聲明式注解緩存查詢的示例
引言:為什么需要聲明式緩存?
- 背景痛點(diǎn):傳統(tǒng)代碼中緩存邏輯與業(yè)務(wù)邏輯高度耦合,存在重復(fù)代碼、維護(hù)困難等問(wèn)題(如手動(dòng)判斷緩存存在性、序列化/反序列化操作)
- 解決方案:通過(guò)注解+AOP實(shí)現(xiàn)緩存邏輯與業(yè)務(wù)解耦,開發(fā)者只需關(guān)注業(yè)務(wù),通過(guò)注解配置緩存策略(如過(guò)期時(shí)間、防擊穿機(jī)制等)
- 技術(shù)價(jià)值:提升代碼可讀性、降低維護(hù)成本、支持動(dòng)態(tài)緩存策略擴(kuò)展。
核心流程設(shè)計(jì):
方法調(diào)用 → 切面攔截 → 生成緩存Key → 查詢Redis → └ 命中 → 直接返回緩存數(shù)據(jù) └ 未命中 → 加鎖查DB → 結(jié)果寫入Redis → 返回?cái)?shù)據(jù)
二、核心實(shí)現(xiàn)步驟
1. 定義自定義緩存注解(如@RedisCache)
package com.mixchains.ytboot.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * @author 衛(wèi)相yang * OverSion03 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RedisCache { /** * Redis鍵前綴(支持SpEL表達(dá)式) */ String key(); /** * 過(guò)期時(shí)間(默認(rèn)1天) */ long expire() default 1; /** * 時(shí)間單位(默認(rèn)天) */ TimeUnit timeUnit() default TimeUnit.DAYS; /** * 是否緩存空值(防穿透) */ boolean cacheNull() default true; }
2. 編寫AOP切面(核心邏輯)
切面職責(zé):
- 緩存Key生成:拼接類名、方法名、參數(shù)哈希(MD5或SpEL動(dòng)態(tài)參數(shù))本次使用的是SpEL
- 緩存查詢:優(yōu)先從Redis讀取,使用FastJson等工具反序列化
空值緩存:緩存NULL
值并設(shè)置短過(guò)期時(shí)間,防止惡意攻擊
package com.mixchains.ytboot.common.aspect; import com.alibaba.fastjson.JSON; import com.mixchains.ytboot.common.annotation.RedisCache; import io.micrometer.core.instrument.util.StringUtils; import lombok.extern.slf4j.Slf4j; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.expression.EvaluationContext; 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; import java.lang.reflect.Type; /** * @author 衛(wèi)相yang * OverSion03 */ @Aspect @Component @Slf4j public class RedisCacheAspect { @Autowired private RedisTemplate<String, String> redisTemplate; private final ExpressionParser parser = new SpelExpressionParser(); private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @Around("@annotation(redisCache)") public Object around(ProceedingJoinPoint joinPoint, RedisCache redisCache) throws Throwable { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); // 解析SpEL表達(dá)式生成完整key String key = parseKey(redisCache.key(), method, joinPoint.getArgs()); // 嘗試從緩存獲取 String cachedValue = redisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(cachedValue)) { Type returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType(); return JSON.parseObject(cachedValue, returnType); } // 執(zhí)行原方法 Object result = joinPoint.proceed(); // 處理緩存存儲(chǔ) if (result != null || redisCache.cacheNull()) { String valueToCache = result != null ? JSON.toJSONString(result) : (redisCache.cacheNull() ? "[]" : null); if (valueToCache != null) { redisTemplate.opsForValue().set( key, valueToCache, redisCache.expire(), redisCache.timeUnit() ); } } return result; } private String parseKey(String keyTemplate, Method method, Object[] args) { String[] paramNames = parameterNameDiscoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); if (paramNames != null) { for (int i = 0; i < paramNames.length; i++) { context.setVariable(paramNames[i], args[i]); } } return parser.parseExpression(keyTemplate).getValue(context, String.class); } }
代碼片段示例:
@RedisCache( key = "'category:homeSecond:' + #categoryType", //緩存的Key + 動(dòng)態(tài)參數(shù) expire = 1, //過(guò)期時(shí)間 timeUnit = TimeUnit.DAYS // 時(shí)間單位 ) @Override public ReturnVO<List<GoodsCategory>> listHomeSecondGoodsCategory(Integer level, Integer categoryType) { // 數(shù)據(jù)庫(kù)查詢 List<GoodsCategory> dbList = goodsCategoryMapper.selectList( new LambdaQueryWrapper<GoodsCategory>() .eq(GoodsCategory::getCategoryLevel, level) .eq(GoodsCategory::getCategoryType, categoryType) .eq(GoodsCategory::getIsHomePage, 1) .orderByDesc(GoodsCategory::getHomeSort) ); // 設(shè)置父級(jí)UUID(可優(yōu)化為批量查詢) List<Long> parentIds = dbList.stream().map(GoodsCategory::getParentId).distinct().collect(Collectors.toList()); Map<Long, String> parentMap = goodsCategoryMapper.selectBatchIds(parentIds) .stream() .collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getUuid)); dbList.forEach(item -> item.setParentUuid(parentMap.get(item.getParentId()))); return ReturnVO.ok("列出首頁(yè)二級(jí)分類", dbList); }
最終效果:
到此這篇關(guān)于Redis+自定義注解+AOP實(shí)現(xiàn)聲明式注解緩存查詢的示例的文章就介紹到這了,更多相關(guān)Redis 聲明式注解緩存查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis的配置、啟動(dòng)、操作和關(guān)閉方法
今天小編就為大家分享一篇Redis的配置、啟動(dòng)、操作和關(guān)閉方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05redis 實(shí)現(xiàn)登陸次數(shù)限制的思路詳解
這篇文章主要介紹了redis 實(shí)現(xiàn)登陸次數(shù)限制的思路詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08關(guān)于Redis?bigkeys命令會(huì)阻塞問(wèn)題的解決
這篇文章主要介紹了關(guān)于Redis?bigkeys命令會(huì)阻塞問(wèn)題的解決,今天分享一次Redis引發(fā)的線上事故,避免再次踩雷,實(shí)現(xiàn)快速入門,需要的朋友可以參考下2023-03-03redis發(fā)布和訂閱_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了redis發(fā)布和訂閱的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺功能
在高并發(fā)場(chǎng)景下,為了提高秒殺業(yè)務(wù)的性能,可將部分工作交給 Redis 處理,并通過(guò)異步方式執(zhí)行,Redis 提供了多種數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)消息隊(duì)列,總結(jié)三種,本文詳細(xì)介紹Redis消息隊(duì)列實(shí)現(xiàn)異步秒殺功能,感興趣的朋友一起看看吧2025-04-04Redis緩存過(guò)期的實(shí)現(xiàn)示例
Redis緩存的過(guò)期策略是保證緩存可靠性和性能的關(guān)鍵之一,本文主要介紹了Redis緩存過(guò)期的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12