[028][缓存模块]命名缓存:多级个性化缓存配置的设计与实现

028缓存模块命名缓存:多级个性化缓存配置的设计与实现

本项目代码:gitee.com/yunjiao-sou...

在复杂的业务系统中,不同缓存往往有着不同的容量、过期时间、键前缀等需求。例如,用户信息缓存可能需要较长的 TTL,而验证码缓存则只需要几分钟。传统的统一缓存配置难以满足这种差异化需求。为此,本文介绍一种"命名缓存"(Named Cache)的设计方案,允许开发者通过配置文件为每个缓存名称单独定义其策略,并同时支持 Redis 和 Caffeine 两种后端实现。

一、命名缓存的概念

命名缓存是指:在同一个缓存管理器中,根据缓存名称的不同,应用不同的缓存配置(如 TTL、是否允许空值、键前缀、初始容量等)。开发者只需在配置文件中以 caches.<缓存名称> 的方式声明参数,即可让缓存管理器在创建对应缓存时自动使用这些个性化设置。

这种设计带来以下好处:

  • 精细控制:针对不同业务场景调整缓存行为,避免"一刀切"。
  • 配置友好:通过外部配置文件修改,无需改动代码。
  • 实现透明 :开发时仍使用 Spring 标准的 @Cacheable 注解,只需指定正确的缓存名称。

二、整体配置模型

2.1 配置属性类

项目定义了 NamedCacheProperties 作为核心配置载体,其结构如下:

java 复制代码
@Data
@ConfigurationProperties(prefix = "tutorials4j.cache.named")
public class NamedCacheProperties {
    private NamedCacheOptions defaults = new NamedCacheOptions();
    private Map<String, NamedCacheOptions> caches = new HashMap<>();
}
  • defaults:全局默认配置,适用于所有缓存(除非被单独覆盖)。
  • caches:键为缓存名称,值为该缓存特有的配置。

NamedCacheOptions 定义了通用选项以及后端特有选项:

java 复制代码
public class NamedCacheOptions {
    private Duration timeToLive;          // 过期时间
    private Boolean cacheNullValues;      // 是否缓存null值
    private Boolean enableStatistics;     // 是否启用统计

    private RedisOptions redis = new RedisOptions();       // Redis专属配置
    private CaffeineOptions caffeine = new CaffeineOptions(); // Caffeine专属配置
}

其中 RedisOptions 包含 keyPrefix 键前缀;CaffeineOptions 包含 initialCapacitymaximumSizeexpireAfterAccess

每个 NamedCacheOptions 可以调用 applyDefaults(NamedCacheOptions defaults) 方法,将未设置的属性从默认配置中合并,实现"默认配置 + 差异化覆盖"的模型。

2.2 配置示例

application.yml 中:

yaml 复制代码
tutorials4j:
  cache:
    named:
      defaults:
        time-to-live: 60s
        cache-null-values: false
        redis:
          key-prefix: "default:"
      caches:
        userCache:
          time-to-live: 300s
          cache-null-values: true
          redis:
            key-prefix: "user:"
        productCache:
          time-to-live: 600s
          caffeine:
            initial-capacity: 500
            maximum-size: 20000

此时 userCache 会覆盖 TTL 和空值策略,并使用自己的键前缀;productCache 则使用默认 TTL 但修改 Caffeine 容量参数。

三、Redis 命名缓存的实现

3.1 缓存管理器创建器

RedisCacheManagerCreator 负责创建 RedisCacheManager 实例,采用双重检查锁单例模式保证同一配置下只有一个管理器。其 newInstance() 方法构建 RedisCacheManager.Builder 链。

java 复制代码
public RedisCacheManager newInstance() {
    RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig();
    defaultConfig = RedisUtils.fillConfiguration(defaultConfig, properties.getDefaults());
    RedisCacheManager.RedisCacheManagerBuilder builder = 
        RedisCacheManager.builder(factory).cacheDefaults(defaultConfig);
    // 应用所有 RedisCacheManagerBuilderCustomizer
    redisCacheManagerBuilderCustomizer.forEach(c -> c.customize(builder));
    // 应用所有 CacheManagerCustomizer<RedisCacheManager>
    RedisCacheManager redisCacheManager = builder.build();
    cacheManagerCustomizer.forEach(c -> c.customize(redisCacheManager));
    return redisCacheManager;
}

3.2 命名缓存配置的注入

关键点在于 NamedRedisCacheManagerBuilderCustomizer,它实现了 RedisCacheManagerBuilderCustomizer 接口,会在 builder 构建前被调用。该类遍历 properties.getCaches() 为每个命名缓存创建独立的 RedisCacheConfiguration,然后通过 builder.withInitialCacheConfigurations(configMap) 一次性注入。

java 复制代码
@Override
public void customize(RedisCacheManager.RedisCacheManagerBuilder builder) {
    RedisCacheConfiguration defaultConfig = builder.cacheDefaults();
    Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
    properties.getCaches().forEach((name, options) -> {
        RedisCacheConfiguration namedConfig = RedisUtils.fillConfiguration(defaultConfig, options);
        configMap.put(name, namedConfig);
    });
    builder.withInitialCacheConfigurations(configMap);
}

RedisUtils.fillConfiguration 方法根据 NamedCacheOptions 中的 timeToLivecacheNullValueskeyPrefix(通过 RedisOptions)修改默认配置。这样,当应用第一次操作 userCache 时,RedisCacheManager 就会使用该缓存专属的配置。

3.3 自动装配

CacheRedisConfiguration 中条件化地注册了上述定制器和创建器:

java 复制代码
@Bean(CacheManagerCreatorCategory.REDIS_CREATOR)
@ConditionalOnMissingBean
RedisCacheManagerCreator redisCacheManagerCreator(...) { ... }

