5

I have a method as following:

@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
}

And I am enabling caching in one of my configuration classes:

@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();
    }

}

I have the following in my 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>

I am declaring a CacheManager as follows:

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

When I get an @Autowired CacheManager instance into one of my @Services I can see that there exists a cache with name "SAMPLE", but its entries are always empty. I call again and again the method find(), but it doesn't seem to populate the cache.

I have tried to put an argument (say int a) to the find() method and put it as key = "#a" to @Cacheable, but nothing changed.

When I try to recreate the issue in an isolated environment, I can see that it works properly. But when I add my dependencies (non open-source company libraries, which include an EhCache configuration as well) it doesn't work. How can I debug this, what am I doing wrong?

Update:

I have also tried to use cacheManager = myCacheManager in @Cacheable as well. No luck.

Update 2:

I am using AspectJ and Spring AOP. I think that it may have something to do with it. I have tried @EnableCaching(mode = AdviceMode.ASPECTJ) with @EnableLoadTimeWeaving but same thing.

Update 3:

I was finally able to reproduce the issue, here it is: client-api-cache

Basically when you run the application and telnet localhost 9000 after you send any line to it, it should print NOT CACHED once even though the method is called twice in CachedController (Second coming from the cache). But it prints twice.

Hasan Can Saral
  • 2,950
  • 5
  • 43
  • 78
  • Is there an ehcache.xml file on the classpath which defines a cache named "find():List"? – Andrew S Sep 26 '18 at 13:05
  • Thanks for the comment. There are two cases here: 1) I don't explicitly configure a `CacheManager` and Spring configures one `GuavaCacheManager`, so `ehcache.xml` is irrelevant. 2) When I explicitly define a `CacheManager` `@Bean` with `EhCacheManager` and put an ehcache.xml which defines a cache named "find():List" (actually it doesn't accept the `<` chars so I had to replace the cache name but still). In both cases, couldn't get it to work. – Hasan Can Saral Sep 26 '18 at 13:10
  • @AndrewS Can you think of anything else with the updated question? Thanks. – Hasan Can Saral Oct 01 '18 at 13:53
  • Can you confirm `SomeConf` is scanned? `find()` doesn't have a parameter so not sure what it would use for a key - maybe get the Spring source to see if the cache proxy class is actually being invoked. – Andrew S Oct 01 '18 at 14:06
  • @AndrewS I can confirm that `@SomeConf` is scanned. I have also tried to put an argument (`int a`) to the `find()` method and put it as `key = "#a"` to `@Cacheable`, but nothing changed. – Hasan Can Saral Oct 01 '18 at 14:08

3 Answers3

4

The root cause is that you are misusing "afterPropertiesSet". So, what you are doing is endless loop and never pass control back to the Spring pipeline, so Spring isn't able to init caching facility properly.

Check out code which fixes our problem: https://dumpz.org/cbx8h28KeAss

Beastmaster
  • 376
  • 2
  • 10
1

As Andrews S noted, this does sound like colliding caches in the auto configuration.

@EnableCaching's javadoc has some useful notes regarding how the cacheManager is selected and, specifically, an idea on how to proceed. What you'd do in this case is establish a CachingConfigurer to select your cache - perhaps you can just extend CachingConfigurerSupport (as in example below) and be done.

A bean of type CacheManager must be registered, as there is no reasonable default that the framework can use as a convention. And whereas the <cache:annotation-driven> element assumes a bean named "cacheManager", @EnableCaching searches for a cache manager bean by type. Therefore, naming of the cache manager bean method is not significant.

For those that wish to establish a more direct relationship between @EnableCaching and the exact cache manager bean to be used, the CachingConfigurer callback interface may be implemented. Notice the @Override-annotated methods below:

@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();
     }
}

This approach may be desirable simply because it is more explicit, or it may be necessary in order to distinguish between two CacheManager beans present in the same container.

Notice also the keyGenerator method in the example above. This allows for customizing the strategy for cache key generation, per Spring's KeyGenerator SPI. Normally, @EnableCaching will configure Spring's SimpleKeyGenerator for this purpose, but when implementing CachingConfigurer, a key generator must be provided explicitly. Return null or new SimpleKeyGenerator() from this method if no customization is necessary.

CachingConfigurer offers additional customization options: it is recommended to extend from CachingConfigurerSupport that provides a default implementation for all methods which can be useful if you do not need to customize everything. See CachingConfigurer Javadoc for further details.

You may also find this thread useful: How to have multiple cache manager configuration in spring cache java

Edit:

So thankfully I have a project using caching and wrote this little bit:

@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);
        }
    };
}

Aside from seeing my established cache names pop out, I do note that context is outputting:

[]

