【问题标题】:Spring cache @Cacheable method ignored when called from within the same class从同一个类中调用时忽略 Spring 缓存 @Cacheable 方法
【发布时间】:2012-08-20 09:46:27
【问题描述】:

我正在尝试从同一类中调用 @Cacheable 方法:

@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(findPerson(id));
   }
   return list;
}

并希望 findPersons 的结果也被缓存,但是 @Cacheable 注释被忽略,并且每次都执行 findPerson 方法。

我在这里做错了什么,还是故意的?

【问题讨论】:

标签: spring caching spring-cache


【解决方案1】:

这是因为在 Spring 中创建代理以处理缓存、事务相关功能的方式。这是 Spring 如何处理它的一个很好的参考 - Transactions, Caching and AOP: understanding proxy usage in Spring

简而言之,自调用绕过了动态代理,并且也绕过了作为动态代理逻辑一部分的缓存、事务等任何横切关注点。

解决方法是使用 AspectJ 编译时或加载时编织。

【讨论】:

  • 我们在事务注释方面遇到了同样的问题,并且完全按照 Biju 发布的方式进行。从那以后就没有问题了。
  • @Gonzalo 刚刚阅读了博客,您能否详细说明在编译时使用 AspectJ 来解决这个问题?谢谢!
  • 我有一个使用缓存注释和编译时方面的例子编织在 - github.com/bijukunjummen/cache-sample.git ,行为类似于你的,一种方法自调用 @Cacheable 方法
  • 另外,一个不好的解决方法是获取代理并通过它拨打电话 - ((PersonService)AopContext.currentProxy())).findPerson(id)
  • @David 我们只需要将它包含在我们的 applicationContext.xml 中: 使用相同的事务注释。虽然没有尝试使用 Cacheable 方法
【解决方案2】:

对于使用 Grails Spring Cache 插件的任何人,a workaround is described in the documentation。我在 grails 应用程序上遇到了这个问题,但不幸的是,接受的答案似乎对 Grails 不可用。恕我直言,解决方案很丑陋,但它有效。

示例代码很好地展示了它:

class ExampleService {
    def grailsApplication

    def nonCachedMethod() {
        grailsApplication.mainContext.exampleService.cachedMethod()
    }

    @Cacheable('cachedMethodCache')
    def cachedMethod() {
        // do some expensive stuff
    }
}

只需将 exampleService.cachedMethod() 替换为您自己的服务和方法。

【讨论】:

    【解决方案3】:

    这是我为在同一个类中只使用少量方法调用的小型项目所做的。强烈建议使用代码内文档,因为它对同事来说可能看起来很奇怪。但它易于测试、简单、快速实现,并且为我省去了成熟的 AspectJ 工具。但是,对于更多的使用,我建议使用 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(proxyMode = ScopedProxyMode.TARGET_CLASS) 后它会起作用?有什么用?
    • 因为要将 PersonDao 注入到 PersonDao 中,您需要在实例化之前拥有 PersonDao 的实例 :) 为了解决这个问题,创建、注入存根代理 (see doc),然后填充它被注入的同一个 PersonDao 实例。太棒了,PersonDao 可以保存自己的一个实例,包装到一个存根代理中。 Easy as that :) 不知道这是否足够清楚......:D
    • 谢谢,它仍然很好用!请注意,我必须将 @Cacheable... find Person() 之前的所有行替换为 @Autowired private PersonDao _personDao;
    猜你喜欢
    • 2013-05-29
    • 2019-05-08
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 2015-03-28
    • 2019-07-01
    • 2020-12-29
    • 1970-01-01
    相关资源
    最近更新 更多