并同时注册了 JSON 序列化定制器等(与命名缓存主题无关的略述)。

四、Caffeine 命名缓存的实现

Caffeine 是进程内缓存,其命名缓存实现思路与 Redis 类似,但需要直接控制 CaffeineCacheManager 的缓存创建逻辑。

4.1 自定义缓存管理器

FlexibleCaffeineCacheManager 继承自 CaffeineCacheManager,重写了 createNativeCaffeineCache(String name) 方法:

java 复制代码
@Override
protected Cache<Object, Object> createNativeCaffeineCache(String name) {
    Map<String, NamedCacheOptions> optionsMap = properties.getCaches();
    if (optionsMap.containsKey(name)) {
        NamedCacheOptions options = optionsMap.get(name);
        options.applyDefaults(properties.getDefaults());  // 合并默认配置
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder();
        CaffeineUtils.copyOption(caffeine, options);      // 应用 TTL、容量等
        return caffeine.build();
    }
    return super.createNativeCaffeineCache(name);
}

关键步骤:

  1. 根据缓存名称查找是否有专用配置。
  2. 调用 applyDefaults 将未设置的属性从全局默认值中补全。
  3. 创建一个新的 Caffeine 构造器,通过 CaffeineUtils.copyOption 设置过期、容量、访问过期等参数。
  4. 调用 build() 生成原生 Cache 实例。

这样,每个命名缓存都会拥有独立的 Caffeine 对象,其大小、过期策略互不干扰。

4.2 缓存管理器创建器

CaffeineCacheManagerCreator 同样采用单例模式,它的 newInstance() 直接创建 FlexibleCaffeineCacheManager 并注入全局 Caffeine 实例(该实例基于 properties.getDefaults() 构建,作为后备默认值)。

java 复制代码
public CaffeineCacheManager newInstance() {
    FlexibleCaffeineCacheManager caffeineCacheManager = new FlexibleCaffeineCacheManager(properties);
    caffeineCacheManager.setCaffeine(caffeine);
    return caffeineCacheManager;
}

4.3 自动装配

CacheCaffeineConfiguration 负责创建默认 Caffeine Bean 和 CaffeineCacheManagerCreator Bean。同时,FlexibleCaffeineCacheManager 内部已处理命名缓存的配置,因此无需额外的 CacheManagerCustomizer

五、使用示例

假设我们有一个业务服务,需要同时使用 Redis 缓存用户信息,使用 Caffeine 缓存产品目录:

java 复制代码
@Service
public class UserService {
    @Cacheable(value = "userCache", key = "#userId")
    public User getUser(Long userId) { ... }
}

@Service
public class ProductService {
    @Cacheable(value = "productCache", key = "#productId")
    public Product getProduct(Long productId) { ... }
}

配置文件(application.yml)如下:

yaml 复制代码
tutorials4j.cache.named:
  defaults:
    time-to-live: 60s
    cache-null-values: false
    redis:
      key-prefix: "global:"
  caches:
    userCache:
      time-to-live: 300s
      cache-null-values: true
      redis.key-prefix: "user:"
    productCache:
      time-to-live: 600s
      caffeine:
        initial-capacity: 200
        maximum-size: 10000
        expire-after-access: 300s

运行时:

  • userCache 操作 Redis:TTL 为 300 秒,允许缓存 null,键前缀为 user:
  • productCache 使用 Caffeine:最大 10000 条记录,初始容量 200,访问后 300 秒过期。

开发者只需关注 @Cacheable 注解中的缓存名称,底层管理器会自动应用对应策略。

六、总结

命名缓存机制通过统一的配置模型将缓存名称与具体参数解耦,极大地提升了缓存管理的灵活性和可维护性。本文分别以 Redis 和 Caffeine 为例,展示了:

  • 如何使用 NamedCachePropertiesNamedCacheOptions 来描述差异化的缓存配置。
  • 如何为 Redis 利用 RedisCacheManagerBuilderCustomizer 注入不同配置。
  • 如何为 Caffeine 通过重写 createNativeCaffeineCache 为每个名称创建独立实例。

这种设计可以轻松扩展至其他缓存提供者(如 EhCache),核心思想始终不变:让配置跟随缓存名称动态变化,而非全局僵化。在微服务或多业务模块的架构中,命名缓存是一项值得参考的基础设施实践。

阅读最新文章,请关注我的微信公众号杨运交

相关推荐
cfm_29142 小时前
SpringBoot整合RocketMQ极速实战
java·spring boot·后端
JAVA面经实录9173 小时前
SpringBoot3企业实战项目开发文档(完整版)
java·spring boot
Micro麦可乐4 小时前
最新Spring Security实战教程(十)权限表达式进阶 - 在SpEL在安全控制中的高阶魔法
java·spring boot·后端·spring·spring security·spel表达式
右耳朵猫AI4 小时前
Java/JVM周刊2026W21 | Java 26发布、JDK 27抢先体验、Spring Boot 4.1预告、GlassFish 8.0.2发布
java·jvm·spring boot
码不停蹄的玄黓4 小时前
SpringBoot 实现自定义注解
java·spring boot·spring
我登哥MVP5 小时前
Spring Boot 从“会用”到“精通”:Converter 原理
java·spring boot·servlet·maven·mybatis·converter
zzz_23685 小时前
【Redis】Redis 数据结构与 Spring Boot 集成
数据结构·spring boot·redis
星轨zb17 小时前
LangChain4j 集成 Spring Boot:会话记忆 NPE 的根源与 ChatMemoryProvider 正确配置
java·spring boot·后端·langchain4j
混凝土拌意大利面17 小时前
TG-BOOT springboot 功能集散开发框架(AI 协作友好)
人工智能·spring boot·后端