@Cacheable 测试方法

发布于 2025-01-19 15:10:23 字数 2017 浏览 2 评论 0原文

我在课堂内有一个@cach的方法。 我尝试在第一次呼叫该方法后创建该缓存,然后,第二个调用不应进入方法 getcacheleads

@Service
public class LeadService {

    @Autowired
    private LeadRepository leadRepository;

    @Autowired
    public LeadService(LeadRepository leadRepository) {
        this.leadRepository = leadRepository;
    }

    public void calculateLead(Lead leadBean) {
        Lead lead = this.getCacheLeads(leadBean);
    }

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        Lead result = leadRepository.findByLeadId(leadBean.getLeadId());
        ***logic to transform de Lead object***
        return result;
    }
}

但是,在测试中,从未使用过缓存,将其称为两次使用相同的参数( serviceSiscalled ),以确保将其调用两次以检查它。

@ExtendWith(SpringExtension.class)
public class LeadServiceTest {

    private LeadService leadService;
    
    @Mock
    private LeadRepository leadRepository;
    
    @Autowired 
    CacheManager cacheManager;
    
    @BeforeEach
    public void setUp(){
        leadService = new LeadService(leadRepository);
    }

    @Configuration
    @EnableCaching
    static class Config {
        @Bean
        CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("leads"); 
        }
    }
    
    @Test
    public void testLead(){
        givenData();
        serviceIsCalled();
        serviceIsCalled();
        checkDataArray();
    }
    
    private void givenData() {
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        
        Mockito.when(leadRepository.findByLeadId(any()))
        .thenReturn(lead);
    }
    
    private void serviceIsCalled(){
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        leadService.calculateLead(lead);
    }
    
    private void checkDataArray(){
        verify(leadRepository, times(1)).findByLeadId(anyString());
    }
}

为什么叫2次?

I have a @Cacheable method inside a class.
I try to create that cache after a first call to that method, then, the second call should't go inside the method getCacheLeads.

@Service
public class LeadService {

    @Autowired
    private LeadRepository leadRepository;

    @Autowired
    public LeadService(LeadRepository leadRepository) {
        this.leadRepository = leadRepository;
    }

    public void calculateLead(Lead leadBean) {
        Lead lead = this.getCacheLeads(leadBean);
    }

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        Lead result = leadRepository.findByLeadId(leadBean.getLeadId());
        ***logic to transform de Lead object***
        return result;
    }
}

But during testing that cache is never used, calling it twice with same parameter (serviceIsCalled) to ensure it is called twice to check it.

@ExtendWith(SpringExtension.class)
public class LeadServiceTest {

    private LeadService leadService;
    
    @Mock
    private LeadRepository leadRepository;
    
    @Autowired 
    CacheManager cacheManager;
    
    @BeforeEach
    public void setUp(){
        leadService = new LeadService(leadRepository);
    }

    @Configuration
    @EnableCaching
    static class Config {
        @Bean
        CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("leads"); 
        }
    }
    
    @Test
    public void testLead(){
        givenData();
        serviceIsCalled();
        serviceIsCalled();
        checkDataArray();
    }
    
    private void givenData() {
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        
        Mockito.when(leadRepository.findByLeadId(any()))
        .thenReturn(lead);
    }
    
    private void serviceIsCalled(){
        Lead lead = new Lead();
        lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
        leadService.calculateLead(lead);
    }
    
    private void checkDataArray(){
        verify(leadRepository, times(1)).findByLeadId(anyString());
    }
}

Why is it called 2 times?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

金兰素衣 2025-01-26 15:10:23

这里发生了很多事情,有人看到这个并回答你的问题肯定需要阅读字里行间的内容。

首先,您的 Spring 配置甚至不正确。您使用 ConcurrentMapCacheManager “静态”声明 Spring 应用程序(和测试)使用的所有缓存的名称 构造函数接受缓存名称数组作为参数。

注意:通过名称显式标识的缓存,并且只有这些缓存在运行时可用。

@Bean
CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("LEAD_DATA"); 
}

在这种情况下,您的第一个也是唯一的缓存称为“LEAD_DATA”。

注意:仅无参数构造函数 允许在运行时按名称动态创建缓存。

但是,然后,在您的 @Service LeadService 类中, @Cacheable getCacheLeads(:Lead) 方法中,您声明缓存用作“线索”。

@Service
public class LeadService {

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        // ...
    }
}

这种未命中配置实际上会导致运行时出现类似于以下内容的异常:

