Какие способы существуют для организации кэширования в Spring без изменения исходного кода сервиса
Что проверяют
Понимаете ли вы, что кэширование в Spring держится на прокси и AOP, а значит его можно навесить снаружи, не трогая класс сервиса. Следом обычно спрашивают, почему кэш не срабатывает при вызове метода из того же класса.
Как отвечать
Сразу оговорите: @Cacheable на методе сервиса это уже правка исходника. Без неё остаются такие пути:
- XML-конфигурация кэша: пространство имён
cacheописывает<cache:advice>с правиламиcacheableиcache-evictпо именам методов, а<aop:advisor>с pointcut привязывает его к классу сервиса; - свой аспект:
@Aspectс@Aroundна методы сервиса, внутриCacheManager.getCache(...)и логика «есть в кэше, вернуть, иначе вызвать и положить»; - декоратор: класс с тем же интерфейсом оборачивает исходный бин и регистрируется как
@Primaryили подставляется черезBeanPostProcessor; - кэш уровнем ниже: второй уровень кэша Hibernate и кэш запросов, если медленное место это база;
- кэш уровнем выше:
Cache-ControlиETag(ShallowEtagHeaderFilter), reverse proxy или шлюз перед сервисом.
Хранилище в Spring Boot меняется конфигурацией: spring.cache.type (caffeine, redis, simple), параметры Caffeine через spring.cache.caffeine.spec, например maximumSize=10000,expireAfterWrite=10m. Настройки подключения к Redis начиная с Boot 3 живут в spring.data.redis.*, а не в spring.redis.*, как в старых примерах.
Где спотыкаются
Называют @Cacheable и @EnableCaching ответом на «без изменения кода». Интервьюер ждёт AOP, XML-совет или декоратор.
Забывают про самовызов: прокси перехватывает только внешние вызовы, поэтому this.findById() изнутри того же бина идёт мимо кэша. То же с private и final методами.
Ставят локальный ConcurrentMapCacheManager в сервис с несколькими экземплярами. У каждого свой кэш, @CacheEvict чистит только один узел, и пользователи видят разные данные.