Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring cache using @Cacheable during @PostConstruct does not work

related to the commit in spring framework https://github.com/spring-projects/spring-framework/commit/5aefcc802ef05abc51bbfbeb4a78b3032ff9eee3

the initialisation is set to a later stage from afterPropertiesSet() to afterSingletonsInstantiated()

In short: This prevents the caching to work when using it in a @PostConstruct use case.

Longer version: This prevents the use case where you would

  1. create serviceB with @Cacheable on a methodB

  2. create serviceA with @PostConstruct calling serviceB.methodB

    @Component 
    public class ServiceA{
    
    @Autowired
    private ServiceB serviceB;
    
    @PostConstruct
    public void init() {
        List<String> list = serviceB.loadSomething();
    }
    

This results in org.springframework.cache.interceptor.CacheAspectSupport not being initialised now and thus not caching the result.

protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
   // check whether aspect is enabled
   // to cope with cases where the AJ is pulled in automatically
   if (this.initialized) {
//>>>>>>>>>>>> NOT Being called
      Class<?> targetClass = getTargetClass(target);
      Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
      if (!CollectionUtils.isEmpty(operations)) {
         return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass));
      }
   }
//>>>>>>>>>>>> Being called
   return invoker.invoke();
}

My workaround is to manually call the initialisation method:

@Configuration
public class SomeConfigClass{

  @Inject
  private CacheInterceptor cacheInterceptor;

  @PostConstruct
  public void init() {
    cacheInterceptor.afterSingletonsInstantiated();
  }

This of course fixes my issue but does it have side effects other that just being called 2 times (1 manual and 1 by the framework as intended)

My question is: "Is this a safe workaround to do as the initial commiter seemed to have an issue with just using the afterPropertiesSet()"

like image 819
TimothyBrake Avatar asked Feb 05 '15 17:02

TimothyBrake


2 Answers

As Marten said already, you are not supposed to use any of those services in the PostConstruct phase because you have no guarantee that the proxy interceptor has fully started at this point.

Your best shot at pre-loading your cache is to listen for ContextRefreshedEvent (more support coming in 4.2) and do the work there. That being said, I understand that it may not be clear that such usage is forbidden so I've created SPR-12700 to improve the documentation. I am not sure what javadoc you were referring to.

To answer your question: no it's not a safe workaround. What you were using before worked by "side-effect" (i.e. it wasn't supposed to work, if your bean was initialized before the CacheInterceptor you would have the same problem with an older version of the framework). Don't call such low-level infrastructure in your own code.

like image 90
Stephane Nicoll Avatar answered Oct 19 '22 16:10

Stephane Nicoll


Just had the exact same problem as OP and listening to ContextRefreshedEvent was causing my initialization method to be called twice. Listening to ApplicationReadyEvent worked best for me. Here is the code I used

@Component
public class MyInitializer implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        //doing things
    }
}
like image 28
maximede Avatar answered Oct 19 '22 16:10

maximede