9

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()"

Stephane Nicoll
  • 31,977
  • 9
  • 97
  • 89
TimothyBrake
  • 551
  • 6
  • 9
  • 2
    The `@PostConstruct` gives no guarantees that proxies have already been created (that is the same reason why `@Transactional` doesn't work for `@PostConstruct` methods. The `@PostConstruct` method is called right after construction and after injection of the dependencies but almost always before the point that proxies have been created. Why do you need it in a `@PostConstruct` method? In general an `ApplicationListener` is beter of implementing the `SmartInitializingSingleton` interface instead of `@PostConstruct`. – M. Deinum Feb 05 '15 at 20:23
  • Thank you for your reply. We use the postconstruct to initialize a bean with values taken from another service (which has @Cacheable on its methods) We expected that these values would be cached even when using a postconstruct. If this is not the case then a java doc would benefit the framework as other developers might not be aware of this as well. Will try the SmartInitializingSingleton instead. Thanks! – TimothyBrake Feb 06 '15 at 22:23
  • This is explained in [the reference guide](http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-lifecycle-default-init-destroy-methods) read the last paragraph of that section. – M. Deinum Feb 08 '15 at 14:40

4 Answers4

7

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.

Stephane Nicoll
  • 31,977
  • 9
  • 97
  • 89
5

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
    }
}
maximede
  • 1,763
  • 14
  • 24
0

Autowire ApplicationContext and invoke method call using :

applicationContext.getBean(RBService.class).getRawBundle(bundleName, DEFAULT_REQUEST_LANG);

where getRawBundle is Cacheable method.

Reegan Miranda
  • 2,879
  • 6
  • 43
  • 55
0

The @PostConstruct gives no guarantees that proxies have already been created

enter image description here

when use @Cacheable during @PostConstruct, this.initialized is false, and Will not execute operation cache related code.

shengbin_xu
  • 128
  • 3
  • 14