Skip to content

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

为了保证@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的深度和广度,让你的代码充满生命力,因为你正在用一种伟大的语言塑造着一个充满无限可能的未来。