写过一篇 发表于 2025-3-26 06:17:04

架构计划之自定义延迟双删缓存注解(下)

架构计划之自定义延迟双删缓存注解(下)

小薛博客官方架构计划之自定义延迟双删缓存注解(下)所在
为了包管@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 {

    @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, args);
            }
            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 {

    @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, args);
            }
            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 {
    @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

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 hKeyHash键
   * @param value 值
   */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
      redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
   * 获取Hash中的数据
   *
   * @param keyRedis键
   * @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的深度和广度,让你的代码充满生命力,由于你正在用一种巨大的语言塑造着一个充满无限可能的未来。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 架构计划之自定义延迟双删缓存注解(下)