@Cacheable не перехватывает метод, кеш всегда пуст - PullRequest
0 голосов
/ 26 сентября 2018

У меня есть следующий метод:

@Cacheable(value = "SAMPLE")
public List<SomeObj> find() {
     // Method that initiates and returns the List<SomeObj> and takes around 2-3 seconds, does some logging too
}

И я включаю кэширование в одном из моих классов конфигурации:

@EnableCaching
@Configuration
public SomeConf extends CachingConfigurerSupport {

    // Here I also initialize my classes with @Cacheable annotation

    @Bean
    @Override
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
        return cacheManager;
    }


    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

}

В моем pom.xml есть следующее:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>1.5.14.RELEASE</version>
</dependency>

Я объявляю CacheManager следующим образом:

@Bean
public CacheManager cacheManager(){
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
    return cacheManager;
}

Когда я получаю экземпляр @Autowired CacheManager в одном из моих @Service s, я вижучто существует кэш с именем "SAMPLE", но его записи всегда пусты.Я снова и снова вызываю метод find(), но, похоже, он не заполняет кэш.

Я попытался установить аргумент (скажем, int a) для метода find() и поместить егокак key = "#a" до @Cacheable, но ничего не изменилось.

Когда я пытаюсь воссоздать проблему в изолированной среде, я вижу, что она работает правильно.Но когда я добавляю свои зависимости (не являющиеся библиотеками компаний с открытым исходным кодом, которые также включают конфигурацию EhCache), это не работает.Как я могу отладить это, что я делаю неправильно?

Обновление:

Я также пытался использовать cacheManager = myCacheManager в @Cacheable.Не повезло.

Обновление 2:

Я использую AspectJ и Spring AOP.Я думаю, что это может иметь какое-то отношение к этому.Я пробовал @EnableCaching(mode = AdviceMode.ASPECTJ) с @EnableLoadTimeWeaving, но то же самое.

Обновление 3:

Я наконец смог воспроизвести проблему, вот она: client-api-cache

В основном, когда вы запускаете приложение и telnet localhost 9000 после отправки в него любой строки, оно должно вывести NOT CACHED один раз, даже если метод вызывается дважды в CachedController (Второе пришествие из кеша).Но он печатает дважды.

Ответы [ 3 ]

0 голосов
/ 01 октября 2018

Как заметил Эндрюс С., в автоконфигурации это звучит как столкновение кэшей. В javadoc

@EnableCaching есть некоторые полезные замечания о том, как выбран cacheManager иВ частности, идея о том, как действовать.В этом случае вы должны установить CachingConfigurer для выбора вашего кэша - возможно, вы можете просто расширить CachingConfigurerSupport (как в примере ниже) и все готово.

Бин типаCacheManager должен быть зарегистрирован, так как нет разумного значения по умолчанию, которое фреймворк может использовать в качестве соглашения.А в то время как элемент <cache:annotation-driven> предполагает bean-компонент с именем cacheManager, @EnableCaching ищет bean-компонент типа cache.Поэтому наименование метода компонента управления кешем не имеет значения.

Для тех, кто хочет установить более прямую связь между @EnableCaching и точным компонентом управления кешем, который будет использоваться, интерфейс обратного вызова CachingConfigurer можетбыть реализованным.Обратите внимание на @Override -аннотированные методы ниже:

@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {

     @Bean
     public MyService myService() {
         // configure and return a class having @Cacheable methods
         return new MyService();
     }

     @Bean
     @Override
     public CacheManager cacheManager() {
         // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
         return cacheManager;
     }

     @Bean
     @Override
     public KeyGenerator keyGenerator() {
         // configure and return an implementation of Spring's KeyGenerator SPI
         return new MyKeyGenerator();
     }
}

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

