注解式+手动式自动异步更新的本地缓存(基于本地缓存Caffeine二次封装,开放源码)

注解式+手动式热更新本地缓存

使用环境为spring+maven+lombok+log+caffeine项目
github地址:https://github.com/HumorChen/LocalCache

/**
 * @author: humorchen
 * date: 2023/11/21
 * description: 本地异步热缓存的注解,基于安全的caffeine二次封装 <br>
 * 支持注解使用普通的方法缓存和高效的自更新方法缓存(缓存后每n秒自动异步执行一次方法更新缓存值)  <br>
 * 支持自动每分钟打印缓存监控,掌握缓存命中、耗时等指标数据  <br>
 * 支持查看每次是否命中缓存,每次执行参数、返回值等  <br>
 * 支持设置初始容量、最大容量、线程安全、不会内存溢出 <br>
 * 普通缓存示范 <br>
 * <code>@LocalCache(expireAfterWrite = 5)<code/> <br>
 * 异步自更新缓存示范
 * <code>@LocalCache(expireAfterWrite = 10,refreshAfterWrite = 3)<code/> <br>
 * 写入的缓存10秒过期,3秒后若有请求访问该key,这次直接返回缓存内的值,然后触发异步加载这个key(key对象里提前保存好了方法所有参数,调用方法执行) <br>
 * 是否为相同key的自定义判定可以通过覆写方法参数对象类的equals方法和hash方法实现,判定规则为equals方法为true或json序列化结果相同,优先equals <br>
 **/

使用示范
注解方式:在这里插入图片描述
手动定义方式:

/**
     * 手机某模型的最大价格缓存
     * 10秒无访问失效,写入3秒后若有访问则会自动更新缓存数据
     */
    private Cache<String, BigDecimal> modelMaxPriceCache = LocalCacheUtil.newCacheBuilder("modelMaxPriceCache").basicAutoRefreshCacheConfig(512, 4096, 10, 5).build(this::getMaxPrice);

LocalCache

一个线程&内存安全、自动异步加载缓存、命中率等状态监控的本地缓存,基于caffeine二次封装

支持注解使用普通的方法缓存和高效的自更新方法缓存(缓存后每n秒自动异步执行一次方法更新缓存值)

支持自动每分钟打印缓存监控,掌握缓存命中、耗时等指标数据

支持查看每次是否命中缓存,每次执行参数、返回值等

默认设置初始容量、最大容量、线程池等参数,线程安全、预防内存溢出
支持全局配置启用/关闭缓存,启用/关闭 单个/全局日志打印,启用/关闭状态信息打印

注解式

普通缓存示范

@LocalCache(expireAfterWrite = 5)

写入的缓存5秒后过期。5秒内对于相同参数调用该方法不再执行方法逻辑,直接返回缓存中的返回对象

异步自动更新缓存示范

@LocalCache(expireAfterWrite = 10,refreshAfterWrite = 3)

写入的缓存10秒过期,3秒后若有请求访问该key,这次直接返回缓存内的值,然后触发异步加载这个key(key对象里提前保存好了方法所有参数,调用方法执行) ,将缓存值刷新为最新值。
是否为相同key的自定义判定可以通过覆写方法参数对象类的equals方法和hash方法实现,判定规则为equals方法为true或json序列化结果相同,优先equals

手动构建控制

每个参数都带有安全可靠的默认值,预防内存溢出

普通缓存示范

Cache<Object, Object> cache = LocalCacheUtil.newCacheBuilder("your cache name").setExpireAfterWrite(5).setTimeUnit(TimeUnit.SECONDS).setInitCapacity(128).setMaxCapacity(1024).build();

上述代码建立了一个名字为"your cache name"的缓存,缓存在写入5秒后过期,缓存空间初始容量128,最大容量1024

异步自动更新缓存示范

Cache<Object, Object> cache = LocalCacheUtil.newCacheBuilder("your cache name").setExpireAfterWrite(600).setRefreshAfterWrite(2).build((key)->{
            return yourLoadMethod(key);
        });

上述代码建立了一个名字为"your cache name"的缓存,缓存在写入600秒后过期,若在该key写入缓存2秒后再次被访问则触发异步加载该缓存key最新的值,加载方法为yourLoadMethod(key)

