LoadingCache 接口详解
快速原型开发:避免复杂的异常处理代码统一异常处理:配合全局异常处理器使用业务逻辑简化:当缓存加载异常应该导致程序终止时函数式编程:在 lambda 表达式中使用更方便选择使用 [get](file://D:\projects\Evo-Oauth\src\main\java\com\dahua\evo\oauth\cache\UserTokenCache.java#L49-L63) 还是取决于您的异
·
LoadingCache 接口详解
LoadingCache 是 Google Guava 库提供的一个接口,它扩展了 Cache 和 Function 接口,提供了自动加载缓存值的功能。
主要特性
- 自动加载: 当缓存中不存在某个键时,会自动调用
CacheLoader加载数据 - 线程安全: 支持并发访问,保证在多线程环境下的数据一致性
- 异常处理: 提供了完善的异常处理机制
- 丰富的API: 提供多种获取和操作缓存的方法
核心方法
1. 获取缓存值的方法
// 基本获取方法,如果缓存中没有会自动加载
V get(K key) throws ExecutionException;
// 获取缓存值,返回Optional包装,避免异常
Optional<V> getIfPresent(K key);
// 批量获取多个键的值
ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException;
2. 缓存管理方法
// 显式地向缓存中放入值
void put(K key, V value);
// 使缓存中的某个键失效
void invalidate(K key);
// 批量使缓存键失效
void invalidateAll(Iterable<?> keys);
// 清空所有缓存
void invalidateAll();
// 刷新指定键的缓存值
void refresh(K key);
经典使用场景
1. 数据库查询缓存
// 创建用户信息缓存
LoadingCache<Long, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<Long, User>() {
@Override
public User load(Long userId) throws Exception {
// 从数据库查询用户信息
return userDao.findById(userId);
}
});
// 使用缓存获取用户信息
try {
User user = userCache.get(123L); // 自动处理缓存未命中情况
} catch (ExecutionException e) {
// 处理加载异常
}
2. Web服务调用缓存
// 缓存外部API调用结果
LoadingCache<String, List<Product>> productCache = CacheBuilder.newBuilder()
.maximumSize(500)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build(new CacheLoader<String, List<Product>>() {
@Override
public List<Product> load(String category) throws Exception {
// 调用外部API获取产品列表
return externalApiService.getProductsByCategory(category);
}
});
// 获取产品列表
try {
List<Product> products = productCache.get("electronics");
} catch (ExecutionException e) {
// 处理API调用异常
}
3. 计算结果缓存
// 缓存复杂计算结果
LoadingCache<String, BigDecimal> calculationCache = CacheBuilder.newBuilder()
.maximumSize(200)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(new CacheLoader<String, BigDecimal>() {
@Override
public BigDecimal load(String calculationKey) throws Exception {
// 执行复杂计算
return performComplexCalculation(calculationKey);
}
});
// 获取计算结果
try {
BigDecimal result = calculationCache.get("formula_123");
} catch (ExecutionException e) {
// 处理计算异常
}
使用示例
基本用法
// 创建缓存
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
// 模拟数据加载过程
return "value_for_" + key;
}
});
// 使用缓存
try {
String value = cache.get("key1"); // 第一次调用会执行load方法
String value2 = cache.get("key1"); // 第二次调用直接从缓存获取
} catch (ExecutionException e) {
e.printStackTrace();
}
批量操作
// 批量获取多个键的值
List<String> keys = Arrays.asList("key1", "key2", "key3");
try {
ImmutableMap<String, String> results = cache.getAll(keys);
for (Map.Entry<String, String> entry : results.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
} catch (ExecutionException e) {
e.printStackTrace();
}
最佳实践
- 合理设置缓存大小: 根据内存情况和访问模式设置合适的
maximumSize - 选择合适的过期策略: 根据数据更新频率选择
expireAfterWrite或expireAfterAccess - 异常处理: 妥善处理
ExecutionException异常 - 监控缓存性能: 使用
CacheStats监控缓存命中率等指标 - 避免缓存雪崩: 可以设置随机过期时间避免大量缓存同时失效
// 启用统计信息收集
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.recordStats() // 启用统计
.build(cacheLoader);
// 查看缓存统计信息
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("平均加载时间: " + stats.averageLoadPenalty());
LoadingCache 是一个功能强大且易于使用的缓存解决方案,特别适用于需要自动加载和管理缓存数据的场景。
getUnchecked 方法详解
getUnchecked 是 LoadingCache 接口中一个重要的方法,它是 get 方法的变体,提供了不同的异常处理方式。
方法签名
V getUnchecked(K key);
方法特点
1. 与 get方法的区别
| 方法 | 异常处理方式 | 返回值 |
|---|---|---|
get(K key) |
抛出 ExecutionException |
正常返回值或抛出受检异常 |
getUnchecked(K key) |
将受检异常转换为运行时异常 | 正常返回值 |
2. 异常转换规则
- 如果
CacheLoader正常返回值,则直接返回该值 - 如果
CacheLoader抛出非受检异常(RuntimeException),则直接抛出 - 如果
CacheLoader抛出受检异常(Checked Exception),则将其包装成UncheckedExecutionException
使用示例
基本使用
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
// 模拟可能抛出异常的加载过程
if ("error_key".equals(key)) {
throw new IOException("加载失败");
}
return "value_for_" + key;
}
});
// 使用 get 方法(需要处理受检异常)
try {
String value1 = cache.get("normal_key");
} catch (ExecutionException e) {
// 处理 ExecutionException
}
// 使用 getUnchecked 方法(简化异常处理)
String value2 = cache.getUnchecked("normal_key"); // 正常情况直接返回值
// 对于会抛出异常的键
try {
String errorValue = cache.getUnchecked("error_key"); // 会抛出 UncheckedExecutionException
} catch (UncheckedExecutionException e) {
// 处理包装后的异常
System.out.println("原始异常: " + e.getCause()); // 可以通过 getCause 获取原始异常
}
实际应用场景
@Service
public class UserService {
private LoadingCache<Long, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<Long, User>() {
@Override
public User load(Long userId) throws Exception {
return userDAO.findById(userId); // 可能抛出SQLException等受检异常
}
});
/**
* 获取用户信息 - 使用 getUnchecked 简化代码
* 在业务逻辑中,我们更关心是否能获取到用户,而不是处理数据库异常
*/
public User getUser(Long userId) {
// 使用 getUnchecked 避免繁琐的异常处理代码
return userCache.getUnchecked(userId);
}
/**
* 获取用户信息 - 使用 get 方法需要处理异常
*/
public User getUserWithExceptionHandling(Long userId) {
try {
return userCache.get(userId);
} catch (ExecutionException e) {
// 需要处理受检异常
throw new RuntimeException("获取用户信息失败", e.getCause());
}
}
}
适用场景
1. 简化代码逻辑
当您不希望在业务代码中处理复杂的异常处理时:
// 简洁的写法
public String getUserName(Long userId) {
return userCache.getUnchecked(userId).getName();
}
2. 统一异常处理策略
当您的应用有统一的异常处理机制时:
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
try {
User user = userService.getUser(id);
return ResponseEntity.ok(user);
} catch (UncheckedExecutionException e) {
// 统一处理缓存加载异常
return ResponseEntity.notFound().build();
}
}
}
注意事项
1. 异常类型转换
// CacheLoader 抛出受检异常
@Override
public String load(String key) throws Exception {
throw new IOException("网络异常");
}
// getUnchecked 会将受检异常转换为运行时异常
try {
String value = cache.getUnchecked("key");
} catch (UncheckedExecutionException e) {
// 原始的 IOException 现在是 e.getCause()
if (e.getCause() instanceof IOException) {
// 处理 IO 异常
}
}
2. 性能考虑
getUnchecked 和 get 在性能上没有差异,差异仅在于异常处理方式。
总结
getUnchecked 方法的主要优势是简化了代码编写,特别是在以下场景中特别有用:
- 快速原型开发:避免复杂的异常处理代码
- 统一异常处理:配合全局异常处理器使用
- 业务逻辑简化:当缓存加载异常应该导致程序终止时
- 函数式编程:在 lambda 表达式中使用更方便
选择使用 get还是 getUnchecked 取决于您的异常处理策略和代码风格偏好。
更多推荐



所有评论(0)