使用SpringAOP+Caffeine实现本地缓存

文章目录

一、背景

公司想对一些不经常变动的数据做一些本地缓存,我们使用AOP+Caffeine来实现

二、实现

1、定义注解

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 本地缓存
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LocalCacheable {

    // 过期时间 默认10分钟
    long expired() default 600;

    // key创建器
    String keyGenerator() default "org.springframework.cache.interceptor.KeyGenerator";
}

2、切面

java 复制代码
import com.google.gson.internal.LinkedTreeMap;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 本地缓存
 */
@Aspect
@Component
public class LocalCacheAspect {

    private static final String separator = ":";

    @Around("@annotation(com.framework.localcache.LocalCacheable)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        if (AopUtils.isAopProxy(point.getTarget())) {
            return point.proceed();
        }

        Method method = getMethodSignature(point).getMethod();
        if (method == null) {
            return point.proceed();
        }

        LocalCacheable annotation = method.getAnnotation(LocalCacheable.class);
        if (annotation == null) {
            return point.proceed();
        }

        // 生成key
        String key = generateKey(point);
//         System.out.println("生成的key:" + key);

        long expired = annotation.expired();

        Throwable[] throwable = new Throwable[1];
        Object proceed = LocalCache.cacheData(key, () -> {
            try {
                return point.proceed();
            } catch (Throwable e) {
                throwable[0] = e;
            }
            return null;
        }, expired);

        if (throwable[0] != null) {
            throw throwable[0];
        }

        return proceed;
    }

    /**
     * 获取方法
     */
    private MethodSignature getMethodSignature(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        if (signature instanceof MethodSignature) {
            return ((MethodSignature) signature);
        }
        return null;
    }

    /**
     * 获取key
     */
    private String generateKey(ProceedingJoinPoint point) {

        // 目标类、方法、参数等
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(point.getTarget());
        Method method = getMethodSignature(point).getMethod();
        String[] parameterNames = getMethodSignature(point).getParameterNames();
        Object[] args = point.getArgs();

        // 解析参数,生成key
        LinkedTreeMap<String, Object> paramResolveResult = new LinkedTreeMap<>();
        if (ArrayUtils.isNotEmpty(args)) {
            for (int i = 0; i < args.length; i++) {
                resolveParam(args[i], paramResolveResult, parameterNames[i]);
            }
        }

        StringBuilder key = new StringBuilder(targetClass.getName() + separator + method.getName() + separator);
        paramResolveResult.forEach((k, v) -> {
            if (v != null) {
                key.append(k + "," + v + separator);
            }
        });

        // 根据方法名和参数生成唯一标识
        return key.toString();
    }


    private void resolveParam(Object param, Map<String, Object> paramResolveResult, String prefix) {
        if (param == null) {
            return;
        }
        Class<?> type = param.getClass();
        if (type == List.class) {
            List<Object> param0 = (List) param;
            for (int i = 0; i < param0.size(); i++) {
                resolveParam(param0.get(i), paramResolveResult, prefix + "[" + i + "]");
            }
        } else if (type == Map.class) {
            Map<Object, Object> param0 = (Map) param;
            param0.forEach((k, v) -> {
                resolveParam(v, paramResolveResult, prefix + "." + k);
            });
        } else if (type.isArray()) {
            Object[] param0 = (Object[]) param;
            for (int i = 0; i < param0.length; i++) {
                resolveParam(param0[i], paramResolveResult, prefix + "[" + i + "]");
            }
        } else if (type == Byte.class
                || type == Short.class
                || type == Integer.class
                || type == Long.class
                || type == Float.class
                || type == Double.class
                || type == Boolean.class
                || type == Character.class
                || type == String.class) {
            paramResolveResult.put(prefix, param);
        } else if (type.getName().startsWith("java.")) {

        } else {
            // 复杂类型
            Map<String, Object> fieldMap = new HashMap<>();
            // CGLIB代理
            if (Enhancer.isEnhanced(type)) {
                getAllFieldsAndValue(param, type.getSuperclass(), fieldMap);
            } else {
                getAllFieldsAndValue(param, type, fieldMap);
            }

            fieldMap.forEach((k, v) -> {
                if (v == null) {
                    return;
                }
                resolveParam(v, paramResolveResult, prefix + "." + k);
            });
        }
    }

    /**
     * 获取所有字段和值
     */
    private void getAllFieldsAndValue(Object o, Class type, Map<String, Object> fieldMap) {

        for (Method method : type.getMethods()) {
            if (method.getName().startsWith("get") && method.getParameterCount() == 0) {
                try {
                    Object value = method.invoke(o);
                    if (value != null) {
                        fieldMap.put(method.getName().substring(3), value);
                    }
                } catch (Exception e) {}
            }
        }
    }


}