参数默认值

    /**
     * 缓存的时间单位
     */
    private final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
    /**
     * 线程池初始线程数 默认值,无任务执行不占用CPU
     */
    private static final int DEFAULT_THREAD = 4;
    /**
     * 线程池最大线程数 默认值
     */
    private final int DEFAULT_MAX_THREAD = 128;
    /**
     * 线程池额外线程数存活时间 默认值
     */
    private final int DEFAULT_THREAD_KEEP_ALIVE = 10;
    /**
     * 线程池额外线程数存活时间单位 默认值
     */
    private final TimeUnit DEFAULT_THREAD_KEEP_ALIVE_TIMEUNIT = TimeUnit.MINUTES;

    /**
     * 线程池拒绝策略 默认值
     */
    private final RejectedExecutionHandler DEFAULT_THREAD_EXECUTOR_ABORT_POLICY = new ThreadPoolExecutor.AbortPolicy();
    /**
     * 初始化缓存容量默认值
     */
    private final int DEFAULT_INIT_CAPACITY = 128;
    /**
     * 缓存最大容量默认值
     */
    private final int DEFAULT_MAX_CAPACITY = 4096;
    /**
     * 线程池等待队列的容量默认值 用的链表不用担心浪费内存
     */
    private final int DEFAULT_BLOCKING_QUEUE_SIZE = 4096;

监控打印

监控打印日志默认打开

默认每分钟打印1次,打印结果示范如下

2023-12-04 10:01:30.621 INFO [] 1 --- [ scheduling-1] c.s.r.c.localcache.LocalCacheMonitor : -----------------------本地缓存状态打印-----------------------
2023-12-04 10:01:30.622 INFO [] 1 --- [ scheduling-1] c.s.r.c.localcache.LocalCacheMonitor : 【本地缓存状态】key:METHOD-CACHE-cn.xxx.recovery.manage.service.impl.xxxServiceImpl#getHttpParam(java.lang.String) 总请求数:1318400 次 ,缓存命中率:99.99%,平均加载耗时:125 ms,淘汰key次数:144 次,缓存命中次数:1318255 次,缓存未命中次数:145次 缓存配置:{"initCapacity":1,"enableLog":true,"maxCapacity":4,"expireAfterWrite":10,"refreshAfterWrite":5,"timeUnit":"SECONDS"}
2023-12-04 10:01:30.622 INFO [] 1 --- [ scheduling-1] c.s.r.c.localcache.LocalCacheMonitor : -----------------------本地缓存状态打印结束-----------------------

有改进建议和其他需求欢迎提出,逐个处理。

源码

  • 引入caffene
		<dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.8.0</version>
        </dependency>

  • 本地源码
    在这里插入图片描述
    注解

/**
 * @author: humorchen
 * date: 2023/11/21
 * description: 本地异步热缓存的注解,基于安全的caffeine二次封装 <br>
 * 支持注解使用普通的方法缓存和高效的自更新方法缓存(缓存后每n秒自动异步执行一次方法更新缓存值)  <br>
 * 支持自动每分钟打印缓存监控,掌握缓存命中、耗时等指标数据  <br>
 * 支持查看每次是否命中缓存,每次执行参数、返回值等  <br>
 * 支持设置初始容量、最大容量、线程安全、不会内存溢出 <br>
 * 普通缓存示范 <br>
 * <code>@LocalCache(expireAfterWrite = 5)<code/> <br>
 * 异步自更新缓存示范
 * <code>@LocalCache(expireAfterWrite = 10,refreshAfterWrite = 3)<code/> <br>
 * 写入的缓存10秒过期,3秒后若有请求访问该key,这次直接返回缓存内的值,然后触发异步加载这个key(key对象里提前保存好了方法所有参数,调用方法执行) <br>
 * 是否为相同key的自定义判定可以通过覆写方法参数对象类的equals方法和hash方法实现,判定规则为equals方法为true或json序列化结果相同,优先equals <br>
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface LocalCache {
    /*
    缓存初始化容量
     */
    int initCapacity() default 128;

    /*
    缓存最大容量
     */
    int maxCapacity() default 4096;

    /*
    缓存过期秒数
     */
    int expireAfterWrite() default 3;

    /*
    缓存刷新秒数
    缓存写入成功refreshAfterWrite秒后如果该缓存被访问了,则异步加载一次更新缓存值,让值为最新的,下次更新需要再等refreshAfterWrite
    若不需要异步自动刷新则不设置该参数即可(-1代表关闭)
    配置时建议expireSecond >= refreshAfterWrite * 2
     */
    int refreshAfterWrite() default -1;

    /*
    时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /*
    打印缓存变更日志方便查看缓存情况
     */
    boolean enableLog() default false;
}


切面



