【问题标题】:Spring Cache @Cacheable - not working while calling from another method of the same beanSpring Cache @Cacheable - 从同一个bean的另一个方法调用时不起作用
【发布时间】:2013-05-29 18:33:08
【问题描述】:

当从同一个 bean 的另一个方法调用缓存方法时,Spring 缓存不起作用。

这是一个清楚地解释我的问题的例子。

配置:

<cache:annotation-driven cache-manager="myCacheManager" />

<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="myCache" />
</bean>

<!-- Ehcache library setup -->
<bean id="myCache"
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
    <property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>

<cache name="employeeData" maxElementsInMemory="100"/>  

缓存服务:

@Named("aService")
public class AService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}

结果:

aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate); 
output: 
aService.getEmployeeEnrichedData(someDate); 
output: Cache is not being used

getEmployeeData 方法调用按预期在第二次调用中使用缓存 employeeData。但是当AService类(在getEmployeeEnrichedData中)调用getEmployeeData方法时,缓存没有被使用。

这是弹簧缓存的工作原理还是我遗漏了什么?

【问题讨论】:

标签: java spring caching ehcache


【解决方案1】:

我相信这就是它的工作原理。根据我记得读过的内容,生成的代理类会拦截所有请求并使用缓存值进行响应,但同一类中的“内部”调用不会获得缓存值。

来自https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

只有通过代理传入的外部方法调用 被截获。这意味着自调用实际上是一个方法 在目标对象内调用目标对象的另一个方法, 不会在运行时导致实际的缓存拦截,即使 调用的方法用@Cacheable 标记。

【讨论】:

  • 好吧,如果您也进行第二次调用 Cacheable,它只会有一次缓存未命中。也就是说,只有第一次调用 getEmployeeEnrichedData 会绕过缓存。第二次调用它会使用之前缓存的第一次调用 getEmployeeEnrichedData 的返回值。
  • @Bala 我也有同样的问题,我的解决方案是将 @Cacheable 移至 DAO :( 如果您有更好的解决方案,请告诉我,谢谢。
  • 您也可以编写服务,例如CacheService 并将您所有的缓存方法放入服务中。在您需要的地方自动装配服务并调用方法。对我有帮助。
  • 从 Spring 4.3 开始,这可以使用 @Resource 自自动装配来解决,参见示例 stackoverflow.com/a/48867068/907576
  • 另外,外部的@Cacheable 方法应该是public,它不适用于包私有方法。很难找到。
【解决方案2】:

从 Spring 4.3 开始,可以使用 self-autowiring 而不是 @Resource 注释来解决问题:

@Component
@CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {

    /**
     * 1. Self-autowired reference to proxified bean of this class.
     */
    @Resource
    private SphereClientFactory self;

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
        // 2. call cached method using self-bean
        return self.createSphereClient(tenantConfig.getSphereClientConfig());
    }

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
        return CtpClientConfigurationUtils.createSphereClient(clientConfig);
    }
}

【讨论】:

  • 4.3.17 下尝试过,但没有成功,对self 的调用不通过代理,缓存(仍然)被绕过。
  • 为我工作。缓存命中。截至目前,我使用最新的 spring 依赖项。
  • 我是唯一一个认为这会破坏模式、看起来像单例混合等等的人吗?
  • 我使用了 Spring Boot Starter 版本 - 2.1.0.RELEASE,我也遇到了同样的问题。这个特殊的解决方案就像一个魅力。
  • 会,不会产生循环依赖?
【解决方案3】:

下面的示例是我用来从同一个 bean 中访问代理的方法,它类似于 @mario-eis 的解决方案,但我发现它更具可读性(也许不是:-)。无论如何,我喜欢将@Cacheable 注解保留在服务级别:

@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {

@Inject
private SettingRepository settingRepository;

@Inject
private ApplicationContext applicationContext;

@Override
@Cacheable("settingsCache")
public String findValue(String name) {
    Setting setting = settingRepository.findOne(name);
    if(setting == null){
        return null;
    }
    return setting.getValue();
}

@Override
public Boolean findBoolean(String name) {
    String value = getSpringProxy().findValue(name);
    if (value == null) {
        return null;
    }
    return Boolean.valueOf(value);
}

/**
 * Use proxy to hit cache 
 */
private SettingService getSpringProxy() {
    return applicationContext.getBean(SettingService.class);
}
...

另见Starting new transaction in Spring bean

【讨论】:

  • 访问应用程序上下文,例如applicationContext.getBean(SettingService.class);,与依赖注入相反。我建议避免这种风格。
  • 是的,最好避免它,但我没有看到更好的解决方案。
【解决方案4】:

这是我为在同一个类中只使用少量方法调用的小型项目所做的。强烈建议使用代码内文档,因为它对同事来说可能看起来很奇怪。但它易于测试、简单、快速实现,并且为我省去了成熟的 AspectJ 工具。但是,对于更频繁的使用,我建议使用 AspectJ 解决方案。

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {

    private final AService _aService;

    @Autowired
    public AService(AService aService) {
        _aService = aService;
    }

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
        ..println("Cache is not being used");
        ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = _aService.getEmployeeData(date);
        ...
    }
}

