8

springboot入门13 – 多CacheManager应用

 3 years ago
source link: http://www.zhyea.com/2021/01/30/springboot-entrence-13-using-multi-cachemanager.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

概述

之前有写过springboot缓存应用的说明(《 springboot入门01 – 缓存的使用 》)。不过实际的场景有时候会比较复杂一些,比如:需要同时使用redis和caffine来做多级缓存,或者需要在通用配置外应用一些个性化的配置。使用多个 CacheManager 来分别管理不同的缓存是应对这种问题的一个常规方案。

接下来介绍下如何实现多 CacheManager 应用。

CacheManager 应用

这里会通过一个具体的案例来进行演示。需求大致是这样的:在应用中的大部分场景都使通用的缓存配置,但是部分特殊场景需要做个性化的配置。在接下来演示中我们主要会用到Caffeine缓存。

配置

先修改下配置。这次如果继续使用SpringBoot的自启动 CacheManager 会有一些不太好控制的地方,因此不宜再使用默认的缓存配置,需要做些独立配置:

caching:
  spec: initialCapacity=1048576,maximumSize=1073741824,expireAfterAccess=10m
  special:
    worker: initialCapacity=32,maximumSize=128,expireAfterWrite=30s

其中 caching.spec 表示通用的缓存配置, caching.special 则表示一组需要做个性化配置的特例。

通用配置是一行字符串,读取的时候可以直接使用 @ Value 注解,个性化配置则是一个 Map 结构,读取的时候需要用到 @ ConfigurationProperties 注解,大致如下:

    @Value("${caching.spec}")
    private String commonSpec;
 
    @Bean
    @ConfigurationProperties("caching.special")
    public Map<String, String> getSpecialCase() {
        return new HashMap<>(4);
    }

(关于配置文件读取可以参考之前的一篇旧文:《 springboot入门07 – 配置文件详解 》)

创建 CacheManager

接下来就是根据配置文件创建 CacheManager 了。

创建通用 CacheManager 直接使用 CaffeineCacheManager 就可以了:

    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }

注意 @ Primary 注解,这个注解表示没有特意注明时,优先选择这个 CacheManager

管理个例的 CacheManager 略有些麻烦,这里使用了 SimpleCacheManager ,代码如下:

    @Bean("special")
    public CacheManager specialCacheManager(Map<String, String> cases) {
        SimpleCacheManager manager = new SimpleCacheManager();
        if (cases != null) {
            List<CaffeineCache> caches =
                    cases.entrySet().stream()
                            .map(e -> buildCaffeineCache(e.getKey(), e.getValue()))
                            .collect(Collectors.toList());
            manager.setCaches(caches);
        }
        return manager;
    }
 
    private CaffeineCache buildCaffeineCache(String name, String spec) {
        logger.info("Cache {} specified with config:{}", name, spec);
        final Caffeine<Object, Object> caffeineBuilder = Caffeine.from(spec);
        return new CaffeineCache(name, caffeineBuilder.build());
    }

在使用这个 CacheManager 时还需要记得下在 @ CacheConfig @ Cacheable 注解中注明对应的qualifier:

@CacheConfig(cacheNames = "worker", cacheManager = "special")

至此,多缓存配置已经是没有问题了。详细代码可以参考Git: zhyea / multi-cache

这里区分通用 CacheManager 与个例 CacheManager 主要依赖 @ Primary 注解实现。接下来会介绍一些其他的方案。

继承 CachingConfigurerSupport

继承抽象类 CachingConfigurerSupport 后,可以通过实现(重写) cacheManager ( ) 方法指明默认的 CacheManager 。嗯,也就是说,节省了一个 @ Bean 和一个 @ Primary 注解。代码如下:

@Configuration
public class CachingConfigSupport extends CachingConfigurerSupport {
 
    private static final Logger logger = LoggerFactory.getLogger(CachingConfigSupport.class);
 
 
    @Value("${caching.spec}")
    private String commonSpec;
 
    @Bean
    @ConfigurationProperties("caching.special")
    public Map<String, String> getSpecialCase() {
        return new HashMap<>(4);
    }
 