Обратите внимание также на метод keyGenerator в приведенном выше примере.Это позволяет настроить стратегию генерации ключа кэша для KeyGenerator SPI Spring.Обычно @EnableCaching настраивает Spring 10 * для этой цели, но при реализации CachingConfigurer генератор ключей должен быть предоставлен явно.Возврат null или new SimpleKeyGenerator() из этого метода, если настройка не требуется.

CachingConfigurer предлагает дополнительные параметры настройки: рекомендуется расширить с CachingConfigurerSupport, что обеспечивает реализацию по умолчанию для всех методов, которые могутбыть полезным, если вам не нужно все настраиватьСм. CachingConfigurer Javadoc для получения более подробной информации.

Вы также можете найти эту ветку полезной: Как иметь конфигурацию с несколькими кэш-менеджерами в весеннем кеше java

Редактировать:

Так что, к счастью, у меня есть проект, использующий кэширование, и я написал этот небольшой кусочек:

@Bean
@Override
public CacheResolver cacheResolver() {
    return new SimpleCacheResolver(cacheManager()) { 

        @Override
        protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
            Collection<String> toReturn = super.getCacheNames(context);
            toReturn.forEach(System.out::println);
            return toReturn;
        }

        @Override
        public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
            System.out.println(Arrays.toString(context.getArgs()));
            System.out.println(context.getClass());
            System.out.println(context.getMethod());
            System.out.println(context.getOperation());
            return super.resolveCaches(context);
        }
    };
}

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

[]

класс org.springframework.cache.interceptor.CacheAspectSupport $ CacheOperationContext public abstract ... Transferobjects.CacheContainer ... service.LookupService.getCacheSelections ()

Builder [public ... Transferobjects.CacheContainer ... dao.LookupFacade.getCacheSelections ()] caches = [cacheselections] |ключ = '' |keyGenerator = '' |cacheManager = '' |cacheResolver = '' |условие = '' |разве что = '' |sync = 'true'

Вывод context.getClass() представляет интерес, учитывая ваш вопрос об аспектах.Тем не менее, у меня есть очень похожий аспект ведения журнала / синхронизации в моем собственном коде, который не вызывает путаницы в остальной части кэширования.Попробуйте мой распознаватель и посмотрите, поучителен ли вывод о том, какие вызовы выполняются для кода кэша.

Edit # 2:

Кажется, проблема TLDR в том, что мы ожидаем @Cacheable работать в местах, где это невозможно - в основном из-за того, что фреймворк еще не полностью себя зарекомендовал.Вы используете InitializingBean, и я попытался заменить эту функциональность на @PostConstruct, который не работает.

Кэш Spring с использованием @Cacheable во время @PostConstruct не работает

Iполучил ваш код GitHubСначала я столкнулся с проблемой блокирования, о которой вы сообщали в другом потоке, но, чтобы преследовать одну вещь за раз, я просто закомментировал этот код блокировки и вызвал service.cachedMethod() напрямую.

Я запустил ваш файл jar с помощью --trace и заметил, что cacheManager был отсканирован и создан, но изначально он не был очевиден для меня, когда он вызывался.Так что, ничего страшного, у меня только что был метод bean RuntimeException.После этого я заметил, что обе ваши строки NOT CACHED печатаются до этого вызова - еще один намек на то, что все настроено не так, как ожидалось.

Наконец, я добавил System.out.println(service.toString()); в методе вашего контроллера afterPropertiesSet () и увидел com.company.client.api.domain.CachedServiceImpl@2bbfb8b - ваш объект, а не объект с прокси.Таким образом, нет кэширования.

Итак, в общем, вам нужно переделать, как вы пытаетесь включить эту функцию.

0 голосов
/ 02 октября 2018

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

Проверьте код, который решает нашу проблему: https://dumpz.org/cbx8h28KeAss

0 голосов
/ 26 сентября 2018

Убедитесь, что вы устанавливаете свой кеш в cachemanager, как показано ниже.

@EnableCaching
@Configuration
public SomeConf {   

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("find():List<SomeObj>")));
        return cacheManager;
    }


}
...