【讨论】:

【解决方案5】:

如果您从同一个 bean 调用缓存方法,它将被视为私有方法,注释将被忽略

【讨论】:

    【解决方案6】:

    在我的例子中,我添加了变量:

    @Autowired
    private AService  aService;
    

    所以我使用aService 调用getEmployeeData 方法

    @Named("aService")
    public class AService {
    
    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }
    
    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = aService.getEmployeeData(date);
        ...
    }
    

    }

    在这种情况下它会使用缓存。

    【讨论】:

      【解决方案7】:

      是的,由于其他帖子中已经提到的原因,缓存不会发生。但是,我会通过将该方法放到它自己的类(在这种情况下为服务)来解决问题。这样,您的代码将更易于维护/测试和理解。

      @Service // or @Named("aService")
      public class AService {
      
          @Autowired //or how you inject your dependencies
          private EmployeeService employeeService;
       
          public List<EmployeeData> getEmployeeData(Date date){
                employeeService.getEmployeeData(date);
          }
      
          public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
              List<EmployeeData> employeeData = getEmployeeData(date);
              ...
          }
      
      }
      
      @Service // or @Named("employeeService")
      public class EmployeeService {
      
          @Cacheable("employeeData")
          public List<EmployeeData> getEmployeeData(Date date){
              println("This will be called only once for same date");
              ...
          }
      
      }
      

      【讨论】:

      • 就是这样。
      【解决方案8】:

      使用静态编织围绕您的 bean 创建代理。在这种情况下,即使是“内部”方法也可以正常工作

      【讨论】:

      • 什么是“静态编织”?谷歌并没有太大帮助。任何指针来理解这个概念?
      • @Bala - 例如在我们的项目中,我们使用&lt;iajc 编译器(来自 ant),它解决了可缓存类的所有必要方面。
      【解决方案9】:

      为此,我使用带有真实缓存的内部内部 bean (FactoryInternalCache):

      @Component
      public class CacheableClientFactoryImpl implements ClientFactory {
      
      private final FactoryInternalCache factoryInternalCache;
      
      @Autowired
      public CacheableClientFactoryImpl(@Nonnull FactoryInternalCache factoryInternalCache) {
          this.factoryInternalCache = factoryInternalCache;
      }
      
      /**
       * Returns cached client instance from cache.
       */
      @Override
      public Client createClient(@Nonnull AggregatedConfig aggregateConfig) {
          return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
      }
      
      /**
       * Returns cached client instance from cache.
       */
      @Override
      public Client createClient(@Nonnull ClientConfig clientConfig) {
          return factoryInternalCache.createClient(clientConfig);
      }
      
      /**
       * Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
       * this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
       * to real AOP proxified cacheable bean method {@link #createClient}.
       *
       * @see <a href="https://stackoverflow.com/questions/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
       * @see <a href="https://stackoverflow.com/questions/12115996/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class">Spring cache @Cacheable method ignored when called from within the same class</a>
       */
      @EnableCaching
      @CacheConfig(cacheNames = "ClientFactoryCache")
      static class FactoryInternalCache {
      
          @Cacheable(sync = true)
          public Client createClient(@Nonnull ClientConfig clientConfig) {
              return ClientCreationUtils.createClient(clientConfig);
          }
      }
      }
      

      【讨论】:

        【解决方案10】:

        我想分享一下我认为最简单的方法:

        • 自动装配控制器并使用它来调用方法,而不是使用类上下文this

        更新后的代码如下所示:

        @Controller
        public class TestController {
        
        
            @Autowired TestController self;
        
            @RequestMapping("/test")
            public String testView(){
                self.expensiveMethod();
                return "test";
            }
        
        
            @Cacheable("ones")
            public void expensiveMethod(){
               System.out.println("Cache is not being used");
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 2021-01-28
          • 2012-08-20
          • 2019-12-05
          • 2011-06-21
          • 2016-04-25
          • 2021-09-25
          • 2021-12-28
          • 2015-03-28
          • 1970-01-01
          相关资源
          最近更新 更多