适合小项目或者自己的学习项目使用的组件-Guava

引入

官方文档

我在GitHub学习开源项目时,发现一些小项目为了不引入过多的中间件,就会使用Guava来代替,这样可以减少项目上线以及后期维护成本。

那到底可以代替哪些中间件呢?

  1. 它有一个时效性缓存,可以替代Redis。

  2. 它有一个 EventBus 消息总线,可以替代 MQ 使用。

Maven依赖引入

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.8.0-jre</version>
</dependency>

特别提醒

如果是 Android 环境,要引入依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.8.0-android</version>
</dependency>

组件介绍

Guava是 Google 开源的一组Java核心库,其中包括新的集合类型(例如 multimap 和 multiset)、不可变集合、图形库以及用于并发、I/O、哈希、原语、字符串等的实用程序!它被广泛用于 Google 内部的大多数 Java 项目,也被许多其他公司广泛使用。

常用功能

1.本地缓存

@Test
public void test_cache() {
  Cache<String, String> cache = CacheBuilder.newBuilder()
    // 最大存储条数,缓存将尝试逐出最近或不经常使用的条目
    .maximumSize(10000)
    // 有效时间
    .expireAfterWrite(3, TimeUnit.SECONDS)
    // 记录次数
    .recordStats()
    .build();
  cache.put("xfg", "bugstack.cn");
  log.info("测试结果:{}", cache.getIfPresent("hhh"));
  cache.invalidate("xfg"); // cache.invalidateAll(); 也可以全部删除
  log.info("测试结果:{}", cache.getIfPresent("xfg"));
  log.info("测试结果:{}", cache.stats());
}

还可以加上 weigher()这个参数来限制存储条数,但是使用这个参数之后就不能和 maximumSize() 共用,否则 Guava 在构建缓存时会抛出 IllegalStateException,提示 "weigher can not be combined with maximum size"。

@Test
public void test_cache() {
  Cache<String, String> cache = CacheBuilder.newBuilder()
    // 正确做法,用 maximumWeight 代替 maximumSize
    // 限制权重
    .maximumWeight(10000)
    .weigher((Weigher<String, String>) (k, v) -> k.length() + v.length()) // 返回非负的权重
    // 有效时间
    .expireAfterWrite(3, TimeUnit.SECONDS)
    // 记录次数
    .recordStats()
    .build();
  cache.put("hhh", "junzhecai.com");
  log.info("测试结果:{}", cache.getIfPresent("hhh"));
  cache.invalidate("xfg"); // cache.invalidateAll(); 也可以全部删除
  log.info("测试结果:{}", cache.getIfPresent("hhh"));
  log.info("测试结果:{}", cache.stats());
}
​

两者区别

  • maximumSize(10000):限制的是条目个数,最多存 10000 个 KV 对,不管每个条目多大。

  • maximumWeight(10000) + weigher:限制的是权重总和,可能存 10000 个很小的条目,也可能只存 600 多个较大的条目(取决于每个条目的权重)。

最后,如果你的项目不大,也不想自己实现有过期时间的缓存,那么 Guava 非常适合你

2.事件总线

