【问题标题】:@Cacheable not working, still calling the caching method@Cacheable 不工作,仍在调用缓存方法
【发布时间】:2020-12-29 11:04:00
【问题描述】:

我正在尝试使用@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

【问题讨论】:

  • @Cachable 注解由 Spring 依赖注入处理。但是,您自己(通过模拟)构建您的测试目标,而不是从 Spring 请求它。例如,在测试类中添加:@RunWith(SpringRunner.class)@Autowire private AdminClient adminClient,以便 Spring 构建并注入您的类并使用缓存。
  • @cruftex 这是有道理的。但是按照这些步骤,我会得到 adminClient 的 NullPointer 异常。我还尝试了“@ExtendWith”(Junit5),并得到了 NoSuchBeanDefinitionException:没有可用的“proxy.AdminClient”类型的合格 bean:预计至少有 1 个有资格作为自动装配候选者的 bean。依赖注释:{@org.springframework.beans.factory.annotation.Autowired(required=true)} 我还没有找到解决方法,我的 AdminClient 使用 Component 进行了注释。

标签: java spring spring-boot caching spring-test


【解决方案1】:

我认为您的 @Cacheable 注释不起作用,因为您没有为类指定接口。这是因为 Spring 为缓存创建了代理。 Spring 在其文档中指定了以下内容。我觉得你没有指定代理目标类,这意味着它将默认为 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);
} 

【讨论】:

  • 不太好用,我用 getAllRoles 方法添加了一个名为 RoleMapping 的接口(此处没有注释)。然后我在 RoleMappingService 中实现了这个接口,并在方法 getAllRoles 上添加了一个 Override 注释。我是不是做错了什么?
  • 我发现的另一件事是,您没有使用 RoleMappingService 作为 spring bean,而是使用 new 创建。您应该将 RoleMappingService 创建为 spring bean,然后您可以使用 ReflectionUtil 将 adminClient 的模拟实例注入到 spring bean RoleMappingService。请让我知道它是否有效
  • 是的。就是这样!感谢您的回复。
  • 非常感谢,投了赞成票。 this页面的静态功能测试呢?
  • 感谢您的投票。我检查了你的链接,我认为在这里,没有必要模拟静态方法。虽然可以用 powerMockito 模拟静态方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-28
  • 1970-01-01
  • 1970-01-01
  • 2021-06-24
  • 1970-01-01
  • 2019-05-08
相关资源
最近更新 更多