注解式+手动式自动异步更新的本地缓存(基于本地缓存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
本文来自博客园,作者:HumorChen99,转载请注明原文链接:https://www.cnblogs.com/HumorChen/p/18039440
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
2020-12-01 linux docker安装脚本