本文目录导读:

在Java项目中封装一个缓存工具类,通常是为了屏蔽底层缓存实现(如本地内存、Redis等)的复杂性,提供统一、易用的API,下面我将从设计思路、核心代码实现(基于本地内存 + 简单泛型)以及最佳实践三个维度为你详细讲解。
设计思路与选型
-
确定缓存类型:
- 本地缓存:使用
HashMap+ConcurrentHashMap、Caffeine、Guava Cache,速度快,但无法跨进程共享。 - 分布式缓存:使用
Redis(Jedis/Redisson/Spring Data Redis)、Memcached。 - 多级缓存:本地缓存 + 分布式缓存组合(如 Caffeine + Redis)。
- 本地缓存:使用
-
核心功能:
put(key, value):存入数据。get(key):获取数据。remove(key):删除数据。clear():清空缓存。- 可选:
put(key, value, expireTime)支持过期时间。
-
设计模式:
- 策略模式:可切换不同的缓存实现(如本地 / Redis)。
- 单例模式:工具类通常为无状态或全局唯一。
- 泛型:支持任意类型的Key和Value。
代码实现示例(基于本地内存 + 支持过期)
这里使用 ConcurrentHashMap + 泛型 + 简单的过期清理机制,为你演示如何封装,这是一个生产可用的轻量级实现。
缓存数据实体(内部类)
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CacheUtil<K, V> {
// 缓存容器:存储键值对及其过期时间
private final ConcurrentMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
// 定时清理过期缓存的线程池
private final ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor();
/**
* 缓存条目,包含值和过期时间戳
*/
private static class CacheEntry<V> {
V value;
long expireTime; // 过期时间戳(毫秒),0表示永不过期
CacheEntry(V value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
boolean isExpired() {
return expireTime > 0 && System.currentTimeMillis() > expireTime;
}
}
public CacheUtil() {
// 启动定时清理任务:每5秒清理一次过期缓存
cleaner.scheduleAtFixedRate(this::evictExpiredEntries, 5, 5, TimeUnit.SECONDS);
}
// ---------- 核心 API ----------
/**
* 存入缓存(永不过期)
*/
public void put(K key, V value) {
cache.put(key, new CacheEntry<>(value, 0));
}
/**
* 存入缓存(指定过期时间)
* @param key 键
* @param value 值
* @param expireAfterSeconds 过期秒数(>0)
*/
public void put(K key, V value, long expireAfterSeconds) {
if (expireAfterSeconds <= 0) {
put(key, value);
return;
}
long expireTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expireAfterSeconds);
cache.put(key, new CacheEntry<>(value, expireTime));
}
/**
* 获取缓存(如果过期则返回null并移除)
*/
@SuppressWarnings("unchecked")
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) {
return null;
}
if (entry.isExpired()) {
cache.remove(key);
return null;
}
return entry.value;
}
/**
* 删除缓存
*/
public V remove(K key) {
CacheEntry<V> removed = cache.remove(key);
return removed != null ? removed.value : null;
}
/**
* 清空全部缓存
*/
public void clear() {
cache.clear();
}
/**
* 当前缓存数量(包括已过期但未清理的)
*/
public int size() {
return cache.size();
}
// ---------- 内部清理方法 ----------
/**
* 清理所有过期条目
*/
private void evictExpiredEntries() {
cache.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
// 可考虑优雅关闭清理线程(在生产环境中需调用)
public void shutdown() {
cleaner.shutdown();
}
}
使用示例
public class Example {
public static void main(String[] args) throws InterruptedException {
// 创建一个 String → String 的缓存工具
CacheUtil<String, String> cache = new CacheUtil<>();
// 存入数据(永久有效)
cache.put("name", "Java");
System.out.println(cache.get("name")); // 输出: Java
// 存入数据(5秒后过期)
cache.put("token", "abc123", 5);
System.out.println(cache.get("token")); // 输出: abc123
// 等待6秒
Thread.sleep(6000);
System.out.println(cache.get("token")); // 输出: null(已过期)
// 删除
cache.remove("name");
System.out.println(cache.get("name")); // 输出: null
// 清空
cache.clear();
}
}
生产环境建议(进阶封装)
如果你需要在真正的生产环境中使用,更推荐以下两种方式:
基于 Caffeine(性能远高于 HashMap)
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CaffeineCacheUtil<K, V> {
private final Cache<K, V> cache;
public CaffeineCacheUtil(int maxSize, int expireAfterWriteSeconds) {
this.cache = Caffeine.newBuilder()
.maximumSize(maxSize) // 最大条目数
.expireAfterWrite(expireAfterWriteSeconds, TimeUnit.SECONDS) // 写入后过期
.recordStats() // 开启统计(可选)
.build();
}
public void put(K key, V value) {
cache.put(key, value);
}
public V get(K key) {
return cache.getIfPresent(key);
}
// 带加载函数的get(推荐:如果key不存在,自动加载并缓存)
public V get(K key, java.util.function.Function<? super K, ? extends V> loader) {
return cache.get(key, loader);
}
public void remove(K key) {
cache.invalidate(key);
}
public void clear() {
cache.invalidateAll();
}
}
基于 Spring Cache 注解(声明式缓存)
如果你使用 Spring Boot,可以直接使用 @Cacheable、@CachePut 等注解,无需自己封装工具类:
@Service
public class UserService {
@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User getUserById(String userId) {
// 模拟数据库查询
return userRepository.findById(userId).orElse(null);
}
}
通过配置 spring.cache.type=redis 或 caffeine,即可切换底层实现。
关键设计要点总结
| 设计点 | 说明 |
|---|---|
| 线程安全 | 使用 ConcurrentHashMap 或 Caffeine,避免并发问题 |
| 过期机制 | 惰性删除(get时检查) + 定时清理(后台线程) |
| 泛型 | 支持 <K, V> 任意类型,提高复用性 |
| 单一职责 | 工具类只负责缓存操作,不耦合业务逻辑 |
| 可切换性 | 建议封装接口(如 ICache),不同实现可灵活替换 |
最终建议
- 小型项目 / 本地缓存:直接用上面的 Caffeine 封装(推荐)或基于
ConcurrentHashMap的自实现。 - 分布式项目:封装 Redis 工具类 或直接使用 Spring Data Redis。
- 最简洁方案:如果你使用 Spring Boot,不要再自己封装工具类,直接使用
@Cacheable+Caffeine或Redis配置即可,这是最标准、最省力的方式。
如果你有特定场景(如需要本地+Redis多级缓存),可以在评论区告诉我,我可以为你提供对应的封装示例。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播