/**
 * @author: humorchen
 * date: 2023/11/21
 * description: 本地缓存的实现类切面
 **/
@Aspect
@Component
@Slf4j
public class LocalCacheAspect {
    /**
     * 线程编号
     */
    private final AtomicInteger threadNum = new AtomicInteger(0);
    /**
     * 共用线程池
     */
    private final Executor executor = new ThreadPoolExecutor(32, 128, 10, TimeUnit.MINUTES, new ArrayBlockingQueue<>(4096), r -> new Thread(r, "【本地缓存】thread" + threadNum.incrementAndGet()), new ThreadPoolExecutor.CallerRunsPolicy());
    /**
     * cache容器
     */
    private final Map<String, Cache<LocalCacheKey, Object>> cacheMap = new ConcurrentHashMap<>();
    /**
     * 初始化锁
     */
    private final ReentrantLock reentrantLock = new ReentrantLock();
    @Autowired
    private LocalCacheGlobalConfig config;

    /**
     * 切入点
     */
    @Pointcut(value = "@annotation(cn.sffix.recovery.common.localcache.LocalCache)")
    public void pointcut() {

    }

    /**
     * 是否打开了日志
     *
     * @param localCache
     * @return
     */
    private boolean isLogEnable(LocalCache localCache) {
        return config.isEnableLog() || localCache.enableLog();
    }
    /**
     * 切面
     *
     * @param joinPoint
     * @return
     */
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 全局禁用了缓存直接返回
        if (config.isDisabled()) {
            return joinPoint.proceed();
        }
        Object ret = null;
        // 拦截之后看有没有创建缓存,没有的话上锁直接执行创建缓存
        try {
            Object[] args = joinPoint.getArgs();
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            Object target = joinPoint.getTarget();
            if (method != null & target != null) {
                LocalCache localCache = method.getAnnotation(LocalCache.class);
                String methodKey = getMethodCacheKey(method);
                LocalCacheKey key = new LocalCacheKey(methodKey, localCache, target, method, args);
                Cache<LocalCacheKey, Object> cache = getOrInitCache(key);
                if (cache != null) {
                    // 加载缓存
                    boolean onCache = true;
                    ret = cache.getIfPresent(key);
                    if (ret == null) {
                        ret = this.cacheLoader(key);
                        cache.put(key, ret);
                        onCache = false;
                    }
                    if (isLogEnable(localCache)) {
                        log.info("本地缓存:{} 参数:{} 处理完毕,{}命中缓存,返回结果:{}", methodKey, JSONObject.toJSONString(args), onCache ? "已" : "未", JSONObject.toJSONString(ret));
                    }
                } else {
                    // 意外找不到缓存直接走自己的
                    log.error("本地缓存出现意外找不到缓存也初始化失败");
                    ret = joinPoint.proceed();
                }
            }
        } catch (Throwable e) {
            log.error("本地方法缓存切面执行报错", e);
            throw e;
        }
        return ret;

    }

    /**
     * 缓存加载
     *
     * @param k
     * @return
     */
    private Object cacheLoader(LocalCacheKey k) {
        try {
            if (isLogEnable(k.getLocalCache())) {
                log.info("本地缓存:{} 执行被代理方法加载最新值,执行参数:{}", k.getMethodKey(), JSONObject.toJSONString(k.getArgs()));
            }
            return k.getMethod().invoke(k.getTarget(), k.getArgs());
        } catch (InvocationTargetException | IllegalAccessException e) {
            log.error("本地缓存:" + k.getMethodKey() + " 动态加载缓存值报错", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取缓存对象,当不存在的时候初始化,是线程安全的
     * @param key
     * @return
     */
    private Cache<LocalCacheKey, Object> getOrInitCache(LocalCacheKey key) {
        Cache<LocalCacheKey, Object> cache = cacheMap.get(key.getMethodKey());
        if (cache == null) {
            cache = initMethodCacheAndGet(key);
        }
        return cache;
    }

    /**
     * 上锁初始化并返回cache对象,是线程安全的
     * @param key
     * @return
     */
    private Cache<LocalCacheKey, Object> initMethodCacheAndGet(LocalCacheKey key) {
        String methodKey = key.getMethodKey();
        LocalCache localCache = key.getLocalCache();
        reentrantLock.lock();
        Cache<LocalCacheKey, Object> cache = null;
        try {
            if (isLogEnable(localCache)) {
                log.info("本地缓存:{} 开始初始化", methodKey);
            }
            cache = cacheMap.get(methodKey);
            // 上锁后二次校验
            if (cache == null) {
                // 开始初始化
                // 检查参数
                Assert.isTrue(localCache.initCapacity() >= 1);
                Assert.isTrue(localCache.initCapacity() <= localCache.maxCapacity());
                Assert.isTrue(localCache.expireAfterWrite() >= 1);
                Assert.isTrue(localCache.timeUnit().toMillis(localCache.expireAfterWrite()) >= 50, () -> new IllegalArgumentException("缓存有效时间最小为50ms"));
                Assert.isTrue(localCache.expireAfterWrite() > localCache.refreshAfterWrite(), () -> new IllegalArgumentException("缓存有效时间需大于缓存动态更新时间"));
                if (isAutoAsyncRefresh(localCache)) {
                    Assert.isTrue(localCache.timeUnit().toMillis(localCache.expireAfterWrite() - localCache.refreshAfterWrite()) > 100, () -> new IllegalArgumentException("缓存有效时间与缓存动态更新时间之差需大于100ms"));
                }

                // 移除key日志打印监听器
                RemovalListener<Object, Object> removalListener = isLogEnable(localCache) ? new LocalCacheDefaultLogRemovalListener() : null;
                // 执行初始化
                if (isAutoAsyncRefresh(localCache)) {
                    if (isLogEnable(localCache)) {
                        log.info("本地缓存 {} 自动异步更新缓存池初始化开始", methodKey);
                    }
                    cache = LocalCacheUtil.newCacheBuilder(methodKey).basicAutoRefreshCacheConfig(localCache.initCapacity(), localCache.maxCapacity(), localCache.expireAfterWrite(), localCache.refreshAfterWrite()).setExecutor(executor).setRemovalListener(removalListener).setTimeUnit(localCache.timeUnit()).build((this::cacheLoader));
                    if (isLogEnable(localCache)) {
                        log.info("本地缓存: {} 自动异步更新缓存池初始化结束,cache对象是否为空:{}", methodKey, cache == null);
                    }
                } else {
                    if (isLogEnable(localCache)) {
                        log.info("本地缓存 {} 普通本地缓存池初始化开始", methodKey);
                    }
                    cache = LocalCacheUtil.newCacheBuilder(methodKey).basicCacheConfig(localCache.initCapacity(), localCache.maxCapacity(), localCache.expireAfterWrite()).setExecutor(executor).setRemovalListener(removalListener).setTimeUnit(localCache.timeUnit()).build();
                    if (isLogEnable(localCache)) {
                        log.info("本地缓存: {} 普通本地缓存池初始化结束,cache对象是否为空:{}", methodKey, cache == null);
                    }
                }
                cacheMap.put(methodKey, cache);
                // 注册到监控去
                LocalCacheMonitor.register(methodKey, cache, key.getLocalCache());
            } else {
                if (isLogEnable(localCache)) {
                    log.info("本地缓存:{} 已被其他线程初始化,本次初始化取消", methodKey);
                }
            }
        } catch (Exception e) {
            log.error("方法本地缓存初始化异常", e);
        } finally {
            reentrantLock.unlock();
        }
        return cache;
    }

    /**
     * 是否为自动刷新
     *
     * @param localCache
     * @return
     */
    public boolean isAutoAsyncRefresh(LocalCache localCache) {
        // refreshAfterWrite为正数则为需要动态异步更新缓存值
        return localCache != null && localCache.refreshAfterWrite() > 0;
    }

    /**
     * 生成方法的键
     *
     * @param method
     * @return
     */
    private String getMethodCacheKey(Method method) {
        StringBuilder keyBuilder = new StringBuilder("METHOD-CACHE-");
        // 填充 类名#方法名
        keyBuilder.append(method.getDeclaringClass().getName());
        keyBuilder.append("#");
        keyBuilder.append(method.getName());
        // 填充参数类
        keyBuilder.append("(");
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length > 0) {
            for (int i = 0; i < parameterTypes.length; i++) {
                if (i > 0) {
                    keyBuilder.append(",");
                }
                keyBuilder.append(parameterTypes[i].getName());
            }
        }
        keyBuilder.append(")");
        return keyBuilder.toString();
    }
}

key过期监听器


/**
 * @author: humorchen
 * date: 2023/11/22
 * description: 默认key移除日志监听器,仅当注解上设置了启用日志时会默认加入该监听器
 **/
@Slf4j
public class LocalCacheDefaultLogRemovalListener implements RemovalListener<Object, Object> {


    /**
     * Notifies the listener that a removal occurred at some point in the past.
     * <p>
     * This does not always signify that the key is now absent from the cache, as it may have already
     * been re-added.
     *
     * @param key   the key represented by this entry, or {@code null} if collected
     * @param value the value represented by this entry, or {@code null} if collected
     * @param cause the reason for which the entry was removed
     */
    @Override
    public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
        if (key instanceof LocalCacheKey) {
            LocalCacheKey k = (LocalCacheKey) key;
            log.info("本地缓存:{}  参数:{} 被移除,移除原因:{},当前方法值为:{}", k.getMethodKey(), JSONObject.toJSONString(k.getArgs()), cause, JSONObject.toJSONString(value));
        }
    }
}

