在@PostConstruct期间使用@Cacheable的Spring缓存无法工作

9

与Spring框架的提交相关:https://github.com/spring-projects/spring-framework/commit/5aefcc802ef05abc51bbfbeb4a78b3032ff9eee3

初始化从afterPropertiesSet()更改为afterSingletonsInstantiated(),即延后了初始化阶段。

简而言之: 这样做可以避免在@PostConstruct使用缓存时出现问题。

详细说明: 这样做可以避免以下情况:

  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();
    }
    
这会导致 org.springframework.cache.interceptor.CacheAspectSupport 现在没有被初始化,因此不会缓存结果。
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();
}

我的解决方法是手动调用初始化方法:
@Configuration
public class SomeConfigClass{

  @Inject
  private CacheInterceptor cacheInterceptor;

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

当然,这解决了我的问题,但除了被调用两次(手动调用和框架预期的一次)之外,它是否有其他副作用?
我的问题是: “这是一个安全的解决方法吗?因为最初的提交者似乎对仅使用afterPropertiesSet()存在问题。”

2
@PostConstruct 不保证代理已经被创建(这也是为什么 @Transactional 不能用于 @PostConstruct 方法的原因)。@PostConstruct 方法在构造函数和依赖注入之后立即调用,但几乎总是在代理被创建之前。你为什么需要在 @PostConstruct 方法中使用它?通常情况下,实现 SmartInitializingSingleton 接口比使用 @PostConstruct 更好,或者使用 ApplicationListener<ContextRefreshedEvent> - M. Deinum
谢谢您的回复。我们使用postconstruct来初始化一个bean,并从另一个带有@Cacheable注释的服务中获取值。我们期望即使使用postconstruct,这些值也会被缓存。如果不是这种情况,那么Java文档将有益于框架,因为其他开发人员可能也不知道这一点。我们将尝试使用SmartInitializingSingleton。谢谢! - TimothyBrake
这在参考指南中有详细说明,请阅读该部分的最后一段。 - M. Deinum
4个回答

7
正如Marten所说,您不应该在PostConstruct阶段使用这些服务,因为此时无法保证代理拦截器已完全启动。
最好的方法是监听ContextRefreshedEvent(4.2版中会有更多支持),并在那里完成工作来预加载缓存。话虽如此,我明白这样的用法可能不被允许,因此我创建了SPR-12700以改进文档。我不确定您所引用的javadoc是什么。
回答您的问题:不,这不是一个安全的解决方案。您之前使用的方法是通过"副作用"实现的(即它不应该起作用,如果您的bean在CacheInterceptor之前被初始化,则会遇到相同的问题,而旧版本的框架也会出现这个问题)。不要在自己的代码中调用这样低级的基础设施。

5

我和楼主遇到了完全相同的问题,监听 ContextRefreshedEvent 会导致我的初始化方法被调用两次。监听 ApplicationReadyEvent 对我来说效果最好。 以下是我使用的代码

@Component
public class MyInitializer implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        //doing things
    }
}

0
@PostConstruct不能保证代理已经被创建

enter image description here

在使用@Cacheable注解的时候,在@PostConstruct方法中,this.initialized为false,不会执行与缓存相关的代码操作。

0
自动装配ApplicationContext并使用以下方式调用方法:
applicationContext.getBean(RBService.class).getRawBundle(bundleName, DEFAULT_REQUEST_LANG);

getRawBundle 是一个 Cacheable 方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接