From 31f4581de3b4fcf94e6af8812b62d8a3cf08603e Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 11 Oct 2017 04:53:59 +0300 Subject: [PATCH] BAEL-1041 Introduction to Caffeine (#2585) * BAEL-748 quick guide to @Value * BAEL-748 changes from review * BAEL-748 inject comma-separated values into array * BAEL-768 Introduction to Netty * BAEL-768 remove commented code * BAEL-861 Introduction to Awaitility * BAEL-861 rename Test to UnitTest * BAEL-1041 Introduction in Caffeine * BAEL-1041 fix test * BAEL-1041 fix formatting * BAEL-1041 fix expected/actual order * BAEL-1041 remove trailing underscore * Formatting after merge * BAEL-1041 add spaces between data and assertions * BAEL-1041 soft values example * BAEL-1041 remove duplicate dependency * BAEL-1041 formatting fix * BAEL-1041 formatting fix --- libraries/pom.xml | 16 +- .../com/baeldung/caffeine/DataObject.java | 28 +++ .../baeldung/caffeine/CaffeineUnitTest.java | 174 ++++++++++++++++++ 3 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 libraries/src/main/java/com/baeldung/caffeine/DataObject.java create mode 100644 libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java diff --git a/libraries/pom.xml b/libraries/pom.xml index e539c0916f..b519b9cd53 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -590,11 +590,16 @@ docx4j 3.3.5 - - javax.xml.bind - jaxb-api - 2.1 - + + javax.xml.bind + jaxb-api + 2.1 + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + @@ -666,5 +671,6 @@ 1.0.3 1.0.0 3.8.4 + 2.5.5 diff --git a/libraries/src/main/java/com/baeldung/caffeine/DataObject.java b/libraries/src/main/java/com/baeldung/caffeine/DataObject.java new file mode 100644 index 0000000000..2a8b60b045 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/caffeine/DataObject.java @@ -0,0 +1,28 @@ +package com.baeldung.caffeine; + +final class DataObject { + private final String data; + + private static int objectCounter = 0; + + private DataObject(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + @Override + public String toString() { + return "DataObject{" + + "data='" + data + '\'' + + '}'; + } + + public static DataObject get(String data) { + objectCounter++; + System.out.println(String.format("Initializing DataObject#%d with data '%s'", objectCounter, data)); + return new DataObject(data); + } +} diff --git a/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java b/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java new file mode 100644 index 0000000000..56dbda5974 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java @@ -0,0 +1,174 @@ +package com.baeldung.caffeine; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +import org.junit.Test; + +import com.github.benmanes.caffeine.cache.*; + +public class CaffeineUnitTest { + + @Test + public void givenCache_whenPopulate_thenValueStored() { + Cache cache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .maximumSize(100) + .build(); + + String key = "A"; + DataObject dataObject = cache.getIfPresent(key); + + assertNull(dataObject); + + dataObject = cache.get(key, k -> DataObject.get("Data for A")); + + assertNotNull(dataObject); + assertEquals("Data for A", dataObject.getData()); + + cache.put(key, dataObject); + dataObject = cache.getIfPresent(key); + + assertNotNull(dataObject); + + cache.invalidate(key); + dataObject = cache.getIfPresent(key); + + assertNull(dataObject); + } + + @Test + public void givenLoadingCache_whenGet_thenValuePopulated() { + LoadingCache cache = Caffeine.newBuilder() + .maximumSize(100) + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(k -> DataObject.get("Data for " + k)); + String key = "A"; + + DataObject dataObject = cache.get(key); + + assertNotNull(dataObject); + assertEquals("Data for " + key, dataObject.getData()); + + Map dataObjectMap = cache.getAll(Arrays.asList("A", "B", "C")); + + assertEquals(3, dataObjectMap.size()); + } + + @Test + public void givenAsyncLoadingCache_whenGet_thenValuePopulated() { + + AsyncLoadingCache cache = Caffeine.newBuilder() + .maximumSize(100) + .expireAfterWrite(1, TimeUnit.MINUTES) + .buildAsync(k -> DataObject.get("Data for " + k)); + String key = "A"; + + cache.get(key).thenAccept(dataObject -> { + assertNotNull(dataObject); + assertEquals("Data for " + key, dataObject.getData()); + }); + + cache.getAll(Arrays.asList("A", "B", "C")) + .thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size())); + } + + @Test + public void givenLoadingCacheWithSmallSize_whenPut_thenSizeIsConstant() { + LoadingCache cache = Caffeine.newBuilder() + .maximumSize(1) + .refreshAfterWrite(10, TimeUnit.MINUTES) + .build(k -> DataObject.get("Data for " + k)); + + assertEquals(0, cache.estimatedSize()); + + cache.get("A"); + + assertEquals(1, cache.estimatedSize()); + + cache.get("B"); + cache.cleanUp(); + + assertEquals(1, cache.estimatedSize()); + } + + @Test + public void givenLoadingCacheWithWeigher_whenPut_thenSizeIsConstant() { + LoadingCache cache = Caffeine.newBuilder() + .maximumWeight(10) + .weigher((k,v) -> 5) + .build(k -> DataObject.get("Data for " + k)); + + assertEquals(0, cache.estimatedSize()); + + cache.get("A"); + + assertEquals(1, cache.estimatedSize()); + + cache.get("B"); + + assertEquals(2, cache.estimatedSize()); + + cache.get("C"); + cache.cleanUp(); + + assertEquals(2, cache.estimatedSize()); + } + + @Test + public void givenTimeEvictionCache_whenTimeLeft_thenValueEvicted() { + LoadingCache cache = Caffeine.newBuilder() + .expireAfterAccess(5, TimeUnit.MINUTES) + .build(k -> DataObject.get("Data for " + k)); + + cache = Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.SECONDS) + .weakKeys() + .weakValues() + .build(k -> DataObject.get("Data for " + k)); + + cache = Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.SECONDS) + .softValues() + .build(k -> DataObject.get("Data for " + k)); + + cache = Caffeine.newBuilder().expireAfter(new Expiry() { + @Override + public long expireAfterCreate(@Nonnull String key, @Nonnull DataObject value, long currentTime) { + return value.getData().length() * 1000; + } + + @Override + public long expireAfterUpdate(@Nonnull String key, @Nonnull DataObject value, long currentTime, long currentDuration) { + return currentDuration; + } + + @Override + public long expireAfterRead(@Nonnull String key, @Nonnull DataObject value, long currentTime, long currentDuration) { + return currentDuration; + } + }).build(k -> DataObject.get("Data for " + k)); + + cache = Caffeine.newBuilder() + .refreshAfterWrite(1, TimeUnit.MINUTES) + .build(k -> DataObject.get("Data for " + k)); + } + + @Test + public void givenCache_whenStatsEnabled_thenStatsRecorded() { + LoadingCache cache = Caffeine.newBuilder() + .maximumSize(100) + .recordStats() + .build(k -> DataObject.get("Data for " + k)); + cache.get("A"); + cache.get("A"); + + assertEquals(1, cache.stats().hitCount()); + assertEquals(1, cache.stats().missCount()); + } +} \ No newline at end of file