配置类


/**
 * @author: humorchen
 * date: 2023/11/24
 * description: 本地缓存的所有配置
 **/
@Component
@Data
@ConfigurationProperties(prefix = "local.cache")
public class LocalCacheGlobalConfig {
    /**
     * 全局开启日志,优先级高于注解的
     */
    private boolean enableLog = true;
    /**
     * 监控缓存状态并打印
     */
    private boolean monitor = true;
    /**
     * 是否全局禁用,禁用后即使有缓存注解也会失效,手动编码加的缓存是无法通过该配置禁用的
     */
    private boolean disabled = false;

}

方法的包装key


/**
 * @author: humorchen
 * date: 2023/11/21
 * description: 本地缓存注解式使用时,对被代理方法参数的包装Key对象
 **/
@Data
@Slf4j
public class LocalCacheKey {
    /**
     * 方法全限定名
     */
    private String methodKey;
    /**
     * 被代理方法用到的注解
     */
    private LocalCache localCache;
    /**
     * 被代理的对象
     */
    private Object target;
    /**
     * 被代理方法
     */
    private Method method;
    /**
     * 执行的方法参数
     */
    private Object[] args;

    public LocalCacheKey() {

    }

    public LocalCacheKey(String methodKey, LocalCache localCache, Object target, Method method, Object[] args) {
        this.methodKey = methodKey;
        this.localCache = localCache;
        this.target = target;
        this.method = method;
        this.args = args;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (this == o) {
            return true;
        }
        if (!(o instanceof LocalCacheKey)) {
            return false;
        }
        LocalCacheKey annotationKey = (LocalCacheKey) o;
        // 不比对被代理对象,被代理的方法是同一个类的同一个方法,不关心是实例
        // 比对方法
        if (!Objects.equals(annotationKey.getMethodKey(), this.getMethodKey())) {
            return false;
        }
        // 比对参数
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                // 优先对象自己的equals,如果没有自己的equals导致不相同则用json序列化再判断一下是不是相同
                if (!(Objects.equals(args[i], annotationKey.args[i]) || Objects.equals(JSONObject.toJSONString(args[i]), JSONObject.toJSON(annotationKey.args[i])))) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        // 加入method
        StringBuilder stringBuilder = new StringBuilder(methodKey);
        // 融入参数hash打乱同method不同参数的hash
        if (args != null) {
            for (Object arg : args) {
                if (arg != null) {
                    stringBuilder.append(arg.hashCode());
                } else {
                    stringBuilder.append("null".hashCode());
                }
            }
        }
        return HashUtil.javaDefaultHash(stringBuilder.toString());
    }


}