3、缓存工具类

java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 本地缓存
 */
public class LocalCache {

    private static final Map<Long, Cache<Object, Object>> cacheMap = new ConcurrentHashMap<>();

    /**
     * 创建本地缓存
     * @param seconds 过期时间:秒
     */
    private static Cache<Object, Object> createCache(long seconds) {
        return Caffeine.newBuilder()
                .expireAfterWrite(seconds, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 创建本地缓存
     * @param seconds 过期时间:秒
     * @param loader 缓存方法
     */
    private Cache<Object, Object> createLoadingCache(long seconds, CacheLoader<Object, Object> loader) {
        return Caffeine.newBuilder()
                .expireAfterWrite(seconds, TimeUnit.SECONDS)
                .build(loader);
    }


    /**
     * 获取一个缓存组
     * @param seconds 缓存过期时间
     */
    private static Cache<Object, Object> getAndLoad(long seconds) {
        if (cacheMap.containsKey(seconds)) {
            return cacheMap.get(seconds);
        }

        Cache<Object, Object> cache = createCache(seconds);
        cacheMap.put(seconds, cache);

        return cache;
    }

    /**
     * 缓存数据,过期时间默认10分钟
     * @param key key
     * @param supplier 数据来源的方法
     */
    public static Object cacheData(Object key, Supplier<Object> supplier) {
        return cacheData(key, supplier, 600);
    }


    /**
     * 缓存数据
     * @param key key
     * @param supplier 数据来源的方法
     * @param seconds 过期时间:秒
     */
    public static Object cacheData(Object key, Supplier<Object> supplier, long seconds) {
        Assert.state(seconds > 0, "过期时间必须大于0秒");
        Cache<Object, Object> cache = getAndLoad(seconds);
        return cache.get(key, k -> supplier.get());
    }
}

三、测试

java 复制代码
    @LocalCacheable
    @GetMapping("test1")
    public String test1() {
        System.out.println("执行了");
        return "success";
    }

    @LocalCacheable
    @GetMapping("test2")
    public String test2(String a) {
        System.out.println("执行了" + a);
        return "success";
    }

    @LocalCacheable
    @GetMapping("test3")
    public String test3(String a, int b, String c) {
        System.out.println("执行了" + a + b + c);
        return "success";
    }

    @LocalCacheable
    @GetMapping("test4")
    public String test4(UserInfo user) {
        System.out.println("执行了" + user);
        return "success";
    }


    @LocalCacheable
    @GetMapping("test5")
    public String test5(UserInfo[] users) {
        System.out.println("执行了" + users);
        return "success";
    }


    @LocalCacheable
    @GetMapping("test6")
    public String test6(List<UserInfo> users) {
        System.out.println("执行了" + users);
        return "success";
    }


    @LocalCacheable
    @GetMapping("test7")
    public String test7(UserInfo user) {
        System.out.println("执行了" + user.getMap());
        return "success";
    }
相关推荐
CoderJia程序员甲2 小时前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存
阳光阿盖尔2 小时前
redis——哨兵机制
数据库·redis·缓存·主从复制·哨兵
看到请催我学习3 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
小登ai学习7 小时前
简单认识 redis -3 -其他命令
数据库·redis·缓存
猿小蔡-Cool7 小时前
CPU 多级缓存
java·spring·缓存
BergerLee20 小时前
对不经常变动的数据集合添加Redis缓存
数据库·redis·缓存
Dylanioucn20 小时前
【分布式微服务云原生】掌握分布式缓存:Redis与Memcached的深入解析与实战指南
分布式·缓存·云原生
wclass-zhengge1 天前
Redis篇(最佳实践)(持续更新迭代)
redis·缓存·bootstrap
Dylanioucn1 天前
【分布式微服务云原生】探索Redis:数据结构的艺术与科学
数据结构·redis·分布式·缓存·中间件
小小娥子2 天前
Redis的基础认识与在ubuntu上的安装教程
java·数据库·redis·缓存