LoadingCache 接口详解

LoadingCache 是 Google Guava 库提供的一个接口,它扩展了 CacheFunction 接口,提供了自动加载缓存值的功能。

主要特性

  1. 自动加载: 当缓存中不存在某个键时,会自动调用 CacheLoader 加载数据
  2. 线程安全: 支持并发访问,保证在多线程环境下的数据一致性
  3. 异常处理: 提供了完善的异常处理机制
  4. 丰富的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();
}

最佳实践

  1. 合理设置缓存大小: 根据内存情况和访问模式设置合适的 maximumSize
  2. 选择合适的过期策略: 根据数据更新频率选择 expireAfterWriteexpireAfterAccess
  3. 异常处理: 妥善处理 ExecutionException 异常
  4. 监控缓存性能: 使用 CacheStats 监控缓存命中率等指标
  5. 避免缓存雪崩: 可以设置随机过期时间避免大量缓存同时失效
// 启用统计信息收集
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 方法详解

getUncheckedLoadingCache 接口中一个重要的方法,它是 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. 性能考虑

getUncheckedget 在性能上没有差异,差异仅在于异常处理方式。

总结

getUnchecked 方法的主要优势是简化了代码编写,特别是在以下场景中特别有用:

  1. 快速原型开发:避免复杂的异常处理代码
  2. 统一异常处理:配合全局异常处理器使用
  3. 业务逻辑简化:当缓存加载异常应该导致程序终止时
  4. 函数式编程:在 lambda 表达式中使用更方便

选择使用 get还是 getUnchecked 取决于您的异常处理策略和代码风格偏好。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