Почему @Cachable работает с закрытым методом в Spring Boot 3.0

Я использую Spring Boot 3.x, и аннотация @Cacheable работает с методами, которые не являются общедоступными. Это странно, поскольку в документации четко указано:

Видимость метода и аннотации кеширования

При использовании прокси следует применять аннотации кеширования только к общедоступным методам. Если вы снабжаете этими аннотациями защищенные, частные или видимые для пакета методы, ошибка не возникает, но аннотированный метод не отображает настроенные параметры кэширования. Рассмотрите возможность использования AspectJ (см. остальную часть этого раздела), если вам нужно аннотировать закрытые методы, поскольку он меняет сам байт-код.

Вот мой пример услуги:

@Service
public class CacheService {

    @Cacheable("cache1")
    String foo() {
        System.out.println("string");
        return "string";
    }
}

И очень простой тест:

@SpringBootTest
class ApplicationTests {
    @Autowired
    private CacheService cacheService;

    @Test
    void test2() {
        cacheService.foo();
        cacheService.foo();
        cacheService.foo();
        cacheService.foo();
        cacheService.foo();
        cacheService.foo();
    }

}

В Spring Boot 2.7 вывод консоли показывает 6 «строковых» сообщений, но в версии 3.0.0 (и выше) отображается только 1 «строка».

Есть ли у вас идеи, что изменилось и где в документации описано такое поведение?

Это всплыло во время презентации компании, и я был уверен, что понял поведение этой аннотации, поэтому ищу аргументы, подтверждающие это :)

РЕДАКТИРОВАТЬ

Вот пример с ветвью spring-2.7 и spring-3.0.

1
0
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не используете прокси-серверы интерфейса JDK Spring по умолчанию, а напрямую проксируете класс, т. е. результатом будет прокси-сервер CGLIB, который по сути является подклассом исходного класса. То есть непубличные методы могут и будут проксироваться и делегироваться исходным целевым методам, если они не являются частными. Это ожидаемое поведение прокси Spring. Для справки:

ПРИМЕЧАНИЕ

Из-за того, что среда АОП Spring основана на прокси-сервере, вызовы внутри целевого объекта по определению не перехватываются. Для прокси-серверов JDK можно перехватывать только вызовы методов открытого интерфейса на прокси-сервере. С помощью CGLIB перехватываются вызовы общедоступных и защищенных методов на прокси-сервере (и даже методов, видимых пакетом, если необходимо). Однако обычное взаимодействие через прокси всегда должно осуществляться с помощью публичных подписей.


Обновление после появления MCVE

Учитывая следующую модификацию вашего сервиса и теста:

@Service
public class CacheService {
  @Cacheable("cache1")
  public String publicMethod() {
    System.out.println("public");
    return "public";
  }

  @Cacheable("cache1")
  String nonPublicMethod() {
    System.out.println("non-public");
    return "non-public";
  }
}
@SpringBootTest
class ApplicationTests {
  @Autowired
  private CacheService cacheService;

  @Test
  void test2() {
    cacheService.publicMethod();
    cacheService.publicMethod();
    cacheService.publicMethod();
    cacheService.nonPublicMethod();
    cacheService.nonPublicMethod();
    cacheService.nonPublicMethod();
  }
}

Я вижу следующее поведение, в зависимости от версии Spring Boot и от того, закомментирую ли я аннотацию @Cacheable одним из двух методов:

Версия Spring Boot Имеет @Cacheable публичный Имеет @Cacheable непубличный доступ Поведение 2,7 да нет Кэшируется только публичный метод 2,7 нет да Ни один метод не кэшируется 2,7 да да Кэшируется только публичный метод 3.0 да нет Кэшируется только публичный метод 3.0 нет да Кэшируется только закрытый метод. 3.0 да да Кэшируются как публичные, так и закрытые методы.

Вкратце: в Spring 2.7 кэширование применяется только к общедоступным методам. В Spring 3.0 кеширование работает и для непубличных (но нечастных) методов, несмотря на рекомендацию — на самом деле это не что иное, — использовать аннотацию только для публичных API.

Некоторые исследования в репозитории Spring Core выявили проблему № 25582: «@Transactional не работает с защищенными пакетами методами прокси CGLib» . В Spring 6.0 он был закрыт коммитом 37bebeaaaf с комментарием: «Принимать защищенные @Transactional/Cacheable методы на прокси-серверах CGLIB». То есть, он был изменен намеренно, что, по моему мнению, имеет смысл, поскольку делает Spring более универсальным в особых ситуациях и работает аналогичным образом в других контекстах прокси Spring, например. для Spring AOP.

Это поведение изменилось в Spring Framework 6.0/Spring Boot 3.0?

rechandler 19.07.2024 12:22

Нет, поведение Spring AOP на основе прокси было таким всегда. Что касается настроек Spring Boot по умолчанию в целом или @Cacheable в частности, я не знаю, так как не являюсь активным пользователем Spring. Возможно, кто-то другой сможет просветить вас больше. Если вы сможете опубликовать минимальный воспроизводитель (проект Maven) на GitHub с двумя ветками для Boot 2.7 и 3.0, я смогу рассмотреть его поближе.

kriegaex 19.07.2024 16:20

вот пример: github.com/rechandler12/spring-cachable-example

rechandler 19.07.2024 19:59

Спасибо. Я поэкспериментировал и немного исследовал ваш MCVE, а затем значительно обновил свой ответ.

kriegaex 20.07.2024 09:17

Другие вопросы по теме