监控打印


/**
 * @author: humorchen
 * date: 2023/11/23
 * description: 本地缓存监视器
 * 打印缓存的命中率、访问数、淘汰数等信息
 **/
@Component
@Slf4j
@Getter
public class LocalCacheMonitor {
    /**
     * 缓存对象map
     */
    private static final Map<String, Cache<Object, Object>> CACHE_MAP = new ConcurrentHashMap<>();
    /**
     * 保存一个注解缓存的包装key对象,方便打印相关信息
     */
    private static final Map<String, LocalCache> LOCAL_CACHE_MAP = new ConcurrentHashMap<>();
    /**
     * 纳秒到微秒的倍数
     */
    private static final BigDecimal NANOS_TO_MILLS = BigDecimal.valueOf(1000000);

    @Autowired
    private LocalCacheGlobalConfig config;
    /**
     * 把缓存注册到监控器监控
     *
     * @param cacheName
     * @param cache
     */

    public static void register(String cacheName, Cache cache, LocalCache localCache) {
        if (CACHE_MAP.containsKey(cacheName)) {
            throw new IllegalArgumentException("本地缓存-缓存名重复:" + cacheName);
        }
        CACHE_MAP.put(cacheName, cache);
        LOCAL_CACHE_MAP.put(cacheName, localCache);
    }

