架構設計之自定義延遲雙刪緩存注解(下)
小薛博客官方架構設計之自定義延遲雙刪緩存注解(下)地址
為了保證@Cache
和@ClearAndReloadCache
的靈活性,特意加入EL表達式解析
1、Cache
package com.xx.cache;import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;/*** @Author: xueqimiao* @Date: 2025/3/17 14:24*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {/*** 過期時間,默認60s* @return*/long expire() default 60 ;TimeUnit unit() default TimeUnit.SECONDS;/*** 緩存標識name* @return*/String name() default "";/*** SpringEL表達式,解析占位符對應的匹配value值* @return*/String matchValue();}
2、CacheAspect
package com.xx.cache;import com.xx.common.Result;
import com.xx.utils.RedisService;
import com.xx.utils.ValidationUtil;
import jakarta.annotation.Resource;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
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.util.concurrent.TimeUnit;/*** @Author: xueqimiao* @Date: 2025/3/17 14:25*/
@Component
@Aspect
@Slf4j
public class CacheAspect {@Resourceprivate RedisService redisService;/*** aop切點* 攔截被指定注解修飾的方法*/@Pointcut("@annotation(com.xx.cache.Cache)")public void cache() {}/*** 緩存操作** @param pjp* @return*/@Around("cache()")public Object toCache(ProceedingJoinPoint joinPoint) {Object result = null;try {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(),signature.getMethod().getParameterTypes());Cache annotation = method.getAnnotation(Cache.class);String matchedValue = annotation.matchValue();String keyPrefix = annotation.name();long time = annotation.expire();TimeUnit unit = annotation.unit();// 解析EL表達式SpelExpressionParser parser = new SpelExpressionParser();Expression expression = parser.parseExpression(matchedValue);EvaluationContext context = new StandardEvaluationContext();// 獲取參數Object[] args = joinPoint.getArgs();DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();String[] parameterNames = discoverer.getParameterNames(method);for (int i = 0; i < parameterNames.length; i++) {context.setVariable(parameterNames[i], args[i]);}String key = keyPrefix + "::" + expression.getValue(context).toString();result = redisService.get(key);if (!ValidationUtil.isEmpty(result)) {return Result.ok(result);}// 執行目標方法result = joinPoint.proceed();Object res = result;if (result instanceof Result) {res = ((Result<?>) result).getResult();}redisService.set(key, res, time, unit);} catch (Throwable e) {throw new RuntimeException(e);}return result;}}
3、ClearAndReloadCache
package com.xx.cache;import java.lang.annotation.*;/*** @Author: xueqimiao* @Date: 2025/3/17 14:26*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {/*** 緩存標識name* @return*/String name() default "";/*** SpringEL表達式,解析占位符對應的匹配value值* @return*/String matchValue();
}
4、ClearAndReloadCacheAspect
package com.xx.cache;import com.xx.utils.RedisUtils;
import jakarta.annotation.Resource;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** @Author: xueqimiao* @Date: 2025/3/17 14:26*/
@Aspect
@Component
@Slf4j
public class ClearAndReloadCacheAspect {@Resourceprivate RedisUtils redisUtils;/*** 切入點* 切入點,基于注解實現的切入點 加上該注解的都是Aop切面的切入點*/@Pointcut("@annotation(com.xx.cache.ClearAndReloadCache)")public void pointCut() {}/*** 環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型** @param proceedingJoinPoint*/@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint joinPoint) {log.info("----------- 環繞通知 -----------");log.info("環繞通知的目標方法名:" + joinPoint.getSignature().getName());try {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(),signature.getMethod().getParameterTypes());ClearAndReloadCache annotation = method.getAnnotation(ClearAndReloadCache.class);//反射得到自定義注解的方法對象String matchedValue = annotation.matchValue();String keyPrefix = annotation.name(); //獲取自定義注解的方法對象的參數即name// 解析EL表達式SpelExpressionParser parser = new SpelExpressionParser();Expression expression = parser.parseExpression(matchedValue);EvaluationContext context = new StandardEvaluationContext();// 獲取參數Object[] args = joinPoint.getArgs();DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();String[] parameterNames = discoverer.getParameterNames(method);for (int i = 0; i < parameterNames.length; i++) {context.setVariable(parameterNames[i], args[i]);}String key = keyPrefix + "::" + expression.getValue(context).toString();redisUtils.del(key);//模糊刪除redis的key值//執行加入雙刪注解的改動數據庫的業務 即controller中的方法業務Object proceed = null;try {proceed = joinPoint.proceed();//放行} catch (Throwable throwable) {throwable.printStackTrace();}//新開開一個線程延遲0.5秒(時間可以改成自己的業務需求),等著mysql那邊業務操作完成//在線程中延遲刪除 同時將業務代碼的結果返回 這樣不影響業務代碼的執行new Thread(() -> {try {Thread.sleep(500);redisUtils.del(key);log.info("-----------0.5秒后,在線程中延遲刪除完畢 -----------");} catch (InterruptedException e) {e.printStackTrace();}}).start();return proceed;//返回業務代碼的值} catch (Throwable e) {throw new RuntimeException(e);}}
}
5、UserController
package com.xx.controller;import com.xx.cache.Cache;
import com.xx.cache.ClearAndReloadCache;
import com.xx.common.Result;
import com.xx.entity.User;
import com.xx.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;/*** @Author: xueqimiao* @Date: 2025/3/17 14:27*/
@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate UserService userService;@PostMapping("/updateData")@ClearAndReloadCache(name = "user", matchValue = "#user.id")public Result updateData(@RequestBody User user) {return userService.update(user);}@GetMapping("/get")@Cache(name = "user", matchValue = "#id")public Result get(@RequestParam Integer id) {return userService.get(id);}}
6、RedisService
package com.xx.utils;import jakarta.annotation.Resource;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** @Author: xueqimiao* @Date: 2023/7/17 13:46*/
@Component
public class RedisService {@Resourcepublic RedisTemplate redisTemplate;/*** 默認過期時長,單位:秒 一天*/public final static long DEFAULT_EXPIRE = 60 * 60 * 24;/*** 不設置過期時長*/public final static long NOT_EXPIRE = -1;private static double size = Math.pow(2, 32);/*=======================================String - Object=======================================*//*** 緩存基本的對象,Integer、String、實體類等** @param key* @param value* @return*/public boolean set(final String key, Object value) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 寫入緩存設置時效時間** @param key* @param value* @return*/public boolean set(final String key, Object value, Long expireTime) {return set(key, value, expireTime, TimeUnit.SECONDS);}public boolean set(final String key, Object value, Long expireTime, TimeUnit unit) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, unit);result = true;} catch (Exception e) {e.printStackTrace();}return result;}public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}/*** 獲得緩存的Object對象** @param key 緩存的鍵值* @return 緩存鍵值對應的數據*/public Object getCache(final String key) {return redisTemplate.opsForValue().get(key);}/*** 獲得緩存的基本對象。** @param key 緩存鍵值* @return 緩存鍵值對應的數據*/public <T> T getCacheObject(final String key) {ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 獲得緩存的基本對象。** @param key 緩存鍵值* @return 緩存鍵值對應的數據*/public <T> T get(final String key) {ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 讀取緩存** @param key* @return*/public <T> T get(final String key, Class<T> clazz) {ValueOperations<Serializable, Object> operation = redisTemplate.opsForValue();T result = (T) operation.get(key);return result;}/*** 設置有效時間** @param key Redis鍵* @param timeout 超時時間* @return true=設置成功;false=設置失敗*/public boolean expire(final String key, final long timeout) {return expire(key, timeout, TimeUnit.SECONDS);}/*** 設置有效時間** @param key Redis鍵* @param timeout 超時時間* @param unit 時間單位* @return true=設置成功;false=設置失敗*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/*** 獲取有效時間** @param key Redis鍵* @return 有效時間*/public long getExpire(final String key) {return redisTemplate.getExpire(key);}/*** 判斷 key是否存在** @param key 鍵* @return true 存在 false不存在*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 刪除單個對象** @param key*/public void delete(String key) {redisTemplate.delete(key);}/*** 刪除集合對象** @param collection*/public void delete(Collection collection) {redisTemplate.delete(collection);}/*** 判斷緩存中是否有對應的value** @param key* @return*/public boolean exists(final String key) {return redisTemplate.hasKey(key);}/*** 累加 1** @param key 緩存的鍵值* @return*/public Long increment(final String key) {return increment(key, 1L);}public Long decrement(final String key) {return decrement(key, 1L);}/*** 累加 指定值** @param key 緩存的鍵值* @param num 累加的數量* @return*/public Long increment(final String key, Long num) {return redisTemplate.opsForValue().increment(key, num);}public Long decrement(final String key, Long num) {return redisTemplate.opsForValue().decrement(key, num);}/*** 獲得緩存的基本對象key列表** @param pattern 字符串前綴* @return 對象列表*/public Collection<String> keys(final String pattern) {return redisTemplate.keys(pattern);}/*=======================================List=======================================*//*** 緩存List數據** @param key 緩存的鍵值* @param dataList 待緩存的List數據* @return 緩存的對象*/public <T> long listRightPush(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}public long listRightPush(String key, String value) {Long listSize = redisTemplate.opsForList().rightPush(key, value);return null == listSize ? 0 : listSize;}public <T> long listLeftPush(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().leftPushAll(key, dataList);return count == null ? 0 : count;}public long listLeftPush(String key, String value) {Long listSize = redisTemplate.opsForList().leftPush(key, value);return null == listSize ? 0 : listSize;}public long listRemove(String key, long count, String value) {Long remove = redisTemplate.opsForList().remove(key, count, value);return null == remove ? 0 : remove;}/*** 獲得緩存的list對象** @param key 緩存的鍵值* @return 緩存鍵值對應的數據*/public <T> List<T> getCacheList(final String key) {return redisTemplate.opsForList().range(key, 0, -1);}/*** 緩存List數據** @param key 緩存的鍵值* @param dataList 待緩存的List數據* @return 緩存的對象*/public <T> long setCacheList(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 列表獲取** @param k* @param start* @param end* @return*/public <T> List<T> listRange(String k, long start, long end) {ListOperations<String, T> list = redisTemplate.opsForList();return list.range(k, start, end);}public <T> List<T> listRange(String k) {return listRange(k, 0, -1);}/*=======================================Set=======================================*//*** 緩存Set** @param key 緩存鍵值* @param dataSet 緩存的數據* @return 緩存數據的對象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()) {setOperation.add(it.next());}return setOperation;}/*** 獲得緩存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key) {return redisTemplate.opsForSet().members(key);}/*** 集合添加** @param key* @param value*/public void add(String key, Object value) {SetOperations<String, Object> set = redisTemplate.opsForSet();set.add(key, value);}/*** 集合獲取** @param key* @return*/public Set<Object> setMembers(String key) {SetOperations<String, Object> set = redisTemplate.opsForSet();return set.members(key);}public <T> T pop(String key) {SetOperations<String, T> set = redisTemplate.opsForSet();return set.pop(key);}public <T> List<T> pop(String key, Long num) {SetOperations<String, T> set = redisTemplate.opsForSet();return set.pop(key, num);}/*=======================================ZSet=======================================*//*** 有序集合添加** @param key* @param value* @param scoure*/public void zAdd(String key, Object value, double scoure) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();zset.add(key, value, scoure);}/*** 有序集合獲取** @param key* @param scoure* @param scoure1* @return*/public Set<Object> rangeByScore(String key, double scoure, double scoure1) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();redisTemplate.opsForValue();return zset.rangeByScore(key, scoure, scoure1);}/*** 有序集合獲取排名** @param key 集合名稱* @param value 值*/public Long zRank(String key, Object value) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();return zset.rank(key, value);}/*** 有序集合獲取排名** @param key*/public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start, long end) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key, start, end);return ret;}/*** 有序集合添加** @param key* @param value*/public Double zSetScore(String key, Object value) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();return zset.score(key, value);}/*** 有序集合添加分數** @param key* @param value* @param scoure*/public void incrementScore(String key, Object value, double scoure) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();zset.incrementScore(key, value, scoure);}/*** 有序集合獲取排名** @param key*/public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start, long end) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key, start, end);return ret;}/*** 有序集合獲取排名** @param key*/public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);return ret;}/*=======================================Hash=======================================*//*** 緩存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 獲得緩存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key) {return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入數據** @param key Redis鍵* @param hKey Hash鍵* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value) {redisTemplate.opsForHash().put(key, hKey, value);}/*** 獲取Hash中的數據** @param key Redis鍵* @param hKey Hash鍵* @return Hash中的對象*/public <T> T getCacheMapValue(final String key, final String hKey) {HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 獲取多個Hash中的數據** @param key Redis鍵* @param hKeys Hash鍵集合* @return Hash對象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 哈希 添加** @param key* @param hashKey* @param value*/public void hmSet(String key, Object hashKey, Object value) {HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();hash.put(key, hashKey, value);}/*** 哈希獲取數據** @param key* @param hashKey* @return*/public Object hmGet(String key, Object hashKey) {HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();return hash.get(key, hashKey);}/*=======================================Bit=======================================*//*** 寫入緩存** @param key* @param offset 位 8Bit=1Byte* @return*/public boolean setBit(String key, long offset, boolean isShow) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.setBit(key, offset, isShow);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 寫入緩存** @param key* @param offset* @return*/public boolean getBit(String key, long offset) {boolean result = false;try {ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();result = operations.getBit(key, offset);} catch (Exception e) {e.printStackTrace();}return result;}public void saveDataToRedis(String name) {Double index = Math.abs(name.hashCode() % size);long indexLong = index.longValue();boolean availableUsers = setBit("availableUsers", indexLong, true);}public boolean getDataToRedis(String name) {Double index = Math.abs(name.hashCode() % size);long indexLong = index.longValue();return getBit("availableUsers", indexLong);}
}
博客心語
在Java編程的征程中,我們都是追夢人。每一次編寫代碼,都是在編織一個關于未來的故事。這個故事可能是一個高效的電商系統,讓購物變得更加便捷;也可能是一個智能的醫療應用,拯救無數的生命。無論你的目標是什么,Java都是你實現夢想的有力武器。所以,不要停下你前進的腳步,不斷探索Java的深度和廣度,讓你的代碼充滿生命力,因為你正在用一種偉大的語言塑造著一個充滿無限可能的未來。