152

从同一 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。但是当在类getEmployeeData中调用该方法时AService(in getEmployeeEnrichedData),缓存没有被使用。

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

4

10 回答 10

221

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

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

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

于 2013-06-03T14:58:13.603 回答
57

从 Spring 4.3 开始,可以使用注释上的自自动装配来解决该问题:@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);
    }
}
于 2018-02-19T13:16:22.780 回答
21

下面的示例是我用来从同一个 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);
}
...

另请参阅在 Spring bean 中启动新事务

于 2016-10-31T11:08:37.417 回答
13

这是我为在同一个类中仅少量使用方法调用的小型项目所做的。强烈建议使用代码内文档,因为它对同事来说可能看起来很奇怪。但它易于测试、简单、快速实现,并且为我省去了成熟的 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);
        ...
    }
}
于 2015-12-04T15:03:46.153 回答
9

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

于 2020-09-22T06:38:06.447 回答
4

在我的情况下,我添加了变量:

@Autowired
private AService  aService;

所以我getEmployeeData通过使用aService

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

}

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

于 2018-10-29T15:41:32.737 回答
3

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

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

}
于 2021-03-22T15:19:44.320 回答
1

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

于 2013-06-03T15:15:25.727 回答
1

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

@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);
    }
}
}
于 2018-01-09T13:00:12.060 回答
0

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

  • 自动装配控制器并使用它来调用方法,而不是使用类上下文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");
    }

}
于 2022-01-21T11:03:48.237 回答