问题描述
无论参数如何,我都尝试使用@Cacheable来缓存角色。但是@Cacheable不能正常工作,该方法将被调用两次。
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;
}
测试:
@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注释不起作用,因为您尚未为类指定接口。这是因为Spring为缓存创建了代理。 Spring在其文档中指定了以下内容。我还没有指定proxy-target-class,这意味着它将默认为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);
}