@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);
}