Статьи

Кэширование веб-контента с помощью менеджера кеша Spring

В этой статье я хотел бы показать основы кеширования и управления кешированием веб-контента с помощью аннотаций Spring CacheManager, @Cacheable и JMX. Представьте себе интернет-магазин, который извлекает некоторый контент, такой как заголовок, нижний колонтитул, тизеры, основная навигация, из удаленной WCMS (системы управления веб-контентом). Выборка может происходить, например, через службу REST. Некоторый контент редко обновляется, поэтому имеет смысл кэшировать его в веб-приложении по соображениям производительности.

Начиная

Во-первых, нам нужен поставщик кеша. Хорошим поставщиком кеша был бы EhCache . Вам необходимо добавить EhCache в качестве зависимости к вашему проекту. Вам также необходимо настроить файл ehcache.xml, который описывает, помимо прочего, имена кэша, где и как долго хранится кэшированный контент. Пожалуйста, обратитесь к документации, чтобы узнать, как выглядит ehcache.xml. Центральный класс EhCache — это net.sf.ehcache.CacheManager. С помощью этого класса вы можете добавлять или удалять любые объекты в / из кэша и многое другое программно. Объекты могут быть кэшированы в памяти, на диске или где-то еще.

Платформа Spring предоставляет CacheManager, поддерживаемый EhCache — org.springframework.cache.CacheManager. Он также предоставляет аннотацию @Cacheable. Из документации : «Как видно из названия, @Cacheable используется для разграничения кешируемых методов, то есть методов, для которых результат сохраняется в кеше, поэтому при последующих вызовах (с теми же аргументами) значение в кеше возвращается без фактического выполнения метода. В самой простой форме для объявления аннотации требуется имя кэша, связанного с аннотированным методом ». Мы также будем использовать аннотации JMX. Это аннотации Spring @ManagedResource и @ManagedOperation. Зачем нам это нужно? Нам нужно, чтобы они могли очищать кеш (ы) через консоль JMX. Почему? Ну, например, базовые данные были изменены, но кеш еще не истек. Устаревшие данные будут по-прежнему считываться из кэша, а не из собственного источника. Бины, аннотированные @ManagedResource, будут представлены как бины JMX, а методы, аннотированные @ManagedOperation, могут быть выполнены через консоль JMX. Я рекомендую использовать JMiniX в качестве простой точки входа в JMX. Встраивание JMiniX в веб-приложение выполняется просто путем объявления сервлета. Параметризованные методы также поддерживаются, так что вы можете даже ввести некоторые реальные значения для параметров метода и запустить выполнение с этими значениями.

Как это сделать…

Теперь мы готовы разработать первый код. Нам нужен сервис, который обменивается данными с удаленным бэкэндом, чтобы получать различное содержимое из WCMS. Покажем примерный базовый код с одним методом fetchMainNavigation (). Этот метод выбирает структуру главного меню навигации и преобразует структуру в объект DTO NavigationContainerDTO (класс модели для меню). Вся деловая и техническая логика находится в компоненте MainNavigationHandler. Эта логика не важна для этого сообщения в блоге. Метод fetchMainNavigation () ожидает два параметра: локаль (например, английский или немецкий) и вариант (например, B2C или магазин B2B).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class WCMSServiceImpl extends BaseService implements WCMSService {
  
    // injection of Spring's CacheManager is needed for @Cacheable
    @Autowired
    private CacheManager cacheManager;
  
    @Autowired
    private MainNavigationHandler mainNavigationHandler;
  
    ...
  
    @Override
    @Cacheable(value = "wcms-mainnavigation",
                        key = "T(somepackage.wcms.WCMSBaseHandler).cacheKey(#root.methodName, #root.args[0], #root.args[1])")
    public NavigationContainerDTO fetchMainNavigation(Locale lang, String variant) {
        Object[] params = new Object[0];
        if (lang != null) {
            params = ArrayUtils.add(params, lang);
        }
        if (variant != null) {
            params = ArrayUtils.add(params, variant);
        }
  
        return mainNavigationHandler.get("fetchMainNavigation", params);
    }
}