java.lang.IllegalArgumentException: Cannot find cache named 'leads' for Builder[public io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead)] caches=[leads] | key='#lead.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

    at org.springframework.cache.interceptor.AbstractCacheResolver.resolveCaches(AbstractCacheResolver.java:92)
    at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:252)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:724)
    at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:265)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:615)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
    at io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$EnhancerBySpringCGLIB$86664246.load(<generated>)
...
..
.

此外,我没有看到 LeadsService bean 调用 @Cacheable 的“外部”任何内容, getCacheLeads(..) 方法。在您的测试中,您正在调用:

leadService.calculateLead(lead);

如下所示:

private void serviceIsCalled(){
    Lead lead = new Lead();
    lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
    leadService.calculateLead(lead);
}

如果 calculateLead(:Lead) LeadService 方法正在调用 @CacheablegetCacheLeads(:Lead) LeadService 方法(内部),那么这不会导致缓存功能启动,因为您已经在 Spring 设置的 AOP 代理“后面”为您的 LeadService bean“启用”缓存行为。

请参阅 Spring 框架 AOP 文档

注意: Spring 的缓存抽象,如Spring 的事务管理,是建立在Spring AOP 基础设施,就像 Spring 中的许多其他东西一样。

在您的情况下,这意味着:

Test -> <PROXY> -> LeadService.calculateLead(:Lead) -> LeadService.getCacheLeads(:Lead)

但是,在 LeadSevice.calculateLead(:Lead)LeadService.getCacheLeads(:Lead) 之间,不涉及代理,因此 Spring 的缓存行为不会被应用。

仅...

Test (or some other bean) -> <PROXY> -> LeadService.getCacheLeads(:Lead)

将导致用被调用的缓存拦截器装饰的 AOP 代理并应用缓存行为。

您可以看到,如果正确配置和使用,您的用例将正常工作,如我的 示例测试类,以您的域为模型。

查找解释为什么您的配置在您的情况下会失败的注释。

You have a lot of things going on here, and someone looking at this and answering your question would definitely have to read between the lines.

First, your Spring configuration is not even correct. You are declaring the names of all the caches used by your Spring application (and tests) "statically" with the use of the ConcurrentMapCacheManager constructor accepting an array of cache names as the argument.

NOTE: Caches identified explicitly by name, and only these caches, are available at runtime.

@Bean
CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("LEAD_DATA"); 
}

In this case, your 1 and only cache is called "LEAD_DATA".

NOTE: Only the no arg constructor in `ConcurrentMapCacheManager allows dynamically created caches by name at runtime.

But then, in your @Service LeadService class, @Cacheable getCacheLeads(:Lead) method, you declare the cache to use as "leads".

@Service
public class LeadService {

    @Cacheable(cacheNames="leads", key="#leadBean.leadId")
    public Lead getCacheLeads(Lead leadBean){
        // ...
    }
}

This miss configuration will actually lead to an Exception at runtime similar to the following:

java.lang.IllegalArgumentException: Cannot find cache named 'leads' for Builder[public io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead)] caches=[leads] | key='#lead.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

    at org.springframework.cache.interceptor.AbstractCacheResolver.resolveCaches(AbstractCacheResolver.java:92)
    at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:252)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:724)
    at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:265)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:615)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
    at io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$EnhancerBySpringCGLIB$86664246.load(<generated>)
...
..
.

Additionally, I don't see anything "outside" of the LeadsService bean calling the @Cacheable, getCacheLeads(..) method. Inside your test, you are calling:

leadService.calculateLead(lead);

As follows:

private void serviceIsCalled(){
    Lead lead = new Lead();
    lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
    leadService.calculateLead(lead);
}

If the calculateLead(:Lead) LeadService method is calling the @Cacheable, getCacheLeads(:Lead) LeadService method (internally), then that is not going to cause the caching functionality to kick in since you are already "behind" the AOP Proxy setup by Spring to "enable" caching behavior for your LeadService bean.

See the Spring Framework AOP documentation on this matter.

NOTE: Spring's Cache Abstraction, like the Spring's Transaction Management, is built on the Spring AOP infrastructure, as are many other things in Spring.

In your case this means:

Test -> <PROXY> -> LeadService.calculateLead(:Lead) -> LeadService.getCacheLeads(:Lead)

However, between LeadSevice.calculateLead(:Lead) and LeadService.getCacheLeads(:Lead), NO PROXY is involved, therefore Spring's caching behavior will not be applied.

Only...

Test (or some other bean) -> <PROXY> -> LeadService.getCacheLeads(:Lead)

Will result in the AOP Proxy decorated with the Caching Interceptors being invoked and the caching behavior applied.

You can see that your use case will work correctly when configured and used correctly as demonstrated in my example test class, modeled after your domain.

Look for the comments that explain why your configuration will fail in your case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文