Appearance
架构设计之自定义延迟双删缓存注解(下)
为了保证@Cache
和@ClearAndReloadCache
的灵活性,特意加入EL表达式解析
1、Cache
java
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
java
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 {
@Resource
private 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
java
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
java
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 {
@Resource
private 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
java
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 {
@Resource
private 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
java
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 {
@Resource
public 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的深度和广度,让你的代码充满生命力,因为你正在用一种伟大的语言塑造着一个充满无限可能的未来。