Метод аннотируется аннотацией Spring @Cacheable. Это означает, что возвращенный объект NavigationContainerDTO будет кэширован, если он еще не был доступен в кэше. Следующая выборка вернет объект из кеша, пока не истечет срок его кеша. Кэширование происходит в соответствии с настройками в ehcache.xml. Spring CacheManager автоматически находит поставщика EhCache в пути к классам. Атрибут value в @Cacheable указывает на имя кэша. Атрибут key указывает на ключ в кеше, к которому может получить доступ объект. Поскольку кеши по сути являются хранилищами значений ключей, каждый вызов кэшированного метода необходимо преобразовать в подходящий ключ для доступа к кешу. В простом случае ключом может быть любая статическая строка. В этом примере нам нужен динамический ключ, потому что метод имеет два параметра: локаль и вариант. К счастью, Spring поддерживает динамические ключи с выражением SpEL (выражение Spring EL). См. Таблицу « Доступные метаданные Cache SpEL » для более подробной информации. Вы можете вызвать любой статический метод, генерирующий ключ. Наше выражение T (somepackage.wcms.WCMSBaseHandler) .cacheKey (# root.methodName, # root.args [0], # root.args [1]) означает, что мы вызываем статический метод cacheKey в классе WCMSBaseHandler с тремя параметрами: имя метода, первый и второй аргументы (локаль и вариант соответственно). Это наш генератор ключей.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static String cacheKey(String method, Object... params) {
    StringBuilder sb = new StringBuilder();
    sb.append(method);
 
    if (params != null && params.length > 0) {
        for (Object param : params) {
            if (param != null) {
                sb.append("-");
                sb.append(param.toString());
            }
        }
    }
 
    return sb.toString();
}

Давайте покажем, как выглядит класс обработчика MainNavigationHandler. Это просто упрощенный пример из реального проекта.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Component
@ManagedResource(objectName = "bean:name=WCMS-MainNavigation",
                                description = "Manages WCMS-Cache for the Main-Navigation")
public class MainNavigationHandler extends WCMSBaseHandler<NavigationContainerDTO, Navigation> {
 
    @Override
    NavigationContainerDTO retrieve(Objects... params) {
        // the logic for content retrieving and DTOs mapping is placed here
        ...
    }
  
    @ManagedOperation(description = "Delete WCMS-Cache")
    public void clearCache() {
        Cache cache = cacheManager.getCache("wcms-mainnavigation");
        if (cache != null) {
            cache.clear();
        }
    }
}

CacheManager также доступен здесь благодаря следующей инъекции в WCMSBaseHandler.

1
2
@Autowired
private CacheManager cacheManager;

@ManagedResource — это аннотация Spring JMX, поэтому бины экспортируются как JMX MBean и становятся видимыми в консоли JMX. Метод, который нужно экспортировать, должен быть аннотирован @ManagedOperation. Это метод clearCache (), который удаляет весь контент для основной навигации из кэша. «Весь контент» означает объект типа NavigationContainerDTO. Разработанный сервис WCMS теперь может быть внедрен в компонент на внешней стороне. Я уже писал в блоге о том, как построить многоуровневое меню с простым HTML, и показал код. Это как раз основная навигация от этого сервиса.

Есть больше …

Сканирование аннотаций JMX должно быть настроено в XML-файле конфигурации Spring.

1
2
3
4
5
6
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="server" ref="mbeanServer"/>
    <property name="assembler" ref="assembler"/>
    <property name="namingStrategy" ref="namingStrategy"/>
    <property name="autodetect" value="true"/>
</bean>

Консоль JMX JMiniX доступна через http (s): //: / mct / webshop / admin / jmx /. Нажатие на кнопку выполнения метода clearCache () запускает очистку кэша.

jmxBeans