    /**
     * 监控打印是否启用
     *
     * @return
     */
    private boolean isMonitorEnabled() {
        return config.isMonitor();
    }

    /**
     * 纳秒到毫秒,保留三位小数
     *
     * @param nanos
     * @return
     */
    public static BigDecimal nanosToMills(double nanos) {
        return BigDecimal.valueOf(nanos).divide(NANOS_TO_MILLS, 3, RoundingMode.HALF_UP);
    }
    /**
     * 打印缓存情况
     *
     * @param cacheName
     * @param cache
     */
    private void printCacheStatus(String cacheName, Cache<Object, Object> cache) {
        try {
            CacheStats stats = cache.stats();
            double loadPenaltyInNanoS = stats.averageLoadPenalty();
            String loadPenaltyInMills = nanosToMills(loadPenaltyInNanoS).toString();
            BigDecimal hitRate = BigDecimal.valueOf(stats.hitRate() * 100).setScale(2, RoundingMode.HALF_UP);
            LocalCache localCache = LOCAL_CACHE_MAP.get(cacheName);
            log.info("【本地缓存状态】key:{} 总请求数:{} 次 ,缓存命中率:{}%,平均加载耗时:{} ms,淘汰key次数:{} 次,缓存命中次数:{} 次,缓存未命中次数:{}次 缓存配置:{}", cacheName, stats.requestCount(), hitRate, loadPenaltyInMills, stats.evictionCount(), stats.hitCount(), stats.missCount(), JSONObject.toJSONString(localCache));
        } catch (Exception e) {
            log.error("【本地缓存状态】key:" + cacheName + "打印缓存状态报错", e);
        }
    }

    /**
     * 打印一次整个本地缓存的情况
     */
    private void printMonitor() {
        log.info("-----------------------本地缓存状态打印-----------------------");
        if (config.isDisabled()) {
            log.info("本地缓存已被全局禁用");
        } else {
            CACHE_MAP.forEach((cacheName, cache) -> {
                printCacheStatus(cacheName, cache);
            });
        }

        log.info("-----------------------本地缓存状态打印结束-----------------------");
    }

    /**
     * 定时任务打印
     */
    @Scheduled(fixedDelay = 60000)
    public void printScheduled() {
        if (isMonitorEnabled()) {
            printMonitor();
        }
    }
}

本地缓存手动版工具类


/**
 * @author: humorchen
 * date: 2023/11/21
 * description: Caffeine本地缓存创建工具,建议使用该工具进行缓存对象构建
 * 使用该工具类,内部提供默认参数,可内存安全、线程安全的创建caffeine
 **/
@Setter
@Accessors(chain = true)
public class LocalCacheUtil<K, V> {
    /**
     * 线程池初始线程数 默认值,无任务执行不占用CPU
     */
    private static final int DEFAULT_THREAD = 4;
    /**
     * 线程池最大线程数 默认值
     */
    private final int DEFAULT_MAX_THREAD = 128;
    /**
     * 线程池额外线程数存活时间 默认值
     */
    private final int DEFAULT_THREAD_KEEP_ALIVE = 10;
    /**
     * 线程池额外线程数存活时间单位 默认值
     */
    private final TimeUnit DEFAULT_THREAD_KEEP_ALIVE_TIMEUNIT = TimeUnit.MINUTES;

    /**
     * 线程池拒绝策略 默认值
     */
    private final RejectedExecutionHandler DEFAULT_THREAD_EXECUTOR_ABORT_POLICY = new ThreadPoolExecutor.AbortPolicy();
    /**
     * 初始化缓存容量默认值
     */
    private final int DEFAULT_INIT_CAPACITY = 128;
    /**
     * 缓存最大容量默认值
     */
    private final int DEFAULT_MAX_CAPACITY = 4096;
    /**
     * 线程池等待队列的容量默认值 用的链表不用担心浪费内存
     */
    private final int DEFAULT_BLOCKING_QUEUE_SIZE = 4096;
    /**
     * 缓存写入后n秒过期 默认值
     */
    private final int DEFAULT_EXPIRE_AFTER_WRITE = 6;
    /**
     * 缓存写入3秒后一旦有访问,就异步更新这个缓存值 默认值
     */
    private final int DEFAULT_REFRESH_AFTER_WRITE = 3;
    /**
     * 缓存的时间单位
     */
    private final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
    /**
     * Caffeine缓存名称,用于做线程名,打印等
     */
    private String cacheName;
    /**
     * 线程计数
     */
    private AtomicInteger threadNum = new AtomicInteger(0);

