本文目录导读:

在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多级缓存),可以在评论区告诉我,我可以为你提供对应的封装示例。