如何在Spring Cache Java中配置多个缓存管理器。

31

我希望在我的Web应用程序中配置多个Spring缓存管理器,并且能够在项目的不同位置使用不同的缓存管理器。有没有什么方法可以实现这个目标?


你目前发现了什么? - J Fabian Meier
我已经在项目的一个模块中配置了EHCacheManager,现在我想在另一个模块中使用RedisCacheManager,但是Spring不允许在单个应用程序上下文中拥有两个CacheManager类型的bean。然后我在我的配置类中实现了CachingConfigurer以避免这个问题。但是最终我只得到了ApplicationContext中类型为EHCacheManager的CacheManager bean。但我的要求是创建两个cacheManagers bean,并且我应该能够在不同的模块中使用它们。我听说过CompositeCacheManager,不确定它是否有帮助。非常感谢。 - Rekha
3个回答

46

你可以有多种方式来实现这个,正确答案取决于你对缓存的使用。

你有一个“主”缓存管理器

如果你在90%的情况下使用CacheManager A,而只有10%的情况下使用B,我建议你创建一个默认的CacheManager用于A(你需要通过CacheConfigurerSupport扩展指定它),类似于:

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    @Override
    @Bean // not strictly necessary
    public CacheManager cacheManager() { ... CacheManager A }

    @Bean
    public CacheManager bCacheManager() { ... CacheManager B }
}

对于需要使用另一个缓存管理器的类,您可以在顶部添加一个CacheConfig,针对10%的情况进行处理。

@CacheConfig(cacheManager="bCacheManager")
public class MyService { /*...*/ }

如果你只需要在一个方法中使用另一个缓存管理器,你也可以在方法级别上指定。

@Cacheable(cacheNames = "books", cacheManager = "bCacheManager")
public Book findById(long id) { /*...*/ }

更精细的分辨率

如果您不处于这种情况下,您需要一种方法来知道每种情况下应该使用哪个缓存管理器。您可以基于目标类型 (MyService) 或缓存名称 (books) 来进行判断。您需要实现一个 CacheResolver 来执行此转换。

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    @Override
    public CacheResolver cacheResolver() { ... }
}

查看CacheResolver的Javadoc以获取更多详细信息。在实现中,您可能有几个CacheManager实例(无论是作为Bean还是其他方式),根据您的逻辑内部调用它们来确定应该使用哪个管理器。

我在评论中看到你提到了“模块”。缓存实际上是基础设施问题,因此我强烈建议将该决策移到应用级别。您可以将缓存标记为“本地”并将其他缓存标记为“集群”,但最好有一种命名规则使其更容易。不要在模块级别选择缓存管理器。

这篇博客文章通过其他示例说明了这一点。


Stephane,我正在遵循这种方法来扩展Spring Boot的默认配置,但它似乎是一种原始的方法来解决我的问题,你能建议我更好的解决方式来解决我的问题吗?谢谢。 - davioooh
注意:在 Spring-Boot 1.5 中,当扩展 CachingConfigurerSupport 时,你会覆盖一些方法(默认的 keyGenerator 和 errorHandler),这会导致你的缓存无法使用。我已经使用了同样的方法,但没有扩展该类。只要声明 bean 就足以使它正常工作。 - dnul
你不必覆盖那些方法。如果你不这样做,将会使用默认的KeyGeneratorErrorHandler - Stephane Nicoll
1
你好,感谢回答。 有没有可能在Spring默认创建的cacheManager之外再创建一个cacheManager? 实际上,我让Spring自动配置JCacheManager,使用ehcache时,我想保留默认的cacheManager而不必重新定义它。当我创建自己的cacheManager时,由于@ConditionalOnMissingBean,就不再创建默认的cacheManager了。 - Thibaut
请不要在评论中提出问题。答案是否定的,如果您创建自己的bean,则自动配置将按照应有的方式退出。也就是说,您不必将其创建为bean。 - Stephane Nicoll

7
正如@Stephane Nicoll所解释的那样,您有几个选择。我将尝试提供有关自定义CacheResolver的一些信息。 CacheResolver有一个方法:
Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);

这个操作的可缓存性与其所在类、方法、参数等相关。

基本形式如下:

public class CustomCacheResolver implements CacheResolver {

    private final CacheManager cacheManager;

    public CustomCacheResolver(CacheManager cacheManager){
        this.cacheManager = cacheManager;
    }

    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        Collection<Cache> caches = getCaches(cacheManager, context);
        return caches;
    }

    private Collection<Cache> getCaches(CacheManager cacheManager, CacheOperationInvocationContext<?> context) {
        return context.getOperation().getCacheNames().stream()
            .map(cacheName -> cacheManager.getCache(cacheName))
            .filter(cache -> cache != null)
            .collect(Collectors.toList());
    }
}

这里我仅使用一个CacheManager以简略表述。但你可以将不同的CacheManager绑定到CacheResolver,并进行更精细的选择:如果类名是X,则使用GuavaCacheManager;否则使用EhCacheCacheManager

完成此步骤后,您应该注册CacheResolver(同样,在这里可以绑定更多CacheManagers):

@Configuration
@EnableCaching
public class CacheConfiguration extends CachingConfigurerSupport {

    @Bean
    @Override
    public CacheManager cacheManager() {
        // Desired CacheManager
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new CustomCacheResolver(cacheManager());
    }
}

作为最后一步,您需要在 @Cacheable @CachePut @CacheConfig 等注释中的一个中指定 CustomCacheResolver
@Cacheable(cacheResolver="cacheResolver")

您可以在这里查看代码示例。

0

你可以像这样编写一个自定义的CacheManager bean

    @Primary
    @Bean("customerCacheManager")
    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
        Map<String, Long> expires = new HashMap<>();
        expires.put("fundShareSplit", TimeUnit.SECONDS.toSeconds(60));
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        cacheManager.setExpires(expires);
        List<String> cacheNames = Arrays.asList("fundShareSplit");
        cacheManager.setCacheNames(cacheNames);
        return cacheManager;
    }

然后在您的注释中使用它,就像这样

 @Cacheable(cacheManager = "customerCacheManager",value = "fundShareSplit",key="'smile:asset:fundSplit:fundId:'+#root.args[0]+'_localDate:'+#root.args[1]",unless="#result == null")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接