当前位置: 首页 > 知识库问答 >
问题:

为什么spring对@Cacheable注释方法的一次调用执行@Cacheable keyGenerator两次

曹原
2023-03-14

为什么spring对一个注释为@Cacheable的方法的一次调用执行我的定制@Cacheable密钥生成器两次,为什么不只执行一次。

我的KeyGenerator实现

package com.example.demo;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Custom key generator
 */
@Component(value = "customerKeyGenerator")
public class CustomerKeyGen implements KeyGenerator
{
    @Override
    public Object generate(Object target, Method method, Object... params)
    {
        System.out.println("Generating a key");
        ArrayList<String> customerNames = (ArrayList<String>) params[0];
        return customerNames.hashCode();
    }
}

我的方法用@Cacheable和自定义keyGenerator注释

package com.example.demo;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

@Component
public class CustomerService {

    @Cacheable(value = "customersCache", keyGenerator = "customerKeyGenerator")
    public int getCountOfCustomers(ArrayList<String> customerNames) {
        return customerNames.size();
    }
}

spring Rest控制器,它调用用@cacheable注释的方法

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;

@Controller
public class CustomerController {
    @Autowired
    CustomerService customerService;

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World!";
    }

    @RequestMapping("/countCustomers")
    @ResponseBody
    String countCustomers() {
        ArrayList<String> customerNames = new ArrayList<>();
        customerNames.add("john");
        customerNames.add("bill");

        return "countOfCustomers=" + String.valueOf(customerService.getCountOfCustomers(customerNames));
    }
}

当我用自定义的keyGenerator对用@Cacheable注释的方法进行一次调用时,我在System.out.println的日志和dubugger中看到2次执行(“生成密钥”);

Curl以触发方法调用调用

curl http://127.0.0.1:8080/countCustomers
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18  100    18    0     0     18      0  0:00:01 --:--:--  0:00:01    
76countOfCustomers=2

Log I在应用程序属性中有以下设置来启用对缓存的跟踪

logging.level.org.springframework.cache=trace

...
2018-08-27 11:56:53.753  INFO 18756 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-08-27 11:56:53.757  INFO 18756 --- [           main] c.example.demo.TestCacheableApplication  : Started TestCacheableApplication in 3.543 seconds (JVM running for 5.137)
2018-08-27 11:56:58.411  INFO 18756 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-08-27 11:56:58.411  INFO 18756 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-08-27 11:56:58.446  INFO 18756 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 35 ms
Generating a key
2018-08-27 11:56:58.480 TRACE 18756 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '104328221' for operation Builder[public int com.example.demo.CustomerService.getCountOfCustomers(java.util.ArrayList)] caches=[customersCache] | key='' | keyGenerator='customerKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
2018-08-27 11:56:58.480 TRACE 18756 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor   : No cache entry for key '104328221' in cache(s) [customersCache]
Generating a key
2018-08-27 11:56:58.480 TRACE 18756 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '104328221' for operation Builder[public int com.example.demo.CustomerService.getCountOfCustomers(java.util.ArrayList)] caches=[customersCache] | key='' | keyGenerator='customerKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

从概念上讲,我认为spring只需要运行一次keyGenerator,首先使用它查找缓存,如果没有找到,当方法完成时,使用相同的键将其放入缓存。所以我不明白为什么我看到这个跑了两次。

我的问题是:

  1. 我对它的工作原理和原因感到困惑
  2. 日志记录和调试变得混乱
  3. 潜在的性能影响,尽管密钥生成实现应该很便宜,但为什么要多次执行。

共有1个答案

卜高超
2023-03-14

我想我知道为什么了,密钥生成一次是为了在缓存中查找,一次是为了放入缓存。不知道为什么会这样,但似乎是正在发生的事情。

 类似资料:
  • 附注。当我试图从其他地方访问此方法时,出现以下错误。 Org.SpringFramework.Expression.Spel.SpelEvaluationException:EL1008E:(pos 0):在“org.SpringFramework.Cache.Interceptor.CacheExpressionRootObject”类型的对象上找不到字段或属性“Cache Key”

  • 问题内容: 最近升级到Spark 2.0,尝试从JSON字符串创建简单的数据集时遇到一些奇怪的行为。这是一个简单的测试用例: 并输出: 即使我仅执行一项操作,“ map”功能似乎仍被执行两次。我以为Spark会懒惰地建立一个执行计划,然后在需要时执行它,但这似乎使得为了将数据读取为JSON并对其进行任何处理,该计划必须至少执行两次。 在这种简单的情况下,这并不重要,但是当map函数长时间运行时,这

  • 我对事务REST控制器中的一些异常处理感到困惑。 我有一个管理服务,看起来像: 我有一个REST控制器,看起来像: 它使用@Transactional(readOnly = true)注释。 如果存在此注释并且没有找到搜索的项目,则它会给出以下异常: 如果删除注释,则一切正常,响应包含搜索的项。 我假设当没有找到搜索的条目时会抛出一个异常,触发事务回滚消息。 以下是异常处理程序: 有没有办法在日志

  • 我使用以下方法定义了一个简单的实现: 当我在没有任何Spring cloud依赖项的情况下运行时,我会得到以下日志: 它还两次提到“在...开始应用程序”这句话。 这种行为有什么特别的原因吗?我正在尝试编写一个,它依赖于已经创建的特定bean。但是,如果我添加spring cloud,当它第一次到达方法时,上下文尚未创建,因此当前抛出一个错误。

  • 通用方法 内部控制器 问题 我们如何在Generic方法中使用@Cacheable("abc")注释,并使用通用DAO的Spring mvc hibernate按需销毁缓存 根据SpringDoc中的示例,它指定了简单方法上的注释! 我实际上要求,当Id传递给泛型方法时,它应该首先在缓存中查找,我也应该按需销毁缓存!

  • 我试图在一个方法上调用@CacheEvict和@Cacheable注释。 在调用@CacheEvict时,我想删除特定键的缓存数据,然后再次缓存方法响应的新鲜数据。但它没有缓存新鲜数据?。而且它也没有给出任何错误?。