Spring 3.1 @Cacheable - 方法仍然被执行

30
我正在尝试实现Spring 3.1缓存,如此处此处所述,但似乎并没有起作用:即使被标记为@cacheable,我的方法每次都会运行。我做错了什么?
我已将其移入一个junit测试用例,并使用自己的配置文件隔离它与我的应用程序的其余部分,但问题仍然存在。以下是相关文件:
Spring-test-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:cache="http://www.springframework.org/schema/cache"
   xmlns:p="http://www.springframework.org/schema/p"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
      p:config-location="classpath:ehcache.xml"/>
</beans>

ehcache.xml

<ehcache>
<diskStore path="java.io.tmpdir"/>
<cache name="cache"
       maxElementsInMemory="100"
       eternal="false"
       timeToIdleSeconds="120"
       timeToLiveSeconds="120"
       overflowToDisk="true"
       maxElementsOnDisk="10000000"
       diskPersistent="false"
       diskExpiryThreadIntervalSeconds="120"
       memoryStoreEvictionPolicy="LRU"/>

</ehcache>

MyTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring-test-servlet.xml"})
@Component
public class MyTest extends TestCase {

    @Test
    public void testCache1(){
        for(int i = 0; i < 5; i++){
            System.out.println("Calling someMethod...");
            System.out.println(someMethod(0));
        }
    }

    @Cacheable("testmethod")
    private int someMethod(int val){
        System.out.println("Not from cache");
        return 5;
    }
}

相关的POM条目: (spring-version = 3.1.1.RELEASE)

    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>

当我运行测试时,Spring会输出一些调试信息,看起来我的缓存已经初始化成功,没有错误。
DEBUG: config.ConfigurationHelper - No CacheManagerEventListenerFactory class specified. Skipping...
DEBUG: ehcache.Cache - No BootstrapCacheLoaderFactory class specified. Skipping...
DEBUG: ehcache.Cache - CacheWriter factory not configured. Skipping...
DEBUG: config.ConfigurationHelper - No CacheExceptionHandlerFactory class specified. Skipping...
DEBUG: store.MemoryStore - Initialized net.sf.ehcache.store.MemoryStore for cache
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.data
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
DEBUG: disk.DiskStorageFactory - Matching data file missing (or empty) for index file. Deleting index file /var/folders/qg/xwdvsg6x3mx_z_rcfvq7lc0m0000gn/T/cache.index
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
DEBUG: ehcache.Cache - Initialised cache: cache
DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'cache'.
DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'cache'.

但是调试输出显示在someMethod方法调用之间没有缓存检查,而且来自someMethod内部的打印语句每次都会打印。我是否漏掉了什么?
3个回答

84

来自http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/cache.html

在代理模式下(默认情况下),只有通过代理进行的外部方法调用才会被拦截。这意味着自我调用实际上是目标对象内的一个方法调用另一个目标对象的方法,即使被调用的方法标有@Cacheable,在运行时也不会导致实际缓存- 在这种情况下考虑使用aspectj模式。

以及

方法可见性和@ Cacheable / @ CachePut / @ CacheEvict

使用代理时,应仅将@Cache注释应用于具有公共可见性的方法。

  1. 您在同一目标对象中自我调用了someMethod
  2. 你的@Cacheable方法不是public。

除此之外,您需要提取到一个接口,以便Spring可以动态地编写包装器来包装您的类。 - RockMeetHardplace

2
你需要定义一个缓存,以匹配你在注释中引用的名称 ("testmethod")。在 ehcache.xml 中为该缓存创建一个条目。

这也是我的原始代码存在的问题,一旦我以尝试将其放入缓存的方式调用该方法(请参见已接受的解决方案),就会变得明显。 - Andy Miller

1
除了Lee Chee Kiam的解决方案外:对于仅使用绕过(未注释)方法调用的小型项目,我的解决方案是将DAO作为代理注入自身,并使用该代理调用自己的方法,而不是简单的方法调用。因此,@Cacheable会被考虑,而不需要进行复杂的仪器化。
强烈建议在代码文档中进行说明,因为这可能会让同事感到奇怪。但它易于测试,简单快捷,避免了完整的AspectJ仪器化。然而,对于更重度使用,我也建议采用Lee Chee Kiam的AspectJ解决方案。
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Cacheable(value = "defaultCache", key = "#id")
    public Person findPerson(int id) {
        return getSession().getPerson(id);
    }

    public List<Person> findPersons(int[] ids) {
        List<Person> list = new ArrayList<Person>();
        for (int id : ids) {
            list.add(_personDao.findPerson(id));
        }
        return list;
    }
}

只有@Scope注解可以绕过代理模式吗?我在这里有一个类似的问题http://stackoverflow.com/questions/36486620/correct-key-annotation-for-caching-on-items-in-a-list-rather-then-the-whole-list - owen gerig
对不起,我觉得我没有正确理解问题。我没有看到与您的帖子的联系。 - Mario Eis
在这个上下文中,“bypassing”的意思是,该方法未使用@Cachable进行注释,但它在内部使用了注入代理的缓存方法,而该代理又是“this”本身的代理。有点奇怪 ;) 将对象作为代理注入到自身中,以避免复杂的演奏。 - Mario Eis
这是一个有趣的黑客技巧,但我对这样一个对象如何构建感到困惑。 - Ryan
通过ScopedProxyMode.TARGET_CLASS的魔法构建。Spring创建了一个代理,直到被使用时才解析。当该类访问代理时,代理被解析为对底层对象的引用。但是Spring也可以用缓存包装器包装代理使其可缓存。 - Ryan

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