为纪念 35 周年:《Terminator 2: Judgment Day》将在全球影院重映
2026-07-13
2026-07-17 0
本文章代码: gitee , gitcode , github
![[028][缓存模块]命名缓存:多级个性化缓存配置的设计与实现](https://www.0405.net/uploads/20260717/img_6a59f8a9c893f30.webp)
在复杂的业务系统中,不同缓存往往有着不同的容量、过期时间、键前缀等需求。例如,用户信息缓存可能需要较长的 TTL,而验证码缓存则只需要几分钟。传统的统一缓存配置难以满足这种差异化需求。为此,本文介绍一种“命名缓存”(Named Cache)的设计方案,允许开发者通过配置文件为每个缓存名称单独定义其策略,并同时支持 Redis 和 Caffeine 两种后端实现。
命名缓存是指:在同一个缓存管理器中,根据缓存名称的不同,应用不同的缓存配置(如 TTL、是否允许空值、键前缀、初始容量等)。开发者只需在配置文件中以 caches.<缓存名称> 的方式声明参数,即可让缓存管理器在创建对应缓存时自动使用这些个性化设置。
这种设计带来以下好处:
@Cacheable 注解,只需指定正确的缓存名称。项目定义了 NamedCacheProperties 作为核心配置载体,其结构如下:
@Data
@ConfigurationProperties(prefix = "tutorials4j.cache.named")
public class NamedCacheProperties { private NamedCacheOptions defaults = new NamedCacheOptions(); private Map caches = new HashMap<>();
}
defaults:全局默认配置,适用于所有缓存(除非被单独覆盖)。caches:键为缓存名称,值为该缓存特有的配置。NamedCacheOptions 定义了通用选项以及后端特有选项:
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 包含 initialCapacity、maximumSize、expireAfterAccess。
每个 NamedCacheOptions 可以调用 applyDefaults(NamedCacheOptions defaults) 方法,将未设置的属性从默认配置中合并,实现“默认配置 + 差异化覆盖”的模型。
在 application.yml 中:
tutorials4j:cache: named:
defaults:
time-to-live: 60s
cache-null-values: false
redis:key-prefix: "default:"
caches:
userCache:time-to-live: 300scache-null-values: trueredis: key-prefix: "user:"
productCache:time-to-live: 600scaffeine: initial-capacity: 500 maximum-size: 20000
此时 userCache 会覆盖 TTL 和空值策略,并使用自己的键前缀;productCache 则使用默认 TTL 但修改 Caffeine 容量参数。
RedisCacheManagerCreator 负责创建 RedisCacheManager 实例,采用双重检查锁单例模式保证同一配置下只有一个管理器。其 newInstance() 方法构建 RedisCacheManager.Builder 链。
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 = builder.build(); cacheManagerCustomizer.forEach(c -> c.customize(redisCacheManager)); return redisCacheManager;
}
关键点在于 NamedRedisCacheManagerBuilderCustomizer,它实现了 RedisCacheManagerBuilderCustomizer 接口,会在 builder 构建前被调用。该类遍历 properties.getCaches() 为每个命名缓存创建独立的 RedisCacheConfiguration,然后通过 builder.withInitialCacheConfigurations(configMap) 一次性注入。
@Override
public void customize(RedisCacheManager.RedisCacheManagerBuilder builder) { RedisCacheConfiguration defaultConfig = builder.cacheDefaults(); Map configMap = new HashMap<>(); properties.getCaches().forEach((name, options) -> {
RedisCacheConfiguration namedConfig = RedisUtils.fillConfiguration(defaultConfig, options);
configMap.put(name, namedConfig); }); builder.withInitialCacheConfigurations(configMap);
}
RedisUtils.fillConfiguration 方法根据 NamedCacheOptions 中的 timeToLive、cacheNullValues、keyPrefix(通过 RedisOptions)修改默认配置。这样,当应用第一次操作 userCache 时,RedisCacheManager 就会使用该缓存专属的配置。
CacheRedisConfiguration 中条件化地注册了上述定制器和创建器:
@Bean(CacheManagerCreatorCategory.REDIS_CREATOR)
@ConditionalOnMissingBean
RedisCacheManagerCreator redisCacheManagerCreator(...) { ... }
并同时注册了 JSON 序列化定制器等(与命名缓存主题无关的略述)。
Caffeine 是进程内缓存,其命名缓存实现思路与 Redis 类似,但需要直接控制 CaffeineCacheManager 的缓存创建逻辑。
FlexibleCaffeineCacheManager 继承自 CaffeineCacheManager,重写了 createNativeCaffeineCache(String name) 方法:
@Override
protected Cache
关键步骤:
applyDefaults 将未设置的属性从全局默认值中补全。Caffeine 构造器,通过 CaffeineUtils.copyOption 设置过期、容量、访问过期等参数。build() 生成原生 Cache 实例。这样,每个命名缓存都会拥有独立的 Caffeine 对象,其大小、过期策略互不干扰。
CaffeineCacheManagerCreator 同样采用单例模式,它的 newInstance() 直接创建 FlexibleCaffeineCacheManager 并注入全局 Caffeine 实例(该实例基于 properties.getDefaults() 构建,作为后备默认值)。
public CaffeineCacheManager newInstance() { FlexibleCaffeineCacheManager caffeineCacheManager = new FlexibleCaffeineCacheManager(properties); caffeineCacheManager.setCaffeine(caffeine); return caffeineCacheManager;
}
CacheCaffeineConfiguration 负责创建默认 Caffeine Bean 和 CaffeineCacheManagerCreator Bean。同时,FlexibleCaffeineCacheManager 内部已处理命名缓存的配置,因此无需额外的 CacheManagerCustomizer。
假设我们有一个业务服务,需要同时使用 Redis 缓存用户信息,使用 Caffeine 缓存产品目录:
@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)如下:
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 为例,展示了:
NamedCacheProperties 和 NamedCacheOptions 来描述差异化的缓存配置。RedisCacheManagerBuilderCustomizer 注入不同配置。createNativeCaffeineCache 为每个名称创建独立实例。这种设计可以轻松扩展至其他缓存提供者(如 EhCache),核心思想始终不变:让配置跟随缓存名称动态变化,而非全局僵化。在微服务或多业务模块的架构中,命名缓存是一项值得参考的基础设施实践。