    /**
     * 线程池初始线程数
     */
    private int thread = DEFAULT_THREAD;
    /**
     * 线程池最大线程数
     */
    private int maxThread = DEFAULT_MAX_THREAD;
    /**
     * 线程池额外线程数存活时间
     */
    private int threadKeepAlive = DEFAULT_THREAD_KEEP_ALIVE;
    /**
     * 线程池额外线程数存活时间单位
     */
    private TimeUnit threadKeepAliveTimeunit = DEFAULT_THREAD_KEEP_ALIVE_TIMEUNIT;
    /**
     * 线程池拒绝策略
     */
    private final RejectedExecutionHandler threadExecutorAbortPolicy = DEFAULT_THREAD_EXECUTOR_ABORT_POLICY;
    /**
     * 初始化缓存容量
     */
    private int initCapacity = DEFAULT_INIT_CAPACITY;
    /**
     * 缓存最大容量
     */
    private int maxCapacity = DEFAULT_MAX_CAPACITY;
    /**
     * 线程池等待队列的容量
     */

    private int blockingQueueSize = DEFAULT_BLOCKING_QUEUE_SIZE;
    /**
     * 缓存写入后n秒过期
     */
    private Integer expireAfterWrite;
    /**
     * 缓存访问后n秒过期
     */
    private Integer expireAfterAccess;
    /**
     * 缓存写入3秒后一旦有访问,就异步更新这个缓存值
     */
    private Integer refreshAfterWrite;
    /**
     * 缓存时间单位
     */
    private TimeUnit timeUnit = DEFAULT_TIME_UNIT;
    /**
     * 移除key的监听器
     */
    private RemovalListener<K, V> removalListener;
    /**
     * 调度器
     */
    private Scheduler scheduler;
    /**
     * 当前构建的caffeine对象
     */
    private Caffeine<K, V> sourceCaffeineBuilder;
    /**
     * 线程池
     */
    private Executor executor;
    /**
     * 是否启用日志打印,默认打开
     */
    private boolean enableLog = true;

    private LocalCacheUtil(String cacheName) {
        this.cacheName = cacheName;
    }

    /**
     * 新建一个caffeine缓存构建器
     *
     * @param cacheName 缓存名称(用于线程名,日志打印)
     * @return
     */
    public static LocalCacheUtil<Object, Object> newCacheBuilder(String cacheName) {
        LocalCacheUtil<Object, Object> builder = new LocalCacheUtil<>(cacheName);
        builder.sourceCaffeineBuilder = Caffeine.newBuilder();
        return builder;
    }

    /**
     * 获取原生caffeine builder修改非常用配置
     *
     * @return
     */
    public Caffeine<K, V> getRawCaffeineBuilder() {
        return this.sourceCaffeineBuilder;
    }

    /**
     * 简单缓存
     *
     * @param initCapacity
     * @param maxCapacity
     * @param expireSecond
     * @return
     */
    public LocalCacheUtil<K, V> basicCacheConfig(int initCapacity, int maxCapacity, int expireSecond) {
        setInitCapacity(initCapacity);
        setMaxCapacity(maxCapacity);
        setExpireAfterWrite(expireSecond);
        return this;
    }

    /**
     * 简单缓存
     *
     * @param initCapacity
     * @param maxCapacity
     * @param expireSecond
     * @param refreshAfterWrite 多久更新缓存数据,build的时候要传入加载方法
     * @return
     */
    public LocalCacheUtil<K, V> basicAutoRefreshCacheConfig(int initCapacity, int maxCapacity, int expireSecond, int refreshAfterWrite) {
        setInitCapacity(initCapacity);
        setMaxCapacity(maxCapacity);
        setExpireAfterWrite(expireSecond);
        setRefreshAfterWrite(refreshAfterWrite);
        return this;
    }

    /**
     * 简单数据的缓存,待自动刷新
     *
     * @param initCapacity
     * @param maxCapacity
     * @param expireSecond
     * @param refreshAfterWrite
     * @return
     */
    public LocalCacheUtil<K, V> basicAutoRefreshCacheConfigWithSingleExecutor(int initCapacity, int maxCapacity, int expireSecond, int refreshAfterWrite) {
        LocalCacheUtil<K, V> util = this.basicAutoRefreshCacheConfig(initCapacity, maxCapacity, expireSecond, refreshAfterWrite);
        util.setThread(1);
        util.setMaxThread(4);
        util.setBlockingQueueSize(16);
        return util;
    }

