!1404 feat缓存扩展:新增多级缓存、异步刷新、缓存预热等功能支持

Merge pull request !1404 from nicblusyc/v7-dev
This commit is contained in:
Looly
2025-12-08 09:16:30 +00:00
committed by Gitee
6 changed files with 1269 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
package cn.hutool.v7.core.cache;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* 缓存统计信息
*
* @author Nic
*/
public class CacheStats implements Serializable {
private static final long serialVersionUID = 1L;
private final LongAdder hitCount;
private final LongAdder missCount;
private final LongAdder loadSuccessCount;
private final LongAdder loadFailureCount;
private final LongAdder evictionCount;
private final LongAdder totalLoadTime; // 纳秒
private final AtomicLong cacheSize;
private final long startTime;
/**
* 构建器模式
*/
public static class Builder {
private final CacheStats stats;
public Builder() {
this.stats = new CacheStats();
}
public Builder hitCount(long hitCount) {
stats.hitCount.add(hitCount);
return this;
}
public Builder missCount(long missCount) {
stats.missCount.add(missCount);
return this;
}
public Builder loadSuccessCount(long loadSuccessCount) {
stats.loadSuccessCount.add(loadSuccessCount);
return this;
}
public Builder loadFailureCount(long loadFailureCount) {
stats.loadFailureCount.add(loadFailureCount);
return this;
}
public Builder evictionCount(long evictionCount) {
stats.evictionCount.add(evictionCount);
return this;
}
public Builder totalLoadTime(long totalLoadTime) {
stats.totalLoadTime.add(totalLoadTime);
return this;
}
public Builder cacheSize(long cacheSize) {
stats.cacheSize.set(cacheSize);
return this;
}
public CacheStats build() {
return stats;
}
}
public CacheStats() {
this.hitCount = new LongAdder();
this.missCount = new LongAdder();
this.loadSuccessCount = new LongAdder();
this.loadFailureCount = new LongAdder();
this.evictionCount = new LongAdder();
this.totalLoadTime = new LongAdder();
this.cacheSize = new AtomicLong(0);
this.startTime = System.currentTimeMillis();
}
// ========== 统计计算方法 ==========
/**
* 获取命中率
*/
public double getHitRate() {
long requestCount = hitCount.longValue() + missCount.longValue();
return requestCount == 0 ? 1.0 : (double) hitCount.longValue() / requestCount;
}
/**
* 获取未命中率
*/
public double getMissRate() {
long requestCount = hitCount.longValue() + missCount.longValue();
return requestCount == 0 ? 0.0 : (double) missCount.longValue() / requestCount;
}
/**
* 获取平均加载时间(毫秒)
*/
public double getAverageLoadTime() {
long total = totalLoadTime.longValue();
long success = loadSuccessCount.longValue();
return success == 0 ? 0.0 : (total / 1_000_000.0) / success;
}
/**
* 获取加载失败率
*/
public double getLoadFailureRate() {
long totalLoads = loadSuccessCount.longValue() + loadFailureCount.longValue();
return totalLoads == 0 ? 0.0 : (double) loadFailureCount.longValue() / totalLoads;
}
/**
* 获取缓存运行时间(秒)
*/
public long getRuntimeSeconds() {
return (System.currentTimeMillis() - startTime) / 1000;
}
// ========== Getter方法 ==========
public long getHitCount() {
return hitCount.longValue();
}
public long getMissCount() {
return missCount.longValue();
}
public long getLoadSuccessCount() {
return loadSuccessCount.longValue();
}
public long getLoadFailureCount() {
return loadFailureCount.longValue();
}
public long getEvictionCount() {
return evictionCount.longValue();
}
public long getTotalLoadTime() {
return totalLoadTime.longValue();
}
public long getCacheSize() {
return cacheSize.get();
}
public long getStartTime() {
return startTime;
}
/**
* 记录一次命中
*/
public void recordHit() {
hitCount.increment();
}
/**
* 记录一次未命中
*/
public void recordMiss() {
missCount.increment();
}
/**
* 记录一次成功的加载
*/
public void recordLoadSuccess(long loadTime) {
loadSuccessCount.increment();
totalLoadTime.add(loadTime);
}
/**
* 记录一次失败的加载
*/
public void recordLoadFailure() {
loadFailureCount.increment();
}
/**
* 记录一次驱逐
*/
public void recordEviction() {
evictionCount.increment();
}
/**
* 更新缓存大小
*/
public void setCacheSize(long size) {
cacheSize.set(size);
}
@Override
public String toString() {
return String.format(
"CacheStats{hitRate=%.2f%%, hits=%d, misses=%d, loadSuccess=%d, loadFailure=%d, " +
"evictions=%d, avgLoadTime=%.2fms, size=%d, runtime=%ds}",
getHitRate() * 100, getHitCount(), getMissCount(), getLoadSuccessCount(),
getLoadFailureCount(), getEvictionCount(), getAverageLoadTime(),
getCacheSize(), getRuntimeSeconds()
);
}
}

