适合小项目或者自己的学习项目使用的组件-Guava
引入
我在GitHub学习开源项目时,发现一些小项目为了不引入过多的中间件,就会使用Guava来代替,这样可以减少项目上线以及后期维护成本。
那到底可以代替哪些中间件呢?
-
它有一个时效性缓存,可以替代Redis。
-
它有一个 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 |
指定回调任务在哪个线程池执行 |
应用场景:订单创建后的异步处理
假设我们开发一个电商系统。
用户下单之后,需要执行三个操作:
-
保存订单。
-
发送订单通知。
-
更新用户积分。
其中发送通知是一个耗时操作,我们不希望它阻塞订单主流程。
可以使用 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());
}

浙公网安备 33010602011771号