«1. Обзор
Проще говоря, JCache — это стандартный API кэширования для Java. В этом уроке мы увидим, что такое JCache и как мы можем его использовать.
2. Зависимости Maven
Чтобы использовать JCache, нам нужно добавить следующую зависимость в наш файл pom.xml:
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.0.0-PFD</version>
</dependency>
Обратите внимание, что последнюю версию библиотеки можно найти в центральном репозитории Maven.
Нам также нужно добавить реализацию API в наш pom.xml; здесь мы будем использовать Hazelcast:
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.9-EA</version>
</dependency>
Мы также можем найти последнюю версию Hazelcast в центральном репозитории Maven.
3. Реализации JCache
JCache реализован различными решениями кэширования:
-
Эталонная реализация JCache Hazelcast Oracle Coherence Terracotta Ehcache Infinispan
Обратите внимание, что, в отличие от других эталонных реализаций, не рекомендуется использовать эталонную реализацию JCache в производство, так как это вызывает некоторые проблемы параллелизма.
4. Основные компоненты
4.1. Cache
Интерфейс Cache имеет следующие полезные методы:
-
get() — принимает ключ элемента в качестве параметра и возвращает значение элемента; он возвращает null, если ключ не существует в кэше getAll() — этому методу можно передать несколько ключей в виде Set; метод возвращает заданные ключи и связанные значения в виде карты. – удаляет все элементы из Cache. containsKey() – проверяет, содержит ли Cache определенный ключ
Как мы видим, названия методов говорят сами за себя. Для получения дополнительной информации об этих и других методах посетите Javadoc.
4.2. CacheManager
CacheManager — один из самых важных интерфейсов API. Это позволяет нам устанавливать, настраивать и закрывать кэши.
4.3. CachingProvider
CachingProvider — это интерфейс, который позволяет нам создавать и управлять жизненным циклом CacheManager.
4.4. Конфигурация
Конфигурация — это интерфейс, который позволяет нам настраивать кэши. Он имеет одну конкретную реализацию — MutableConfiguration и подинтерфейс — CompleteConfiguration.
5. Создание кэша
Давайте посмотрим, как мы можем создать простой кэш:
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();
MutableConfiguration<String, String> config
= new MutableConfiguration<>();
Cache<String, String> cache = cacheManager
.createCache("simpleCache", config);
cache.put("key1", "value1");
cache.put("key2", "value2");
cacheManager.close();
Все, что мы делаем, это:
-
Создаем объект CachingProvider, который мы используем для создания CacheManager object Создание объекта MutableConfiguration, который является реализацией интерфейса Configuration Создание объекта Cache с использованием объекта CacheManager, который мы создали ранее Помещение всех записей, которые нам нужно кэшировать в наш объект Cache Закрытие CacheManager для освобождения ресурсов, используемых Cache ~ ~~ Если мы не предоставим какую-либо реализацию JCache в нашем pom.xml, будет выдано следующее исключение:
Причина этого в том, что JVM не смогла найти никакой конкретной реализации метода getCacheManager() .
javax.cache.CacheException: No CachingProviders have been configured
6. EntryProcessor
EntryProcessor позволяет нам изменять записи кэша, используя атомарные операции, без необходимости повторного добавления их в кэш. Чтобы использовать его, нам нужно реализовать интерфейс EntryProcessor:
Теперь давайте воспользуемся нашей реализацией EntryProcessor:
public class SimpleEntryProcessor
implements EntryProcessor<String, String, String>, Serializable {
public String process(MutableEntry<String, String> entry, Object... args)
throws EntryProcessorException {
if (entry.exists()) {
String current = entry.getValue();
entry.setValue(current + " - modified");
return current;
}
return null;
}
}
7. Прослушиватели событий
@Test
public void whenModifyValue_thenCorrect() {
this.cache.invoke("key", new SimpleEntryProcessor());
assertEquals("value - modified", cache.get("key"));
}
Прослушиватели событий позволяют нам выполнять действия при запуске любого из типы событий, определенные в перечислении EventType, а именно:
CREATED UPDATED REMOVED EXPIRED
-
Во-первых, нам нужно реализовать интерфейсы событий, которые мы собираемся использовать.
Например, если мы хотим использовать типы событий CREATED и UPDATED, то мы должны реализовать интерфейсы CacheEntryCreatedListener и CacheEntryUpdatedListener.
Давайте рассмотрим пример:
Теперь давайте запустим наш тест:
public class SimpleCacheEntryListener implements
CacheEntryCreatedListener<String, String>,
CacheEntryUpdatedListener<String, String>,
Serializable {
private boolean updated;
private boolean created;
// standard getters
public void onUpdated(
Iterable<CacheEntryEvent<? extends String,
? extends String>> events) throws CacheEntryListenerException {
this.updated = true;
}
public void onCreated(
Iterable<CacheEntryEvent<? extends String,
? extends String>> events) throws CacheEntryListenerException {
this.created = true;
}
}
8. CacheLoader
@Test
public void whenRunEvent_thenCorrect() throws InterruptedException {
this.listenerConfiguration
= new MutableCacheEntryListenerConfiguration<String, String>(
FactoryBuilder.factoryOf(this.listener), null, false, true);
this.cache.registerCacheEntryListener(this.listenerConfiguration);
assertEquals(false, this.listener.getCreated());
this.cache.put("key", "value");
assertEquals(true, this.listener.getCreated());
assertEquals(false, this.listener.getUpdated());
this.cache.put("key", "newValue");
assertEquals(true, this.listener.getUpdated());
}
CacheLoader позволяет нам использовать режим сквозного чтения для обработки кэша как основного хранилища данных. и считывать с него данные.
В реальном сценарии мы можем заставить кеш считывать данные из реального хранилища.
Давайте посмотрим на пример. Во-первых, мы должны реализовать интерфейс CacheLoader:
А теперь давайте воспользуемся нашей реализацией CacheLoader:
public class SimpleCacheLoader
implements CacheLoader<Integer, String> {
public String load(Integer key) throws CacheLoaderException {
return "fromCache" + key;
}
public Map<Integer, String> loadAll(Iterable<? extends Integer> keys)
throws CacheLoaderException {
Map<Integer, String> data = new HashMap<>();
for (int key : keys) {
data.put(key, load(key));
}
return data;
}
}
«
public class CacheLoaderTest {
private Cache<Integer, String> cache;
@Before
public void setup() {
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();
MutableConfiguration<Integer, String> config
= new MutableConfiguration<>()
.setReadThrough(true)
.setCacheLoaderFactory(new FactoryBuilder.SingletonFactory<>(
new SimpleCacheLoader()));
this.cache = cacheManager.createCache("SimpleCache", config);
}
@Test
public void whenReadingFromStorage_thenCorrect() {
for (int i = 1; i < 4; i++) {
String value = cache.get(i);
assertEquals("fromCache" + i, value);
}
}
}
«9. Заключение
В этом руководстве мы рассмотрели, что такое JCache, и рассмотрели некоторые его важные функции в нескольких практических сценариях.
Как всегда, полную реализацию этого руководства можно найти на GitHub.