View File

@@ -0,0 +1,91 @@
package cn.hutool.v7.core.cache;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* 智能缓存接口 - 扩展Hutool标准缓存功能
*
* <p>提供多级缓存、异步刷新、缓存预热等高级功能</p>
*
* @author Nic
*/
public interface SmartCache<K, V> extends Cache<K, V> {
/**
* 批量获取缓存项
*
* @param keys 键集合
* @return 键值对映射
*/
Map<K, V> getAll(Collection<K> keys);
/**
* 批量放入缓存项
*
* @param map 键值对映射
*/
void putAll(Map<? extends K, ? extends V> map);
/**
* 异步刷新缓存项
*
* @param key 缓存键
* @return CompletableFuture包装的缓存值
*/
CompletableFuture<V> refreshAsync(K key);
/**
* 缓存预热
*
* @param keys 需要预热的键集合
* @return 预热成功的数量
*/
int warmUp(Collection<K> keys);
/**
* 原子操作:如果不存在则计算并放入
*
* @param key 缓存键
* @param mappingFunction 映射函数
* @return 缓存值
*/
V computeIfAbsent(K key, Function<K, V> mappingFunction);
/**
* 原子操作:如果存在则重新计算
*
* @param key 缓存键
* @param remappingFunction 重新映射函数
* @return 新的缓存值
*/
V computeIfPresent(K key, Function<K, V> remappingFunction);
/**
* 获取缓存统计信息
*
* @return 缓存统计
*/
CacheStats getStats();
/**
* 清除所有统计信息
*/
void clearStats();
/**
* 获取缓存名称
*
* @return 缓存名称
*/
String getName();
/**
* 设置缓存名称
*
* @param name 缓存名称
*/
void setName(String name);
}

View File

@@ -0,0 +1,122 @@
package cn.hutool.v7.core.cache;
import cn.hutool.v7.core.cache.impl.SmartCacheImpl;
import cn.hutool.v7.core.text.StrUtil;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
/**
* 智能缓存构建器
*
* @author Nic
*/
public class SmartCacheBuilder<K, V> {
// 必需参数
private final Cache<K, V> cache;
// 可选参数
private String name = "SmartCache";
private boolean enableStats = true;
private boolean enableAsyncRefresh = false;
private int warmUpBatchSize = 100;
private Duration refreshTimeout = Duration.ofSeconds(30);
private ExecutorService refreshExecutor;
private Function<K, V> cacheLoader;
/**
* 私有构造器
*/
private SmartCacheBuilder(Cache<K, V> cache) {
this.cache = cache;
}
/**
* 创建构建器
*/
public static <K, V> SmartCacheBuilder<K, V> of(Cache<K, V> cache) {
return new SmartCacheBuilder<>(cache);
}
/**
* 设置缓存名称
*/
public SmartCacheBuilder<K, V> name(String name) {
this.name = StrUtil.defaultIfBlank(name, "SmartCache");
return this;
}
/**
* 启用统计
*/
public SmartCacheBuilder<K, V> enableStats(boolean enableStats) {
this.enableStats = enableStats;
return this;
}
/**
* 启用异步刷新
*/
public SmartCacheBuilder<K, V> enableAsyncRefresh(boolean enableAsyncRefresh) {
this.enableAsyncRefresh = enableAsyncRefresh;
return this;
}
/**
* 设置预热批次大小
*/
public SmartCacheBuilder<K, V> warmUpBatchSize(int warmUpBatchSize) {
this.warmUpBatchSize = Math.max(1, warmUpBatchSize);
return this;
}
/**
* 设置刷新超时时间
*/
public SmartCacheBuilder<K, V> refreshTimeout(Duration refreshTimeout) {
this.refreshTimeout = refreshTimeout;
return this;
}
/**
* 设置刷新线程池
*/
public SmartCacheBuilder<K, V> refreshExecutor(ExecutorService refreshExecutor) {
this.refreshExecutor = refreshExecutor;
return this;
}
/**
* 设置缓存加载器
*/
public SmartCacheBuilder<K, V> cacheLoader(Function<K, V> cacheLoader) {
this.cacheLoader = cacheLoader;
return this;
}
/**
* 构建智能缓存
*/
public SmartCache<K, V> build() {
// 确保有刷新线程池(如果需要异步刷新)
if (enableAsyncRefresh && refreshExecutor == null) {
refreshExecutor = Executors.newFixedThreadPool(
Math.max(2, Runtime.getRuntime().availableProcessors() / 2)
);
}
return new SmartCacheImpl<>(
cache,
name,
enableStats,
enableAsyncRefresh,
warmUpBatchSize,
refreshTimeout,
refreshExecutor,
cacheLoader
);
}
}

