1. 概述
在本教程中,我们将了解cache2k—一个轻量级、高性能、内存中的Java缓存库。
2. 关于cache2k
cache2k库通过无阻塞和无等待访问缓存值来提供快速访问时间。它还支持与Spring框架、Scala Cache、Datanucleus和Hibernate的集成。
该库具有许多功能,包括一组线程安全的原子操作、具有阻塞通读功能的缓存加载器、自动过期、提前刷新、事件监听器以及对JSR107 API的JCache实现的支持,我们将在本教程中讨论其中的一些功能。
重要的是要注意cache2k不是像Infinispan或Hazelcast这样的分布式缓存解决方案。
3. Maven依赖
要使用cache2k,我们需要首先将cache2k-base-bom依赖添加到pom.xml中:
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-base-bom</artifactId>
<version>1.2.3.Final</version>
<type>pom</type>
</dependency>
4. 一个简单的cache2k例子
现在,让我们通过一个简单的例子看看如何在Java应用程序中使用cache2k。
让我们考虑一个在线购物网站的例子,假设该网站对所有运动产品提供20%的折扣,对其他产品提供10%的折扣。我们的目标是缓存折扣,这样我们就不会每次都计算它。
因此,首先,我们将创建一个ProductHelper类并创建一个简单的缓存实现:
public class ProductHelper {
private Cache<String, Integer> cachedDiscounts;
private int cacheMissCount = 0;
public ProductHelper() {
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
.name("discount")
.eternal(true)
.entryCapacity(100)
.build();
}
public Integer getDiscount(String productType) {
Integer discount = cachedDiscounts.get(productType);
if (Objects.isNull(discount)) {
cacheMissCount++;
discount = "Sports".equalsIgnoreCase(productType) ? 20 : 10;
cachedDiscounts.put(productType, discount);
}
return discount;
}
// Getters and setters
}
如我们所见,我们使用了cacheMissCount变量来计算在缓存中未找到折扣的次数。因此,如果getDiscount方法使用缓存来获取折扣,则cacheMissCount不会发生变化。
接下来,我们将编写一个测试用例并验证我们的实现:
@Test
public void whenInvokedGetDiscountTwice_thenGetItFromCache() {
ProductHelper productHelper = new ProductHelper();
assertTrue(productHelper.getCacheMissCount() == 0);
assertTrue(productHelper.getDiscount("Sports") == 20);
assertTrue(productHelper.getDiscount("Sports") == 20);
assertTrue(productHelper.getCacheMissCount() == 1);
}
最后,让我们快速浏览一下我们使用的配置。
第一个是name方法,它设置缓存的唯一名称。缓存名称是可选的,如果我们不提供,则会生成它。
然后,我们将eternal设置为true以指示缓存值不会随时间过期。因此,在这种情况下,我们可以选择显式地从缓存中删除元素。否则,一旦缓存达到其容量,元素将被自动逐出。
此外,我们还使用了entryCapacity方法来指定缓存中可容纳的最大条目数。当缓存达到最大大小时,缓存逐出算法将删除一个或多个条目以维持指定的容量。
我们可以进一步探索Cache2kBuilder类中的其他可用配置。
5. cache2k功能
现在,让我们增强示例以探索一些cache2k功能。
5.1 配置缓存过期
到目前为止,我们已经为所有运动产品提供了固定折扣。但是,我们的网站现在希望仅在固定时间段内提供折扣。
为了满足这个新要求,我们将使用expireAfterWrite方法配置缓存过期:
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
// other configurations
.expireAfterWrite(10, TimeUnit.MILLISECONDS)
.build();
现在让我们编写一个测试用例来检查缓存是否过期:
@Test
public void whenInvokedGetDiscountAfterExpiration_thenDiscountCalculatedAgain() throws InterruptedException {
ProductHelper productHelper = new ProductHelper();
assertTrue(productHelper.getCacheMissCount() == 0);
assertTrue(productHelper.getDiscount("Sports") == 20);
assertTrue(productHelper.getCacheMissCount() == 1);
Thread.sleep(20);
assertTrue(productHelper.getDiscount("Sports") == 20);
assertTrue(productHelper.getCacheMissCount() == 2);
}
在我们的测试用例中,我们尝试在配置的持续时间过后再次获取折扣。可以看到,与前面的示例不同,cacheMissCount已递增。这是因为缓存中的商品已过期,折扣会再次计算。
对于高级缓存过期配置,我们还可以配置ExpiryPolicy。
5.2 缓存加载或通读
在我们的示例中,我们使用缓存备用模式来加载缓存,这意味着我们已经在getDiscount方法中按需计算并添加了缓存中的折扣。
或者,我们可以简单地使用cache2k支持通读操作。在此操作中,缓存将在加载器的帮助下自行加载缺失值,这也称为缓存加载。
现在,让我们进一步增强我们的示例以自动计算和加载缓存:
cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
// other configurations
.loader((key) -> {
cacheMissCount++;
return "Sports".equalsIgnoreCase(key) ? 20 : 10;
})
.build();
此外,我们将从getDiscount中删除计算和更新折扣的逻辑:
public Integer getDiscount(String productType) {
return cachedDiscounts.get(productType);
}
之后,让我们编写一个测试用例以确保加载器按预期工作:
@Test
public void whenInvokedGetDiscount_thenPopulateCacheUsingLoader() {
ProductHelper productHelper = new ProductHelper();
assertTrue(productHelper.getCacheMissCount() == 0);
assertTrue(productHelper.getDiscount("Sports") == 20);
assertTrue(productHelper.getCacheMissCount() == 1);
assertTrue(productHelper.getDiscount("Electronics") == 10);
assertTrue(productHelper.getCacheMissCount() == 2);
}
5.3 事件监听器
我们还可以为不同的缓存操作配置事件监听器,例如缓存元素的插入、更新、删除和过期。
假设我们要记录所有添加到缓存中的条目,因此,让我们在缓存构建器中添加一个事件监听器配置:
.addListener(new CacheEntryCreatedListener<String, Integer>() {
@Override
public void onEntryCreated(Cache<String, Integer> cache, CacheEntry<String, Integer> entry) {
LOGGER.info("Entry created: [{}, {}].", entry.getKey(), entry.getValue());
}
})
现在,我们可以执行我们创建的任何测试用例并验证日志:
Entry created: [Sports, 20].
需要注意的是,除了到期事件之外,事件监听器都是同步执行的。如果我们想要一个异步监听器,我们可以使用addAsyncListener方法。
5.4 原子操作
Cache类有许多支持原子操作的方法,这些方法仅适用于对单个条目的操作。
这些方法包括containsAndRemove、putIfAbsent、removeIfEquals、replaceIfEquals、peekAndReplace和peekAndPut。
6. 总结
在本教程中,我们研究了cache2k库及其一些有用的功能。我们可以参考cache2k用户指南来进一步探索这个库。
Post Directory