    /**
     * 构建不带加载器的cache
     * @return
     * @param <K1>
     * @param <V1>
     */
    public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
        return this.build(null);
    }
    /**
     * 构建最终的缓存对象
     *
     * @return Cache
     */
    public <K1 extends K, V1 extends V> Cache<K1, V1> build(CacheLoader<K1, V1> cacheLoader) {
        Caffeine<K, V> caffeine = this.sourceCaffeineBuilder.initialCapacity(initCapacity).maximumSize(maxCapacity);
        // 必须设置过期时间
        Assert.isFalse(expireAfterAccess == null && expireAfterWrite == null, () -> new IllegalArgumentException("expireAfterAccess和expireAfterWrite必须设置一个"));
        int expireSecond = 1;
        // 写入后多久过期
        if (expireAfterWrite != null) {
            caffeine.expireAfterWrite(expireAfterWrite, timeUnit);
            expireSecond = expireAfterWrite;
        }
        // 最后一次访问多久后过期
        if (expireAfterAccess != null) {
            caffeine.expireAfterAccess(expireAfterAccess, timeUnit);
            expireSecond = expireAfterAccess;
        }
        // 填充默认过期时间值并检查值
        if (cacheLoader != null && refreshAfterWrite == null) {
            refreshAfterWrite = Math.max(expireSecond / 2, 1);
            Assert.isTrue(refreshAfterWrite < expireSecond, () -> new IllegalArgumentException("使用默认方案时过期时间请大于1秒"));
            setRefreshAfterWrite(refreshAfterWrite);
        }
        // 自动刷新缓存最新值
        if (refreshAfterWrite != null) {
            Assert.notNull(cacheLoader, () -> new IllegalArgumentException("使用自动刷新必须提供加载器cacheLoader"));
            caffeine.refreshAfterWrite(refreshAfterWrite, timeUnit);
        }
        // 调度器
        if (scheduler != null) {
            caffeine.scheduler(scheduler);
        }
        // 移除缓存监听器
        if (removalListener != null) {
            caffeine.removalListener(removalListener);
        }
        // 线程池
        if (executor != null) {
            caffeine.executor(executor);
        } else {
            ThreadFactory threadFactory = (runnable) -> {
                String cacheName = "【本地缓存线程】" + this.cacheName + "-" + threadNum.incrementAndGet();
                return new Thread(runnable, cacheName);
            };
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(thread, maxThread, threadKeepAlive, threadKeepAliveTimeunit, new LinkedBlockingDeque<>(blockingQueueSize), threadFactory, threadExecutorAbortPolicy);
            caffeine.executor(threadPoolExecutor);
        }
        // 启用记录缓存命中等状态计数信息
        caffeine.recordStats();
        Cache cache = cacheLoader == null ? caffeine.build() : caffeine.build(cacheLoader);
        // 注册到监控
        LocalCacheMonitor.register(this.cacheName, cache, config2LocalCache());
        return cache;
    }


    /**
     * 当前配置转cache对象
     *
     * @return
     */
    private LocalCache config2LocalCache() {
        return new LocalCache() {

            /**
             * Returns the annotation type of this annotation.
             *
             * @return the annotation type of this annotation
             */
            @Override
            public Class<? extends Annotation> annotationType() {
                return LocalCache.class;
            }

            /**
             * @return
             */
            @Override
            public int initCapacity() {
                return initCapacity;
            }

            /**
             * @return
             */
            @Override
            public int maxCapacity() {
                return maxCapacity;
            }

            /**
             * @return
             */
            @Override
            public int expireAfterWrite() {
                return expireAfterWrite;
            }

            /**
             * @return
             */
            @Override
            public int refreshAfterWrite() {
                return refreshAfterWrite;
            }

            /**
             * @return
             */
            @Override
            public TimeUnit timeUnit() {
                return timeUnit;
            }

            /**
             * @return
             */
            @Override
            public boolean enableLog() {
                return enableLog;
            }
        };
    }
}

最后在spring.factories文件中将下面三个类配置到自动加载
在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.xxxx.LocalCacheAspect,\
  cn.xxxx.LocalCacheMonitor,\
  cn.xxxx.LocalCacheGlobalConfig
posted @ 2023-12-01 10:26  HumorChen99  阅读(32)  评论(0编辑  收藏  举报  来源