    @Override
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }
 
 
    @Bean("special")
    public CacheManager specialCacheManager(Map<String, String> cases) {
        SimpleCacheManager manager = new SimpleCacheManager();
        if (cases != null) {
            List<CaffeineCache> caches =
                    cases.entrySet().stream()
                            .map(e -> buildCaffeineCache(e.getKey(), e.getValue()))
                            .collect(Collectors.toList());
            manager.setCaches(caches);
        }
        return manager;
    }
 
    private CaffeineCache buildCaffeineCache(String name, String spec) {
        logger.info("Cache {} specified with config:{}", name, spec);
        final Caffeine<Object, Object> caffeineBuilder = Caffeine.from(spec);
        return new CaffeineCache(name, caffeineBuilder.build());
    }
}

管理个例的 CacheManager 还是需要 @ Bean 注解并设置Qualifier的。

实现 CacheResolver

这个方案,怎么说呢,应该是可操作性最强大的。如果场景再复杂些,完全可以考虑用这个方案来处理。但是就我们当前这个case来说,用 CacheResolver 来实现应该是最繁琐的了。太繁琐了,懒得写了。

姑且写一个简单的例子来演示下 CacheResolver 是怎么发挥作用的吧。

CacheResolver 的实现类如下:

@Component("multi")
public class MultiCacheResolver implements CacheResolver, InitializingBean {
 
    @Value("${caching.spec}")
    private String commonSpec;
 
    private CacheManager manager;
 
    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        Collection<Cache> caches = new LinkedList<>();
        Set<String> cacheNames = context.getOperation().getCacheNames();
        for (String name : cacheNames) {
            caches.add(manager.getCache(name));
        }
        return caches;
    }
 
 
    public CacheManager newCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }
 
 
    @Override
    public void afterPropertiesSet() throws Exception {
        manager = newCacheManager();
    }
}

在代码中可以看到,是通过 resolveCaches ( ) 方法决定了提供哪些缓存。

使用 CacheResolver 后就有机会可以考虑不注入 CacheManager 的实例到容器中了,因为 CacheResolver 会管理会用到的 CacheManager 的实例。 不过在应用缓存注解的情况下,要记得指定使用哪个 CacheResolver ,像这样:

@CacheConfig(cacheNames = "worker", cacheResolver = "multi")

就这样了。示例代码都放在了这里: zhyea / multi-cache

参考文档


Recommend

  • 49
    • www.zhyea.com 4 years ago
    • Cache

    springboot入门03 – 定时任务

    概述 在Java环境下创建定时任务有多种方式: 使用while循环配合 Thread . sleep ( ...

  • 40

    概述 在Java环境下创建定时任务有多种方式: 使用while循环配合 Thread . sleep ( ...

  • 53

    以前写过关于springboot Controller层单元测试的系列文章( Spring Controller层测试 )。但是那几篇文章还是更偏方法论一些...

  • 36

    概述 从开始使用SpringBoot到现在,一直都是在用SpringBoot开发web服务(API服务)。直到前段时间,需要帮其他组的同事写一个非web的简单服务时,才想到Springboot是不是也支持非web项目。 答案是肯定的:spring诞生之初...

  • 19
    • www.cnblogs.com 4 years ago
    • Cache

    SpringBoot简介与快速入门

    一、SpringBoot简介 1.1 原有Spring优缺点分析 1.1.1 Spring的优点分析 Spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的Enterprise JavaBean(EJB),S...

  • 34

    一共两个文件,一个处理全局异常,保存信息到日志,另外一个负责返回异常信息给接口,只要将其文件添加到项目中,无需再做其他配置即可 1. MyExceptionHandler.java 全局异常处理类 如果多个异常处理...

  • 0
    • www.zhyea.com 3 years ago
    • Cache

    springboot入门14 – Kafka应用

    这几天优化了一下之前写的一个springboot kafka组件。比较起原生的spring-kafka来,我希望能够简化kafka的使用,可以更聚焦于具体的消息处理逻辑。 接下来的内容是这个组件的用法。 这...

  • 1
    • Github github.com 2 years ago
    • Cache

    Issues · MichaCo/CacheManager · GitHub

    Issues · MichaCo/CacheManager · GitHub Clear current search query, filters, and sorts Author Label...

  • 2

    springboot(24)ehcache自定义CacheManager 祈雨的博客 2018-08-31

  • 5

    【中间件】内存缓存Caffiene自定义CacheManager ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK