ПРИХОДИТЕ УЧИТЬСЯ НОВОЙ ПРОФЕССИИ ЛЕТОМ СО СКИДКОЙ ДО 70%Забронировать скидку

Spring Cache: проблема вызова @Cacheable метода внутри класса

Пройдите тест, узнайте какой профессии подходите и получите бесплатную карьерную консультацию
В конце подарим скидку до 55% на обучение
Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

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

Вот пример с внедрением ссылки на сам бин и внедрением поля:

Java
Скопировать код
@Service
public class MyService {

    // Ссылка на сам объект
    @Autowired
    private MyService proxy;

    public void callCachedMethod() {
        proxy.cachedOperation(); // Вызывается метод с кэшированием
    }
    
    @Cacheable(cacheNames = "myCache")
    public String cachedOperation() {
        return "пример кэширования";
    }
}

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

Пройдите тест и узнайте подходит ли вам сфера IT
Пройти тест

Разбор работы Spring Cache при внутренних вызовах

Spring использует динамические прокси для перехвата вызовов методов с аннотацией @Cacheable. Главное: прокси функционируют только при вызовах снаружи бина, а не внутри, аналогично вызову приватного метода из других классов без использования прокси.

Прокси и их применение

В Spring AOP выбор между использованием динамических прокси JDK и CGLIB зависит от конфигурации. JDK прокси кешируют только вызовы интерфейсных методов, CGLIB прокси — и методы классов. Однако оба варианта не кешируют внутренние вызовы в бине, так как они не взаимодействуют с прокси, и @Cacheable не срабатывает.

Как обеспечить кэширование при внутренних вызовах

Хорошей практикой для решения этой проблемы будет установка proxyTargetClass в true в настройках кэша или использования внедрённого бина. Будьте внимательны к циклическим зависимостям при автовнедрении ApplicationContext. Если эти методы не подходят, можно разнести методы по разным бинам.

Дополнительные инструменты: AspectJ и отладка кэша

Для сложных ситуаций с вызовами внутри класса вы можете использовать AspectJ, который встраивается на этапе компиляции и обходит необходимость использования прокси.

Для понимания, как работает ваш кэш, полезна отладка. Старайтесь логировать попадания и промахи кэша для анализа его эффективности.

Визуализация

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

Markdown
Скопировать код
Обычный вызов без кэширования:
🚶 -> 📝 -> 🚪 (заходит и выходит из того же 🏠)

Ожидаемое Spring'ом действие:
🚶 -> 📝 -> 📮 -> 🚶 (сообщение доставляется из другого 🏠)

Система кэширования активируется только при использовании "почтовой системы".

Markdown
Скопировать код
Решение:
🚶 -> 📝 -> 🚪 -> 📮 -> ✅ -> 🚶 (сообщение доставлено в том же 🏠, но через почту)

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

Оптимизация Spring Cache: возможности и ограничения

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

Подходы к настройке кэша

Применяйте @CacheConfig на уровне класса, чтобы создать чёткий шаблон кэширования, что упрощает чтение и управление настройками.

Продвинутые методы кэширования: самоссылка, планирование мощности и стратегии очистки

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

В крайних ситуациях можно использовать ApplicationContext, как показано ниже:

Java
Скопировать код
@Autowired
private ApplicationContext applicationContext;

public String invokeCacheable() {
    MyService beanProxy = applicationContext.getBean(MyService.class);
    return beanProxy.invokeCacheable();
}

Но следует избегать частого использования ApplicationContext, так как это может создавать тесные и сложноуправляемые связи в приложении.

Полезные материалы

  1. Официальная документация Spring по кэшированию — подробное руководство по кэшированию в Spring.
  2. Подробное руководство по кэшированию данных в Spring — вводное руководство по использованию Spring Cache.
  3. Реализация кэша в Spring Boot — пошаговое руководство по интеграции кэширования в Spring Boot.
  4. Учебник по работе с кэшированием в Spring на DigitalOcean — всё о @Cacheable и других аннотациях кэширования.
  5. Принципы работы кэширования в Spring на DZone — детальный обзор механизма кэширования в Spring.