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

Руководство по кэшированию в Spring

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

1. Абстракция кэша?

В этом уроке мы узнаем, как использовать абстракцию кэширования в Spring и в целом повысить производительность нашей системы.

Мы включим простое кэширование для некоторых реальных примеров методов и обсудим, как мы можем практически улучшить производительность этих вызовов с помощью интеллектуального управления кэшем.

2. Начало работы

Основная абстракция кэширования, предоставляемая Spring, находится в модуле spring-context . Поэтому при использовании Maven наш pom.xml должен содержать следующую зависимость:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>

Интересно, что есть еще один модуль с именем spring-context-support , который находится поверх модуля spring-context и предоставляет еще несколько CacheManager , поддерживаемых такими, как EhCache или Caffeine . Если мы хотим использовать их в качестве нашего хранилища кеша, нам нужно вместо этого использовать модуль spring-context-support :

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.3</version>
</dependency>

Поскольку модуль поддержки spring-context транзитивно зависит от модуля spring-context , нет необходимости в отдельном объявлении зависимостей для spring-context.

2.1. Весенний ботинок

Если мы используем Spring Boot, то мы можем использовать стартовый пакет spring-boot-starter-cache , чтобы легко добавить зависимости кэширования:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.4.0</version>
</dependency>

Под капотом стартер выводит модуль пружинно-контекстной опоры .

3. Включить кэширование

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

Мы можем включить функцию кэширования, просто добавив аннотацию @EnableCaching к любому из классов конфигурации:

@Configuration
@EnableCaching
public class CachingConfig {

@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("addresses");
}
}

Конечно, мы можем включить управление кешем с конфигурацией XML:

<beans>
<cache:annotation-driven />

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="addresses"/>
</set>
</property>
</bean>
</beans>

Примечание. После того, как мы включим кеширование, для минимальной настройки мы должны зарегистрировать cacheManager .

3.1. Использование весенней загрузки

При использовании Spring Boot простое присутствие начального пакета в пути к классам вместе с аннотацией EnableCaching регистрирует один и тот же ConcurrentMapCacheManager. Таким образом, нет необходимости в отдельном объявлении bean-компонента.

Кроме того, мы можем настроить автоматически настроенный CacheManager , используя один или несколько bean-компонентов CacheManagerCustomizer<T> :

@Component
public class SimpleCacheCustomizer
implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(asList("users", "transactions"));
}
}

Автоконфигурация CacheAutoConfiguration подбирает эти настройщики и применяет их к текущему CacheManager перед его полной инициализацией.

4. Используйте кэширование с аннотациями

После включения кэширования следующим шагом будет привязка поведения кэширования к методам с декларативными аннотациями.

4.1. @ Кэшируемый

Самый простой способ включить поведение кэширования для метода — разграничить его с помощью @Cacheable и параметризовать его именем кеша, в котором будут храниться результаты:

@Cacheable("addresses")
public String getAddress(Customer customer) {...}

Вызов getAddress() сначала проверяет адреса кеша, прежде чем фактически вызывать метод, а затем кэширует результат.

Хотя в большинстве случаев достаточно одного кеша, среда Spring также поддерживает передачу нескольких кешей в качестве параметров:

@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}

`` В этом случае, если какой-либо из кэшей содержит требуемый результат, результат возвращается, а метод не вызывается.

4.2. @CacheEvict _ ``

Теперь, в чем проблема сделать все методы @Cacheable ?

Проблема в размере. Мы не хотим заполнять кэш значениями, которые нам не нужны часто . Кэши могут разрастаться довольно быстро, и мы можем хранить много устаревших или неиспользуемых данных.

Мы можем использовать аннотацию @CacheEvict , чтобы указать на удаление одного или нескольких/всех значений, чтобы новые значения можно было снова загрузить в кеш:

@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

Здесь мы используем дополнительный параметр allEntries в сочетании с очисткой кеша; это очистит все записи в адресах кеша и подготовит его для новых данных.

`**4.3. @CachePut _`**