class org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext public abstract ...transferobjects.CacheContainer ...service.LookupService.getCacheSelections()

Builder[public ...transferobjects.CacheContainer ...dao.LookupFacade.getCacheSelections()] caches=[cacheselections] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='true'

The context.getClass() output is of interest, given your Aspect question. That said I have a very similar logging/timing aspect in my own code that isn't causing any confusion for the rest of the caching. Try my resolver out and see if the output is instructive on what calls are being made against the cache code.

Edit #2:

The TLDR issue appears to be that we're expecting @Cacheable to work in places where it can't - mainly due to the fact that the framework hasn't fully brought itself up yet. You are using InitializingBean and I tried replacing that functionality with @PostConstruct which both fail.

Spring cache using @Cacheable during @PostConstruct does not work

I got your github code. I initially ran into the blocking issue you reported in the other thread but, in the interest of pursuing one thing at a time, I just commented out that blocking code and called service.cachedMethod() directly.

I ran your jar file with --trace and noted that the cacheManager was scanned and created, but it wasn't initially obvious to me when it was being invoked. So, no big deal, I just had the bean method throw a RuntimeException. Upon doing that I noted that both your NOT CACHED lines print prior to that invocation - another hint that things aren't set up as expected.

Finally, I added System.out.println(service.toString()); in your Controller's afterPropertiesSet() method and saw com.company.client.api.domain.CachedServiceImpl@2bbfb8b - your object, not a proxied object. Thus no caching.

So, in general, you'll need to rework how you're trying to enable this functionality.

Jon Sampson
  • 1,473
  • 1
  • 21
  • 31
  • Thanks for the answer. This is exactly what I do, without any difference. But I cannot make it work. – Hasan Can Saral Oct 01 '18 at 14:49
  • @HasanCanSaral So you've already modified your local `@Configuration` `SomeConf` to extend `CachingConfigurerSupport`? You can update your post with a current snapshot, but I suppose you can also try overriding [`cacheResolver()`](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/annotation/CachingConfigurerSupport.html#cacheResolver--) to try to debug the `CacheOperationInvocationContext` contents. – Jon Sampson Oct 01 '18 at 14:59
  • I have updated the question. 5th full day, with various tries, but still not working... – Hasan Can Saral Oct 01 '18 at 15:10
  • I am using Spring AOP and AspectJ annotations, and had an issue [here](https://stackoverflow.com/questions/52517497/around-aspect-in-the-same-package-only-works-with-dependson). Do you think it has anything to do with it? – Hasan Can Saral Oct 01 '18 at 15:51
  • @HasanCanSaral Interesting, but I'm not sure it's related. See my updated answer for a cacheResolver to try in debugging. – Jon Sampson Oct 01 '18 at 16:34
  • I’ll try it asap. But from my understanding, my methods are not intercepted with `@Cacheable`. I put logging level to trace ( as Spring logs caching in trace level) and no logs are printed when the method is called. I would expect a hit or a miss log. – Hasan Can Saral Oct 01 '18 at 16:40
  • @HasanCanSaral Then I expect you're correct - you'll see no output. Then do you feel that your `execution(* com.package.to.MyService.myMethod(..)` is interfering with the caching interceptor? Perhaps you could (temporarily) rewrite to `execution(* com.package.to.MyService.*(..)` to check the effect? Also look here: https://stackoverflow.com/questions/39047520/how-to-make-spring-cacheable-work-on-top-of-aspectj-aspect – Jon Sampson Oct 01 '18 at 16:50
  • Yes, no output. Simply `@Cacheable` doesn't intercept. Disabled all `@Aspect`s but nothing changed. Don't know how to debug this further. – Hasan Can Saral Oct 02 '18 at 10:57
  • 1
    I have replicated the issue and put it to GitHub. Please find it [here](https://github.com/hasancansaral/client-api-cache). – Hasan Can Saral Oct 02 '18 at 12:29
  • @HasanCanSaral See edit #2 for an update based on your git hub code. I don't have good news for you. xP – Jon Sampson Oct 02 '18 at 20:14
  • 1
    I have put the blocking code in `afterPropertiesSet` into a new thread and let the framework fully bring itself up. It’s now working. Thanks. – Hasan Can Saral Oct 02 '18 at 20:27
-1

Make sure you are setting your cache into cachemanager as below.

@EnableCaching
@Configuration
public SomeConf {   

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


}
Sundararaj Govindasamy
  • 8,180
  • 5
  • 44
  • 77
  • Thanks for the answer, but this doesn't help. Spring actually autoconfigures a `CacheManager` but even if I manually configure one as you showed, it is not populated. – Hasan Can Saral Sep 28 '18 at 12:56