У меня есть метод @Cacheable внутри класса. Я пытаюсь создать этот кеш после первого вызова этого метода, тогда второй вызов не должен проходить внутри метода 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;
}
}
Но во время тестирования этот кеш никогда не используется, вызывая его дважды с одним и тем же параметром (serviceIsCalled), чтобы убедиться, что он вызывается дважды для проверки.
@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 раза?
У вас здесь происходит много всего, и кто-то, кто смотрит на это и отвечает на ваш вопрос, определенно должен читать между строк.
Во-первых, ваша конфигурация Spring даже неверна. Вы объявляете имена всех кешей, используемых вашим приложением Spring (и тестами) «статически» с использованием ConcurrentMapCacheManagerконструктор, принимающего массив имен кешей в качестве аргумента.
NOTE: Caches identified explicitly by name, and only these caches, are available at runtime.
@Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("LEAD_DATA");
}
В этом случае ваш первый и единственный кэш называется «LEAD_DATA».
NOTE: Only the no arg constructor in `ConcurrentMapCacheManager allows dynamically created caches by name at runtime.
Но затем в вашем @ServiceLeadService классе, @CacheablegetCacheLeads(: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 вызывает метод @Cacheable, getCacheLeads(:Lead)LeadService (внутренне), то это не приведет к срабатыванию функции кэширования, поскольку вы уже «за» настройкой прокси-сервера AOP с помощью Spring, чтобы «включить» поведение кэширования для твоя LeadService фасоль.
См. Spring Framework AOP документация по этому вопросу.
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.
В вашем случае это означает:
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 украшен перехватчиками кэширования и применяется поведение кэширования.
Вы можете видеть, что ваш вариант использования будет работать правильно при правильной настройке и использовании, как показано в моем пример тестового класса, смоделированном по образцу вашего домена.
Ищите комментарии, которые объясняют, почему ваша конфигурация не будет работать в вашем случае.
Ну и конечно ищу пример, который отлично работает.
Рад, что помогло. Удачи!
Да, извините, я не позаботился о том, чтобы поставить чистый код, я изменил его, и теперь он работает. Спасибо, я просматриваю ваш комментарий -> Если метод calculateLead(:Lead) LeadService вызывает метод @Cacheable, getCacheLeads(:Lead) LeadService (внутренне), то это не приведет к срабатыванию функции кэширования поскольку вы уже «отстаете» от настройки AOP Proxy с помощью Spring, чтобы «включить» поведение кэширования для вашего bean-компонента LeadService.