@Cacheable 不起作用,仍在调用缓存方法
@Cacheable not working, still calling the caching method
我正在尝试使用@Cacheable 来缓存角色,而不考虑参数。但是 @Cacheable 不太有效,该方法会被调用两次。
缓存配置:
@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);
}
};
}
}
角色映射服务:
@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;
}
测试:
@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));
}
adminClient.getAllRoleGroups() 在此测试中总是会被调用两次,而我预计它只会被调用一次,因为 @Cacheable。
项目结构:
project structure
我认为您的@Cacheable 注释不起作用,因为您没有为class 指定接口。这是因为 Spring 为缓存创建的代理。 Spring 在其文档中指定如下。我知道你没有指定 proxy-target-class,这意味着它将默认为 false。如果为假,它将使用 JDK 基于接口的代理。但是在您的情况下,您 class 即 RollMappingService 没有实现接口。使用方法 getAllRoles 创建接口 RollMappingService 并实现它,将解决您的问题。
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.)
同时修改您的测试 class 以通过以下方式为 RoleMappingService 创建 Spring bean 并将 AdminClient 的 mock 注入其中
@Mock
private AdminClient mockedAdminClient;
@InjectMocks
@Autowired
private RoleMappingService roleMappingService
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(roleMappingService,
"adminClient",
mockedAdminClient);
}
我正在尝试使用@Cacheable 来缓存角色,而不考虑参数。但是 @Cacheable 不太有效,该方法会被调用两次。
缓存配置:
@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);
}
};
}
}
角色映射服务:
@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;
}
测试:
@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));
}
adminClient.getAllRoleGroups() 在此测试中总是会被调用两次,而我预计它只会被调用一次,因为 @Cacheable。
项目结构: project structure
我认为您的@Cacheable 注释不起作用,因为您没有为class 指定接口。这是因为 Spring 为缓存创建的代理。 Spring 在其文档中指定如下。我知道你没有指定 proxy-target-class,这意味着它将默认为 false。如果为假,它将使用 JDK 基于接口的代理。但是在您的情况下,您 class 即 RollMappingService 没有实现接口。使用方法 getAllRoles 创建接口 RollMappingService 并实现它,将解决您的问题。
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.)
同时修改您的测试 class 以通过以下方式为 RoleMappingService 创建 Spring bean 并将 AdminClient 的 mock 注入其中
@Mock
private AdminClient mockedAdminClient;
@InjectMocks
@Autowired
private RoleMappingService roleMappingService
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(roleMappingService,
"adminClient",
mockedAdminClient);
}