Хотя @CacheEvict уменьшает накладные расходы на поиск записей в большом кеше, удаляя устаревшие и неиспользуемые записи, мы хотим избежать вытеснения слишком большого количества данных из кеша .

Вместо этого мы выборочно обновляем записи всякий раз, когда мы их изменяем.

С помощью аннотации @CachePut мы можем обновлять содержимое кеша, не мешая выполнению метода. То есть метод всегда будет выполняться, а результат кэшироваться:

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

Разница между @Cacheable и @CachePut заключается в том, что @Cacheable пропустит запуск метода , тогда как @CachePut фактически запустит метод, а затем поместит его результаты в кеш.

4.4. @ Кэширование

Что, если мы хотим использовать несколько аннотаций одного типа для кэширования метода? Давайте посмотрим на неправильный пример:

@CacheEvict("addresses")
@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}

Приведенный выше код не скомпилируется, поскольку Java не позволяет объявлять несколько аннотаций одного типа для данного метода.

Обходной путь к вышеуказанной проблеме будет следующим:

@Caching(evict = { 
@CacheEvict("addresses"),
@CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}

Как показано во фрагменте кода выше, мы можем сгруппировать несколько аннотаций кэширования с помощью @Caching и использовать его для реализации собственной настраиваемой логики кэширования.

4.5. @ КэшКонфиг

С помощью аннотации @CacheConfig мы можем упорядочить некоторые настройки кеша в одном месте на уровне класса, чтобы нам не приходилось объявлять вещи несколько раз:

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

@Cacheable
public String getAddress(Customer customer) {...}

5. Условное кэширование

Иногда кэширование может не работать для метода во всех ситуациях.

Повторно используя наш пример из аннотации @CachePut , это будет как выполнять метод, так и кэшировать результаты каждый раз:

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

5.1. Параметр условия

Если нам нужен больший контроль над тем, когда аннотация активна, мы можем параметризовать @CachePut параметром условия, который принимает выражение SpEL и гарантирует, что результаты кэшируются на основе оценки этого выражения:

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}

5.2. Если параметр

Мы также можем управлять кешированием на основе вывода метода, а не ввода с помощью параметра « если » :

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

Приведенная выше аннотация будет кэшировать адреса, если они не короче 64 символов.

Важно знать, что параметры условия и исключения можно использовать вместе со всеми аннотациями кэширования.

Этот вид условного кэширования может оказаться весьма эффективным для управления большими результатами. Это также полезно для настройки поведения на основе входных параметров вместо принудительного применения общего поведения для всех операций.

6. Декларативное кэширование на основе XML

Если у нас нет доступа к исходному коду нашего приложения или мы хотим внедрить поведение кэширования извне, мы также можем использовать декларативное кэширование на основе XML.

Вот наша конфигурация XML:

<!-- the service that you wish to make cacheable -->
<bean id="customerDataService"
class="com.your.app.namespace.service.CustomerDataService"/>

<bean id="cacheManager"
class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="directory"/>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="addresses"/>
</set>
</property>
</bean>
<!-- define caching behavior -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
<cache:caching cache="addresses">
<cache:cacheable method="getAddress" key="#customer.name"/>
</cache:caching>
</cache:advice>

<!-- apply the behavior to all the implementations of CustomerDataService interface->
<aop:config>
<aop:advisor advice-ref="cachingBehavior"
pointcut="execution(* com.your.app.namespace.service.CustomerDataService.*(..))"/>
</aop:config>

7. Кэширование на основе Java

Вот эквивалентная конфигурация Java:

@Configuration
@EnableCaching
public class CachingConfig {

@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("directory"),
new ConcurrentMapCache("addresses")));
return cacheManager;
}
}

А вот и наш CustomerDataService :

@Component
public class CustomerDataService {

@Cacheable(value = "addresses", key = "#customer.name")
public String getAddress(Customer customer) {
return customer.getAddress();
}
}

8. Резюме

В этой статье мы обсудили основы кэширования в Spring и как эффективно использовать эту абстракцию с аннотациями.

Полную реализацию этой статьи можно найти в проекте GitHub .