英文:
@Cacheable not working, still calling the caching method
问题
I am trying to use @Cacheable to cache the roles regardless of the parameter. But the @Cacheable does not quite work and the method would get called twice.
CachingConfig:
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public CacheManager cacheManager(@Value("${caching.ttl.period}") long period,
@Value("${caching.ttl.unit}") String unit) {
return new ConcurrentMapCacheManager() {
@Override
public Cache createConcurrentMapCache(String name) {
return new ConcurrentMapCache(name, CacheBuilder.newBuilder()
.expireAfterWrite(period, TimeUnit.valueOf(unit)).build().asMap(), true);
}
};
}
}
RoleMappingService:
@Service
public class RoleMappingService {
private final AdminClient adminClient;
public RoleMappingService(AdminClient adminClient) {
this.adminClient = adminClient;
}
@Cacheable(value = "allRoles", key = "#root.method")
public List<Role> getAllRoles(String sessionToken) {
AdminSession adminSession = new AdminSession();
AdminSession.setSessionToken(sessionToken);
List<RoleGroup> allRoleGroups = this.adminClient.getAllRoleGroups(adminSession)
.orElse(Collections.emptyList());
List<Role> allRoles = allRoleGroups
.stream()
.map(RoleGroup::getRoles)
.flatMap(List::stream)
.collect(Collectors.toList());
return allRoles;
}
Test:
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RoleCachingTest {
private final JFixture fixture = new JFixture();
private AdminClient adminClient = mock(AdminClient.class);
@Test
public void allRolesShouldBeCached(){
RoleGroup mockRoleGroup = mock(RoleGroup.class);
Role mockRole = this.fixture.create(Role.class);
when(this.adminClient.getAllRoleGroups(any(AdminSession.class)))
.thenReturn(Optional.of(Arrays.asList(mockRoleGroup)));
when(mockRoleGroup.getRoles()).thenReturn(Arrays.asList(mockRole));
RoleMappingService sut = new RoleMappingService(adminClient);
List<Role> firstRes = sut.getAllRoles(
fixture.create(String.class));
List<Role> secondRes = sut.getAllRoles(
fixture.create(String.class));
assertEquals(firstRes.size(), secondRes.size());
assertEquals(firstRes.get(0).getId(), secondRes.get(0).getId());
assertEquals(firstRes.get(0).getRoleName(), secondRes.get(0).getRoleName());
// The getAllRoleGroups() should not be called on the second call
verify(this.adminClient, times(1)).getAllRoleGroups(any(AdminSession.class));
}
The adminClient.getAllRoleGroups() would always get called twice in this test, while I expect it would only get called once because of @Cacheable.
英文:
I am trying to use @Cacheable to cache the roles regardless of the parameter. But the @Cacheable does not quite work and the method would get called twice.
CachingConfig:
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public CacheManager cacheManager(@Value("${caching.ttl.period}") long period,
@Value("${caching.ttl.unit}") String unit) {
return new ConcurrentMapCacheManager() {
@Override
public Cache createConcurrentMapCache(String name) {
return new ConcurrentMapCache(name, CacheBuilder.newBuilder()
.expireAfterWrite(period, TimeUnit.valueOf(unit)).build().asMap(), true);
}
};
}
}
RoleMappingService:
@Service
public class RoleMappingService {
private final AdminClient adminClient;
public RoleMappingService(AdminClient adminClient) {
this.adminClient = adminClient;
}
@Cacheable(value = "allRoles", key = "#root.method")
public List<Role> getAllRoles(String sessionToken) {
AdminSession adminSession = new AdminSession();
AdminSession.setSessionToken(sessionToken);
List<RoleGroup> allRoleGroups = this.adminClient.getAllRoleGroups(adminSession)
.orElse(Collections.emptyList());
List<Role> allRoles = allRoleGroups
.stream()
.map(RoleGroup::getRoles)
.flatMap(List::stream)
.collect(Collectors.toList());
return allRoles;
}
Test:
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RoleCachingTest {
private final JFixture fixture = new JFixture();
private AdminClient adminClient = mock(AdminClient.class);
@Test
public void allRolesShouldBeCached(){
RoleGroup mockRoleGroup = mock(RoleGroup.class);
Role mockRole = this.fixture.create(Role.class);
when(this.adminClient.getAllRoleGroups(any(AdminSession.class)))
.thenReturn(Optional.of(Arrays.asList(mockRoleGroup)));
when(mockRoleGroup.getRoles()).thenReturn(Arrays.asList(mockRole));
RoleMappingService sut = new RoleMappingService(adminClient);
List<Role> firstRes = sut.getAllRoles(
fixture.create(String.class));
List<Role> secondRes = sut.getAllRoles(
fixture.create(String.class));
assertEquals(firstRes.size(), secondRes.size());
assertEquals(firstRes.get(0).getId(), secondRes.get(0).getId());
assertEquals(firstRes.get(0).getRoleName(), secondRes.get(0).getRoleName());
// The getAllRoleGroups() should not be called on the second call
verify(this.adminClient, times(1)).getAllRoleGroups(any(AdminSession.class));
}
The adminClient.getAllRoleGroups() would always get called twice in this test, while I expect it would only get called once because of @Cacheable.
The project structure:
project structure
答案1
得分: 1
我认为您的@Cacheable注解不起作用,因为您没有为类指定接口。这是因为Spring创建了用于缓存的代理。Spring在其文档中指定了以下内容。我认为您没有指定proxy-target-class,这意味着它将默认为false。如果它为false,它将使用基于JDK接口的代理。但在您的情况下,您的类即RollMappingService没有实现接口。创建一个带有方法getAllRoles的接口RollMappingService并实现它,将解决您的问题。
> 控制为使用@Cacheable或@CacheEvict注解标记的类创建什么类型的缓存代理。如果proxy-target-class属性设置为true,则创建基于类的代理。如果proxy-target-class为false或者省略了该属性,则创建标准的基于JDK接口的代理。(详见第9.6节,“代理机制”中对不同代理类型的详细考察。)
还要按以下方式修改您的测试类,为RoleMappingService创建Spring bean,并将AdminClient的模拟对象注入其中:
@Mock
private AdminClient mockedAdminClient;
@InjectMocks
@Autowired
private RoleMappingService roleMappingService;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(roleMappingService,
"adminClient",
mockedAdminClient);
}
英文:
I think your @Cacheable annotation is not working because you have not specified Interface for class. This is because of proxy created for caching by Spring. Spring has specified below in its documentation . I thing you have not specified proxy-target-class, it means it will be default to false. If it is false it will use JDK interface based proxies. But in your case you class i.e. RollMappingService is not implementing interface. Create interface RollMappingService with method getAllRoles and implement it, will sole your problem.
> Controls what type of caching proxies are created for classes annotated with the @Cacheable or @CacheEvict annotations. If the proxy-target-class attribute is set to true, then class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, then standard JDK interface-based proxies are created. (See Section 9.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)
Also modify your test class to create Spring bean for RoleMappingService in following ways and inject mock of AdminClient into it
@Mock
private AdminClient mockedAdminClient;
@InjectMocks
@Autowired
private RoleMappingService roleMappingService
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(roleMappingService,
"adminClient",
mockedAdminClient);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论