Перейти к основному содержимому

Введение в JCache

· 5 мин. чтения

Задача: Наибольшая подстрока палиндром

Для заданной строки s, верните наибольшую подстроку палиндром входящую в s. Подстрока — это непрерывная непустая последовательность символов внутри строки. Стока является палиндромом, если она читается одинаково в обоих направлениях...

ANDROMEDA 42

1. Обзор

Проще говоря, JCache — это стандартный API кэширования для Java. В этом уроке мы увидим, что такое JCache и как мы можем его использовать.

2. Зависимости Maven

Чтобы использовать JCache, нам нужно добавить следующую зависимость в наш pom.xml :

<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>

Обратите внимание, что последнюю версию библиотеки мы можем найти в центральном репозитории Maven .

Нам также нужно добавить реализацию API в наш pom.xml ; мы будем использовать Hazelcast здесь:

<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>5.1.1</version>
</dependency>

Мы также можем найти последнюю версию Hazelcast в центральном репозитории Maven .

3. Реализации JCache

JCache реализуется различными решениями для кэширования:

  • Эталонная реализация JCache
  • Хейзелкаст
  • Согласованность оракула
  • Терракотовый тайник
  • Инфиниспан

Обратите внимание, что, в отличие от других эталонных реализаций, не рекомендуется использовать эталонную реализацию JCache в рабочей среде, так как это вызывает некоторые проблемы параллелизма.

4. Основные компоненты

4.1. Кэш

Интерфейс Cache имеет следующие полезные методы:

  • get() – принимает в качестве параметра ключ элемента и возвращает значение элемента; возвращает ноль , если ключ не существует в кеше
  • getAll() — этому методу можно передать несколько ключей в виде Set; метод возвращает заданные ключи и связанные значения в виде карты
  • getAndRemove() — метод извлекает значение по своему ключу и удаляет элемент из кэша .
  • put() — вставляет новый элемент в кэш
  • clear() — удаляет все элементы в кеше
  • containsKey () — проверяет, содержит ли кэш определенный ключ

Как мы видим, названия методов говорят сами за себя. Для получения дополнительной информации об этих и других методах посетите Javadoc .

4.2. CacheManager

CacheManager — один из самых важных интерфейсов API. Это позволяет нам устанавливать, настраивать и закрывать кэши .

4.3. CachingProvider

CachingProvider — это интерфейс, который позволяет нам создавать и управлять жизненным циклом CacheManagers .

4.4. Конфигурация

Configuration — это интерфейс, который позволяет нам настраивать Caches . У него есть одна конкретная реализация — MutableConfiguration и субинтерфейс — CompleteConfiguration .

5. Создание кеша

Давайте посмотрим, как мы можем создать простой Cache :

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 .
  • Создание объекта MutableConfiguration , который является реализацией интерфейса Configuration .
  • Создание объекта Cache с использованием объекта CacheManager , который мы создали ранее
  • Помещая все записи, нам нужно кэшировать в наш объект Cache
  • Закрытие CacheManager для освобождения ресурсов, используемых кешем

Если мы не обеспечим какую-либо реализацию JCache в нашем pom.xml , будет выдано следующее исключение:

javax.cache.CacheException: No CachingProviders have been configured

Причина этого в том, что JVM не смогла найти какой-либо конкретной реализации метода getCacheManager() .

6. Входной процессор

EntryProcessor позволяет нам изменять записи Cache с помощью атомарных операций без необходимости повторного добавления их в Cache . Чтобы использовать его, нам нужно реализовать интерфейс 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;
}
}

Теперь воспользуемся нашей реализацией EntryProcessor :

@Test
public void whenModifyValue_thenCorrect() {
this.cache.invoke("key", new SimpleEntryProcessor());

assertEquals("value - modified", cache.get("key"));
}

7. Слушатели событий

Прослушиватели событий позволяют нам выполнять действия при запуске любого из типов событий, определенных в перечислении EventType , а именно:

  • СОЗДАННЫЙ
  • ОБНОВЛЕНО
  • УДАЛЕННЫЙ
  • ИСТЕКШИЙ

Во-первых, нам нужно реализовать интерфейсы событий, которые мы собираемся использовать.

Например, если мы хотим использовать типы событий 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;
}
}

Теперь давайте запустим наш тест:

@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());
}

8. Кэш-загрузчик

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;
}
}

А теперь воспользуемся нашей реализацией CacheLoader :

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 .