@Test
public void test_eventbus() {
  EventBus eventBus = new EventBus();
  eventBus.register(new Listener());
  // 可以由其他服务推送消息,之后就可以在监听中收到了
  eventBus.post("消息总线,订单号:100001");
}
​
static class Listener {
  @Subscribe

事件总线和MQ消息队列实现的效果是一致的,都是为了解耦功能流程。但 Guava 这个组件和常见的 Kafka、RocketMQ、RabbitMQ 相比,接入成本和复杂程度简直小巫见大巫。

3.并发回调

@Test
public void test_ListenableFuture() throws InterruptedException {
  // 1. 创建 CountDownLatch,初始计数为 1
  // 用于让主线程等待异步任务的回调执行完成
  CountDownLatch countDownLatch = new CountDownLatch(1);
​
  // 2. 创建普通线程池,并通过 listeningDecorator 包装
  // 将 ExecutorService 转换为 ListeningExecutorService
  // 使提交任务后可以获得 ListenableFuture,支持注册回调
  ListeningExecutorService executorService =
    MoreExecutors.listeningDecorator(
    Executors.newFixedThreadPool(10)
  );
​
  // 3. 提交异步任务
  // 线程池中的工作线程执行 Callable,返回 "finished"
  // submit() 不会阻塞当前线程,而是立即返回 ListenableFuture
  ListenableFuture<String> explosion =
    executorService.submit(() -> "finished");
​
  // 4. 创建回调线程池
  // 用于指定回调方法在哪个线程中执行
  // 避免回调中的耗时操作影响原来的任务执行线程
  ExecutorService callBackService =
    Executors.newFixedThreadPool(1);
​
  // 5. 为 ListenableFuture 注册回调
  Futures.addCallback(
    explosion,
​
    // FutureCallback 定义任务执行成功和失败时的处理逻辑
    new FutureCallback<String>() {
​
      // 任务执行成功时调用
      // explosion 参数就是异步任务返回的结果
      @Override
      public void onSuccess(String explosion) {
        System.out.println("onSuccess");
        
        // 回调执行完成,计数器减 1
        // 计数器变为 0 后,主线程的 await() 被唤醒
        countDownLatch.countDown();
      }
​
      // 任务执行失败时调用
      // thrown 表示任务执行过程中产生的异常
      @Override
      public void onFailure(Throwable thrown) {
        System.out.println("onFailure");
​
        // 无论成功还是失败,都通知主线程回调已经执行完成
        countDownLatch.countDown();
      }
    },
    // 指定回调任务由 callBackService 线程池执行
    callBackService
  );
​
  // 6. 主线程阻塞等待
  // 直到 onSuccess() 或 onFailure() 中调用 countDown()
  // 计数器归零后,主线程才会继续执行
  countDownLatch.await();
​
  // 7. 关闭线程池,释放线程资源
  executorService.shutdown();
  callBackService.shutdown();
}
对象 作用
ListeningExecutorService 支持返回 ListenableFuture 的线程池
ListenableFuture 可以注册监听器的 Future
Futures.addCallback() 注册任务完成后的回调
ExecutorService 指定回调任务在哪个线程池执行

应用场景:订单创建后的异步处理

假设我们开发一个电商系统。

用户下单之后,需要执行三个操作:

  1. 保存订单。

  2. 发送订单通知。

  3. 更新用户积分。

其中发送通知是一个耗时操作,我们不希望它阻塞订单主流程。

可以使用 ListenableFuture:

ListeningExecutorService executor =
        MoreExecutors.listeningDecorator(
                Executors.newFixedThreadPool(10)
        );
​
// 异步发送订单通知
ListenableFuture<String> future =
        executor.submit(() -> {
            // 模拟发送短信
            System.out.println("正在发送订单通知");
​
            // 模拟发送成功
            return "通知发送成功";
        });
​
// 注册回调
Futures.addCallback(
        future,
        new FutureCallback<String>() {
​
            @Override
            public void onSuccess(String result) {
                System.out.println(result);
​
                // 通知发送成功,记录通知状态
                System.out.println("更新通知状态为成功");
            }
​
            @Override
            public void onFailure(Throwable t) {
                System.out.println("通知发送失败:" + t.getMessage());
​
                // 记录失败状态,后续可以进行重试
            }
        },
        Executors.newFixedThreadPool(1)
);

执行过程:

这个场景体现了 ListenableFuture:

  • 不需要主线程通过 get() 阻塞等待结果。

  • 不需要不断轮询任务状态。

  • 任务成功和失败可以分别处理。

  • 可以指定独立线程池执行回调。

不过,真实电商系统中,如果短信发送涉及可靠投递、服务重启后的任务恢复和持久化重试,通常会考虑 RocketMQ、RabbitMQ 等消息队列,而不是单纯依赖 ListenableFuture。

4.布隆过滤器

@Test
public void test_BloomFilter() {
  BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()),
            1000,
            0.01);
  // 向布隆过滤器中添加元素
  bloomFilter.put("apple");
  bloomFilter.put("banana");
  bloomFilter.put("orange");
  // 检查元素是否存在于布隆过滤器中
  System.out.println(bloomFilter.mightContain("apple"));   // true
  System.out.println(bloomFilter.mightContain("banana"));  // true
  System.out.println(bloomFilter.mightContain("orange"));  // true
  System.out.println(bloomFilter.mightContain("grape"));   // false
  // 输出布隆过滤器的统计信息
  System.out.println("Expected FPP: " + bloomFilter.expectedFpp());
  System.out.println("Number of Inserted Elements: " + bloomFilter.approximateElementCount());
}
​
posted @ 2026-11-06 02:02  coz_820  阅读(4)  评论(0)    收藏  举报