diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java new file mode 100644 index 0000000000000000000000000000000000000000..577115e7e99e3980c6288ea4f7cce53bae41be81 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/CacheStats.java @@ -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() + ); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java new file mode 100644 index 0000000000000000000000000000000000000000..2f11dc8057820ed4f8058bb5aef501ee7e43f431 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCache.java @@ -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标准缓存功能 + * + *

提供多级缓存、异步刷新、缓存预热等高级功能

+ * + * @author Nic + */ +public interface SmartCache extends Cache { + + /** + * 批量获取缓存项 + * + * @param keys 键集合 + * @return 键值对映射 + */ + Map getAll(Collection keys); + + /** + * 批量放入缓存项 + * + * @param map 键值对映射 + */ + void putAll(Map map); + + /** + * 异步刷新缓存项 + * + * @param key 缓存键 + * @return CompletableFuture包装的缓存值 + */ + CompletableFuture refreshAsync(K key); + + /** + * 缓存预热 + * + * @param keys 需要预热的键集合 + * @return 预热成功的数量 + */ + int warmUp(Collection keys); + + /** + * 原子操作:如果不存在则计算并放入 + * + * @param key 缓存键 + * @param mappingFunction 映射函数 + * @return 缓存值 + */ + V computeIfAbsent(K key, Function mappingFunction); + + /** + * 原子操作:如果存在则重新计算 + * + * @param key 缓存键 + * @param remappingFunction 重新映射函数 + * @return 新的缓存值 + */ + V computeIfPresent(K key, Function remappingFunction); + + /** + * 获取缓存统计信息 + * + * @return 缓存统计 + */ + CacheStats getStats(); + + /** + * 清除所有统计信息 + */ + void clearStats(); + + /** + * 获取缓存名称 + * + * @return 缓存名称 + */ + String getName(); + + /** + * 设置缓存名称 + * + * @param name 缓存名称 + */ + void setName(String name); +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..9313dbcdaec2707e564bae9ed0705c5bcef7b39e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheBuilder.java @@ -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 { + + // 必需参数 + private final Cache 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 cacheLoader; + + /** + * 私有构造器 + */ + private SmartCacheBuilder(Cache cache) { + this.cache = cache; + } + + /** + * 创建构建器 + */ + public static SmartCacheBuilder of(Cache cache) { + return new SmartCacheBuilder<>(cache); + } + + /** + * 设置缓存名称 + */ + public SmartCacheBuilder name(String name) { + this.name = StrUtil.defaultIfBlank(name, "SmartCache"); + return this; + } + + /** + * 启用统计 + */ + public SmartCacheBuilder enableStats(boolean enableStats) { + this.enableStats = enableStats; + return this; + } + + /** + * 启用异步刷新 + */ + public SmartCacheBuilder enableAsyncRefresh(boolean enableAsyncRefresh) { + this.enableAsyncRefresh = enableAsyncRefresh; + return this; + } + + /** + * 设置预热批次大小 + */ + public SmartCacheBuilder warmUpBatchSize(int warmUpBatchSize) { + this.warmUpBatchSize = Math.max(1, warmUpBatchSize); + return this; + } + + /** + * 设置刷新超时时间 + */ + public SmartCacheBuilder refreshTimeout(Duration refreshTimeout) { + this.refreshTimeout = refreshTimeout; + return this; + } + + /** + * 设置刷新线程池 + */ + public SmartCacheBuilder refreshExecutor(ExecutorService refreshExecutor) { + this.refreshExecutor = refreshExecutor; + return this; + } + + /** + * 设置缓存加载器 + */ + public SmartCacheBuilder cacheLoader(Function cacheLoader) { + this.cacheLoader = cacheLoader; + return this; + } + + /** + * 构建智能缓存 + */ + public SmartCache 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 + ); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..f4e439ff3e8c7aea70f6b06cf70709cbc7c310cf --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/SmartCacheUtil.java @@ -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 SmartCache newLRUSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newLRUCache(capacity)) + .name("LRU-SmartCache") + .build(); + } + + /** + * 创建LFU智能缓存 + */ + public static SmartCache newLFUSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newLFUCache(capacity)) + .name("LFU-SmartCache") + .build(); + } + + /** + * 创建FIFO智能缓存 + */ + public static SmartCache newFIFOSmartCache(int capacity) { + return (SmartCache) SmartCacheBuilder.of(CacheUtil.newFIFOCache(capacity)) + .name("FIFO-SmartCache") + .build(); + } + + /** + * 创建带加载器的智能缓存 + */ + public static SmartCache newSmartCache( + Cache cache, + java.util.function.Function loader) { + + return SmartCacheBuilder.of(cache) + .cacheLoader(loader) + .enableAsyncRefresh(true) + .enableStats(true) + .build(); + } + + /** + * 创建定时过期的智能缓存 + */ + public static SmartCache newTimedSmartCache( + int capacity, + long timeout, + java.util.function.Function loader) { + + Cache 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(); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..162de67573fa1dbffc6ac3d2030769b855348edf --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java @@ -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 implements SmartCache { + + // 底层缓存 + private final Cache 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 cacheLoader; + + // 统计信息 + private final CacheStats stats; + + // 锁机制 + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final Map> pendingRefreshes = new ConcurrentHashMap<>(); + + /** + * 构造器 + */ + public SmartCacheImpl( + Cache delegate, + String name, + boolean enableStats, + boolean enableAsyncRefresh, + int warmUpBatchSize, + Duration refreshTimeout, + ExecutorService refreshExecutor, + Function 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 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 getAll(Collection keys) { + if (CollUtil.isEmpty(keys)) { + return Collections.emptyMap(); + } + + lock.readLock().lock(); + try { + Map 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 map) { + if (MapUtil.isEmpty(map)) { + return; + } + + lock.writeLock().lock(); + try { + for (Map.Entry entry : map.entrySet()) { + delegate.put(entry.getKey(), entry.getValue()); + } + + if (enableStats) { + stats.setCacheSize(delegate.size()); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public CompletableFuture 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 pending = pendingRefreshes.get(key); + if (pending != null) { + return pending; + } + + CompletableFuture 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 keys) { + if (cacheLoader == null || CollUtil.isEmpty(keys)) { + return 0; + } + + int warmedUp = 0; + Collection> batches = new Partition<>(new ArrayList<>(keys), warmUpBatchSize); + + for (List 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 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 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 getDelegate() { + return delegate; + } + + @Override + public V get(K key, boolean isUpdateLastAccess, long timeout, SerSupplier 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> cacheObjIterator() { + CopiedIter> 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); + } + } +} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8ce7abf4320581023bbe5325e41abe7fde528547 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java @@ -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 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 data = new HashMap<>(); + data.put("batch1", "value1"); + data.put("batch2", "value2"); + data.put("batch3", "value3"); + + cache.putAll(data); + + // 批量获取 + Map 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 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); + } +}