«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.