本文目录导读:

- 使用 Guava Cache(最常用)
- 使用 Caffeine(性能更好,Guava的继承者)
- 使用 Redis(分布式场景)
- 手动实现简单缓存(带过期清除)
- 使用 Spring Cache(企业级应用)
- 缓存过期清除的最佳实践
- 选择建议
在Java缓存中设置过期清除,主要有以下几种常见方案,从简单到复杂:
使用 Guava Cache(最常用)
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.Cache;
import java.util.concurrent.TimeUnit;
public class GuavaCacheExample {
public static void main(String[] args) {
// 创建缓存,设置过期策略
Cache<String, Object> cache = CacheBuilder.newBuilder()
// 写入后固定时间过期
.expireAfterWrite(10, TimeUnit.MINUTES)
// 或:访问后固定时间过期(如果一直在访问,就不会过期)
// .expireAfterAccess(30, TimeUnit.MINUTES)
// 最大缓存数量
.maximumSize(1000)
.build();
// 放入缓存
cache.put("key1", "value1");
// 获取缓存
Object value = cache.getIfPresent("key1");
System.out.println(value);
}
}
使用 Caffeine(性能更好,Guava的继承者)
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CaffeineCacheExample {
public static void main(String[] args) {
Cache<String, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.expireAfterAccess(30, TimeUnit.MINUTES) // 访问后30分钟过期
.maximumSize(10000) // 最大条目数
.build();
cache.put("user_123", "user_data");
Object data = cache.getIfPresent("user_123");
System.out.println(data);
}
}
使用 Redis(分布式场景)
import redis.clients.jedis.Jedis;
public class RedisCacheExample {
private static final Jedis jedis = new Jedis("localhost", 6379);
// 设置带过期时间的缓存
public static void setWithExpire(String key, String value, int seconds) {
jedis.setex(key, seconds, value); // 设置值并指定过期时间(秒)
}
// 或使用更精确的过期时间
public static void setWithExpireAt(String key, String value, long unixTime) {
jedis.set(key, value);
jedis.expireAt(key, unixTime); // 指定具体过期时间戳
}
public static void main(String[] args) {
// 缓存用户信息,10分钟后过期
setWithExpire("user:123", "{\"name\":\"张三\"}", 600);
// 或:指定具体过期时间(今晚24点过期)
long midnight = System.currentTimeMillis() / 1000 + 86400;
setWithExpireAt("daily_data", "some_value", midnight);
}
}
手动实现简单缓存(带过期清除)
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class SimpleCache<K, V> {
private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
private static class CacheEntry<V> {
V value;
long expireTime;
CacheEntry(V value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
}
// 放入缓存,指定过期时间
public void put(K key, V value, long duration, TimeUnit unit) {
long expireTime = System.currentTimeMillis() + unit.toMillis(duration);
cache.put(key, new CacheEntry<>(value, expireTime));
}
// 获取缓存,自动检查是否过期
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) {
return null;
}
// 检查是否过期
if (System.currentTimeMillis() > entry.expireTime) {
cache.remove(key); // 清除过期缓存
return null;
}
return entry.value;
}
// 定期清理过期缓存(可选)
public void cleanExpired() {
long now = System.currentTimeMillis();
cache.entrySet().removeIf(entry -> now > entry.getValue().expireTime);
}
public static void main(String[] args) throws InterruptedException {
SimpleCache<String, String> cache = new SimpleCache<>();
// 设置缓存,5秒过期
cache.put("key1", "value1", 5, TimeUnit.SECONDS);
System.out.println(cache.get("key1")); // 输出: value1
Thread.sleep(6000); // 等待6秒
System.out.println(cache.get("key1")); // 输出: null(已过期清除)
}
}
使用 Spring Cache(企业级应用)
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 缓存结果,根据配置自动过期
@Cacheable(value = "users", key = "#userId")
public User getUserById(String userId) {
// 模拟数据库查询
return new User(userId, "张三");
}
// 更新缓存,同时设置过期时间(通过配置)
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
// 更新数据库
return user;
}
// 主动清除缓存
@CacheEvict(value = "users", key = "#userId")
public void deleteUser(String userId) {
// 删除用户
}
}
配置文件(application.yml):
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=10m
缓存过期清除的最佳实践
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.TimeUnit;
public class CacheBestPractice {
// 1. 组合过期策略
public static Cache<String, Object> createStrategicCache() {
return Caffeine.newBuilder()
.expireAfter(new Expiry<String, Object>() {
@Override
public long expireAfterCreate(String key, Object value, long currentTime) {
// 不同key设置不同过期时间
if (key.startsWith("session_")) {
return TimeUnit.MINUTES.toNanos(30); // session30分钟
} else if (key.startsWith("temp_")) {
return TimeUnit.SECONDS.toNanos(10); // 临时数据10秒
}
return TimeUnit.MINUTES.toNanos(10); // 默认10分钟
}
@Override
public long expireAfterUpdate(String key, Object value,
long currentTime, long currentDuration) {
return currentDuration; // 更新时不改变过期时间
}
@Override
public long expireAfterRead(String key, Object value,
long currentTime, long currentDuration) {
return currentDuration; // 读取时不改变
}
})
.maximumSize(10000)
.build();
}
// 2. 监听过期事件(清理资源)
public static void main(String[] args) {
Cache<String, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.removalListener((key, value, cause) -> {
System.out.println("缓存被清除: " + key +
", 原因: " + cause);
// 如果有需要释放的资源,在这里处理
if (value instanceof AutoCloseable) {
try {
((AutoCloseable) value).close();
} catch (Exception e) {
e.printStackTrace();
}
}
})
.build();
cache.put("temp_file", new TempFile("data.txt"));
// 1分钟后,缓存自动过期,触发移除监听器
}
static class TempFile implements AutoCloseable {
String filename;
TempFile(String filename) { this.filename = filename; }
@Override
public void close() { System.out.println("关闭文件: " + filename); }
}
}
选择建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 单机应用 | Caffeine | 性能最好,功能完善 |
| 分布式系统 | Redis | 统一缓存管理 |
| Spring项目 | Spring Cache + Caffeine | 注解简洁,易于维护 |
| 学习/教学 | Guava Cache | API经典,文档丰富 |
使用缓存过期时要注意:
- 合理设置过期时间,不要太长(占用内存)也不要太短(失去缓存意义)
- 考虑缓存穿透问题,使用布隆过滤器等方案
- 敏感数据要设置较短过期时间
- 监控缓存命中率,及时调整过期策略