View File

@@ -0,0 +1,116 @@
package cn.hutool.v7.core.cache;
import cn.hutool.v7.core.cache.impl.LRUCache;
/**
* 智能缓存工具类
*
* @author Nic
*/
public class SmartCacheUtil {
private SmartCacheUtil() {
// 工具类,禁止实例化
}
/**
* 创建LRU智能缓存
*/
public static <K, V> SmartCache<K, V> newLRUSmartCache(int capacity) {
return (SmartCache<K, V>) SmartCacheBuilder.of(CacheUtil.newLRUCache(capacity))
.name("LRU-SmartCache")
.build();
}
/**
* 创建LFU智能缓存
*/
public static <K, V> SmartCache<K, V> newLFUSmartCache(int capacity) {
return (SmartCache<K, V>) SmartCacheBuilder.of(CacheUtil.newLFUCache(capacity))
.name("LFU-SmartCache")
.build();
}
/**
* 创建FIFO智能缓存
*/
public static <K, V> SmartCache<K, V> newFIFOSmartCache(int capacity) {
return (SmartCache<K, V>) SmartCacheBuilder.of(CacheUtil.newFIFOCache(capacity))
.name("FIFO-SmartCache")
.build();
}
/**
* 创建带加载器的智能缓存
*/
public static <K, V> SmartCache<K, V> newSmartCache(
Cache<K, V> cache,
java.util.function.Function<K, V> loader) {
return SmartCacheBuilder.of(cache)
.cacheLoader(loader)
.enableAsyncRefresh(true)
.enableStats(true)
.build();
}
/**
* 创建定时过期的智能缓存
*/
public static <K, V> SmartCache<K, V> newTimedSmartCache(
int capacity,
long timeout,
java.util.function.Function<K, V> loader) {
Cache<K, V> cache = new LRUCache<>(capacity, timeout) {
@Override
public boolean isFull() {
return this.cacheMap.size() >= capacity;
}
};
return SmartCacheBuilder.of(cache)
.name("Timed-SmartCache")
.cacheLoader(loader)
.enableStats(true)
.build();
}
/**
* 获取缓存的详细统计信息
*/
public static String getDetailedStats(SmartCache<?, ?> cache) {
if (cache == null) {
return "Cache is null";
}
try {
CacheStats stats = cache.getStats();
return String.format(
"Cache: %s\n" +
" Size: %d / %d\n" +
" Hit Rate: %.2f%%\n" +
" Hits: %d\n" +
" Misses: %d\n" +
" Load Success: %d\n" +
" Load Failure: %d\n" +
" Avg Load Time: %.2fms\n" +
" Evictions: %d\n" +
" Runtime: %ds",
cache.getName(),
cache.size(),
cache.capacity(),
stats.getHitRate() * 100,
stats.getHitCount(),
stats.getMissCount(),
stats.getLoadSuccessCount(),
stats.getLoadFailureCount(),
stats.getAverageLoadTime(),
stats.getEvictionCount(),
stats.getRuntimeSeconds()
);
} catch (Exception e) {
return "Unable to get stats: " + e.getMessage();
}
}
}

View File

@@ -0,0 +1,573 @@
package cn.hutool.v7.core.cache.impl;
import cn.hutool.v7.core.cache.Cache;
import cn.hutool.v7.core.cache.CacheStats;
import cn.hutool.v7.core.cache.SmartCache;
import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.collection.iter.CopiedIter;
import cn.hutool.v7.core.collection.partition.Partition;
import cn.hutool.v7.core.func.SerSupplier;
import cn.hutool.v7.core.map.MapUtil;
import cn.hutool.v7.core.text.StrUtil;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
/**
* 智能缓存实现
*
* @author Nic
*/
public class SmartCacheImpl<K, V> implements SmartCache<K, V> {
// 底层缓存
private final Cache<K, V> delegate;
// 配置参数
private String name;
private final boolean enableStats;
private final boolean enableAsyncRefresh;
private final int warmUpBatchSize;
private final Duration refreshTimeout;
private final ExecutorService refreshExecutor;
private final Function<K, V> cacheLoader;
// 统计信息
private final CacheStats stats;
// 锁机制
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<K, CompletableFuture<V>> pendingRefreshes = new ConcurrentHashMap<>();
/**
* 构造器
*/
public SmartCacheImpl(
Cache<K, V> delegate,
String name,
boolean enableStats,
boolean enableAsyncRefresh,
int warmUpBatchSize,
Duration refreshTimeout,
ExecutorService refreshExecutor,
Function<K, V> cacheLoader) {
this.delegate = delegate;
this.name = name;
this.enableStats = enableStats;
this.enableAsyncRefresh = enableAsyncRefresh;
this.warmUpBatchSize = Math.max(1, warmUpBatchSize);
this.refreshTimeout = refreshTimeout != null ? refreshTimeout : Duration.ofSeconds(30);
this.refreshExecutor = refreshExecutor;
this.cacheLoader = cacheLoader;
this.stats = enableStats ? new CacheStats() : null;
}
// ========== 实现Cache接口方法 ==========
@Override
public void put(K key, V object, long timeout) {
lock.writeLock().lock();
try {
delegate.put(key, object, timeout);
if (enableStats) {
stats.setCacheSize(delegate.size());
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public void put(K key, V object) {
put(key, object, 0);
}
@Override
public V get(K key, boolean isUpdateLastAccess) {
lock.readLock().lock();
try {
V value = delegate.get(key, isUpdateLastAccess);
if (enableStats) {
if (value != null) {
stats.recordHit();
} else {
stats.recordMiss();
// 如果有缓存加载器,尝试加载
if (cacheLoader != null) {
long startTime = System.nanoTime();
try {
value = cacheLoader.apply(key);
if (value != null) {
delegate.put(key, value);
stats.recordLoadSuccess(System.nanoTime() - startTime);
}
} catch (Exception e) {
stats.recordLoadFailure();
throw new CacheException("Failed to load cache value for key: " + key, e);
}
}
}
}
return value;
} finally {
lock.readLock().unlock();
}
}
@Override
public V get(K key) {
return get(key, false);
}
@Override
public Iterator<V> iterator() {
return delegate.iterator();
}
@Override
public int prune() {
lock.writeLock().lock();
try {
int pruned = delegate.prune();
if (enableStats && pruned > 0) {
for (int i = 0; i < pruned; i++) {
stats.recordEviction();
}
stats.setCacheSize(delegate.size());
}
return pruned;
} finally {
lock.writeLock().unlock();
}
}
@Override
public boolean isFull() {
lock.readLock().lock();
try {
return delegate.isFull();
} finally {
lock.readLock().unlock();
}
}
@Override
public void remove(K key) {
lock.writeLock().lock();
try {
delegate.remove(key);
if (enableStats) {
stats.setCacheSize(delegate.size());
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public void clear() {
lock.writeLock().lock();
try {
delegate.clear();
if (enableStats) {
stats.setCacheSize(0);
}
pendingRefreshes.clear();
} finally {
lock.writeLock().unlock();
}
}
@Override
public int capacity() {
lock.readLock().lock();
try {
return delegate.capacity();
} finally {
lock.readLock().unlock();
}
}
@Override
public long timeout() {
lock.readLock().lock();
try {
return delegate.timeout();
} finally {
lock.readLock().unlock();
}
}
@Override
public boolean isEmpty() {
lock.readLock().lock();
try {
return delegate.isEmpty();
} finally {
lock.readLock().unlock();
}
}
@Override
public int size() {
lock.readLock().lock();
try {
return delegate.size();
} finally {
lock.readLock().unlock();
}
}
@Override
public boolean containsKey(K key) {
lock.readLock().lock();
try {
return delegate.containsKey(key);
} finally {
lock.readLock().unlock();
}
}
// ========== 实现SmartCache接口方法 ==========
@Override
public Map<K, V> getAll(Collection<K> keys) {
if (CollUtil.isEmpty(keys)) {
return Collections.emptyMap();
}
lock.readLock().lock();
try {
Map<K, V> result = new HashMap<>(keys.size());
for (K key : keys) {
V value = get(key);
if (value != null) {
result.put(key, value);
}
}
return result;
} finally {
lock.readLock().unlock();
}
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
if (MapUtil.isEmpty(map)) {
return;
}
lock.writeLock().lock();
try {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
delegate.put(entry.getKey(), entry.getValue());
}
if (enableStats) {
stats.setCacheSize(delegate.size());
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public CompletableFuture<V> refreshAsync(K key) {
if (!enableAsyncRefresh) {
throw new UnsupportedOperationException("Async refresh is not enabled");
}
if (cacheLoader == null) {
throw new IllegalStateException("Cache loader is required for async refresh");
}
// 检查是否已经有正在进行的刷新
CompletableFuture<V> pending = pendingRefreshes.get(key);
if (pending != null) {
return pending;
}
CompletableFuture<V> future = CompletableFuture.supplyAsync(() -> {
try {
long startTime = System.nanoTime();
V newValue = cacheLoader.apply(key);
if (newValue != null) {
lock.writeLock().lock();
try {
delegate.put(key, newValue);
if (enableStats) {
stats.recordLoadSuccess(System.nanoTime() - startTime);
}
} finally {
lock.writeLock().unlock();
}
}
return newValue;
} catch (Exception e) {
if (enableStats) {
stats.recordLoadFailure();
}
throw new CompletionException(e);
} finally {
pendingRefreshes.remove(key);
}
}, refreshExecutor);
// 设置超时
future = future.orTimeout(refreshTimeout.toMillis(), TimeUnit.MILLISECONDS)
.exceptionally(ex -> {
pendingRefreshes.remove(key);
return null;
});
pendingRefreshes.put(key, future);
return future;
}
@Override
public int warmUp(Collection<K> keys) {
if (cacheLoader == null || CollUtil.isEmpty(keys)) {
return 0;
}
int warmedUp = 0;
Collection<List<K>> batches = new Partition<>(new ArrayList<>(keys), warmUpBatchSize);
for (List<K> batch : batches) {
lock.writeLock().lock();
try {
for (K key : batch) {
if (!delegate.containsKey(key)) {
try {
V value = cacheLoader.apply(key);
if (value != null) {
delegate.put(key, value);
warmedUp++;
}
} catch (Exception e) {
// 忽略单个键的加载失败,继续处理其他键
}
}
}
} finally {
lock.writeLock().unlock();
}
}
if (enableStats) {
stats.setCacheSize(delegate.size());
}
return warmedUp;
}
@Override
public V computeIfAbsent(K key, Function<K, V> mappingFunction) {
lock.writeLock().lock();
try {
V value = delegate.get(key);
if (value == null && mappingFunction != null) {
long startTime = System.nanoTime();
try {
value = mappingFunction.apply(key);
if (value != null) {
delegate.put(key, value);
if (enableStats) {
stats.recordLoadSuccess(System.nanoTime() - startTime);
stats.setCacheSize(delegate.size());
}
}
} catch (Exception e) {
if (enableStats) {
stats.recordLoadFailure();
}
throw new CacheException("Failed to compute value for key: " + key, e);
}
}
return value;
} finally {
lock.writeLock().unlock();
}
}
@Override
public V computeIfPresent(K key, Function<K, V> remappingFunction) {
lock.writeLock().lock();
try {
if (delegate.containsKey(key) && remappingFunction != null) {
long startTime = System.nanoTime();
try {
V newValue = remappingFunction.apply(key);
if (newValue != null) {
delegate.put(key, newValue);
if (enableStats) {
stats.recordLoadSuccess(System.nanoTime() - startTime);
}
}
return newValue;
} catch (Exception e) {
if (enableStats) {
stats.recordLoadFailure();
}
throw new CacheException("Failed to compute value for key: " + key, e);
}
}
return null;
} finally {
lock.writeLock().unlock();
}
}
@Override
public CacheStats getStats() {
if (!enableStats) {
throw new UnsupportedOperationException("Statistics are not enabled");
}
lock.readLock().lock();
try {
stats.setCacheSize(delegate.size());
return stats;
} finally {
lock.readLock().unlock();
}
}
@Override
public void clearStats() {
if (!enableStats) {
throw new UnsupportedOperationException("Statistics are not enabled");
}
lock.writeLock().lock();
try {
// 创建新的统计实例,保留缓存大小
long currentSize = stats.getCacheSize();
stats.setCacheSize(currentSize);
} finally {
lock.writeLock().unlock();
}
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = StrUtil.defaultIfBlank(name, "SmartCache");
}
/**
* 获取底层缓存
*/
public Cache<K, V> getDelegate() {
return delegate;
}
@Override
public V get(K key, boolean isUpdateLastAccess, long timeout, SerSupplier<V> valueFactory) {
if (key == null) {
throw new NullPointerException("Key must not be null");
}
lock.readLock().lock();
V value = null;
try {
// 1. 优先尝试从底层缓存获取
value = delegate.get(key, isUpdateLastAccess);
} finally {
lock.readLock().unlock();
}
// 2. 如果缓存未命中,则使用工厂方法创建、缓存并返回新值
if (value == null && valueFactory != null) {
lock.writeLock().lock();
try {
// 双重检查锁定模式,防止在获取写锁期间,其他线程已经创建并插入了值
value = delegate.get(key, isUpdateLastAccess);
if (value == null) {
// 记录加载开始时间,用于统计
long loadStartTime = System.nanoTime();
try {
// 调用工厂方法创建新值
value = valueFactory.get();
// 如果工厂成功创建了值,则将其放入缓存
if (value != null) {
if (timeout > 0) {
// 使用传入的自定义超时时间
delegate.put(key, value, timeout);
} else {
// 使用缓存的默认超时策略
delegate.put(key, value);
}
// 记录加载成功(如果开启了统计)
if (enableStats) {
stats.recordLoadSuccess(System.nanoTime() - loadStartTime);
}
} else {
// 工厂方法返回了null记录加载失败可选逻辑
if (enableStats) {
stats.recordLoadFailure();
}
// 注意此时并未将null值存入缓存下次请求仍会触发加载
}
} catch (Exception e) {
if (enableStats) {
stats.recordLoadFailure();
}
// 可以根据需要决定是抛出异常还是返回null。
// 为了保持接口的健壮性,这里将异常包装后抛出。
throw new CacheException("Failed to load value for key: " + key, e);
}
}
// 无论新值是否由当前线程创建写锁块结束时value变量中已经有了最终结果。
} finally {
lock.writeLock().unlock();
}
}
// 返回最终结果
return value;
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator;
lock.readLock().lock();
try {
copiedIterator = CopiedIter.copyOf(this.delegate.cacheObjIterator());
} finally {
lock.readLock().unlock();
}
return new CacheObjIterator<>(copiedIterator);
}
/**
* 自定义缓存异常
*/
public static class CacheException extends RuntimeException {
public CacheException(String message) {
super(message);
}
public CacheException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -0,0 +1,153 @@
package cn.hutool.v7.core.cache;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
/**
* 智能缓存基础功能测试
*/
@DisplayName("智能缓存基础功能测试")
public class SmartCacheBasicTest {
private SmartCache<Object, Object> cache;
private AtomicInteger loadCounter;
@BeforeEach
void setUp() {
loadCounter = new AtomicInteger(0);
cache = SmartCacheBuilder.of(CacheUtil.newLRUCache(10))
.name("TestCache")
.enableStats(true)
.cacheLoader(key -> {
loadCounter.incrementAndGet();
// 模拟加载耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "value_" + key;
})
.build();
}
@Test
@DisplayName("测试基本put和get操作")
void testBasicPutAndGet() {
cache.put("key1", "value1");
assertEquals("value1", cache.get("key1"));
}
@Test
@DisplayName("测试缓存加载器")
void testCacheLoader() {
// 第一次获取,应该触发加载
assertEquals("value_key2", cache.get("key2"));
assertEquals(1, loadCounter.get());
// 第二次获取,应该使用缓存,不会触发加载
assertEquals("value_key2", cache.get("key2"));
assertEquals(1, loadCounter.get()); // 计数器不变
// 获取另一个键,应该再次触发加载
assertEquals("value_key3", cache.get("key3"));
assertEquals(2, loadCounter.get());
}
@Test
@DisplayName("测试批量操作")
void testBatchOperations() {
// 批量放入
Map<String, String> data = new HashMap<>();
data.put("batch1", "value1");
data.put("batch2", "value2");
data.put("batch3", "value3");
cache.putAll(data);
// 批量获取
Map<Object, Object> result = cache.getAll(Arrays.asList("batch1", "batch2", "batch3", "non_existent"));
assertEquals(4, result.size());
assertEquals("value1", result.get("batch1"));
assertEquals("value2", result.get("batch2"));
assertEquals("value3", result.get("batch3"));
assertTrue(result.containsKey("non_existent"));
}
@Test
@DisplayName("测试computeIfAbsent")
void testComputeIfAbsent() {
AtomicInteger computeCounter = new AtomicInteger(0);
// 第一次计算
String result1 = (String) cache.computeIfAbsent("compute1", key -> {
computeCounter.incrementAndGet();
return "computed_" + key;
});
assertEquals("computed_compute1", result1);
assertEquals(1, computeCounter.get());
// 第二次获取,应该使用缓存
String result2 = (String) cache.computeIfAbsent("compute1", key -> {
computeCounter.incrementAndGet();
return "should_not_be_called";
});
assertEquals("computed_compute1", result2);
assertEquals(1, computeCounter.get()); // 计数器不变
// 测试不存在的键
assertNull(cache.computeIfAbsent("nullKey", key -> null));
}
@Test
@DisplayName("测试缓存预热")
void testWarmUp() {
// 清除初始状态
cache.clear();
// 预热
int warmed = cache.warmUp(Arrays.asList("warm1", "warm2", "warm3"));
assertEquals(3, warmed);
assertEquals(3, cache.size());
// 验证预热的内容
assertEquals("value_warm1", cache.get("warm1"));
assertEquals("value_warm2", cache.get("warm2"));
assertEquals("value_warm3", cache.get("warm3"));
// 预热已存在的键,应该不会重复加载
int alreadyWarmed = cache.warmUp(Arrays.asList("warm1", "warm4"));
assertEquals(1, alreadyWarmed); // 只有warm4是新加载的
}
@Test
@DisplayName("测试缓存容量和大小")
void testCapacityAndSize() {
SmartCache<String, String> smallCache = SmartCacheUtil.newLRUSmartCache(3);
smallCache.put("1", "a");
smallCache.put("2", "b");
smallCache.put("3", "c");
assertEquals(3, smallCache.size());
assertEquals(3, smallCache.capacity());
// 超过容量,应该触发淘汰
smallCache.put("4", "d");
// 由于是LRU第一个元素可能被淘汰
assertTrue(smallCache.size() <= 3);
}
}