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 包含 initialCapacity、maximumSize、expireAfterAccess。
每个 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 中的 timeToLive、cacheNullValues、keyPrefix(通过 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);
}
关键步骤:
- 根据缓存名称查找是否有专用配置。
- 调用
applyDefaults将未设置的属性从全局默认值中补全。 - 创建一个新的
Caffeine构造器,通过CaffeineUtils.copyOption设置过期、容量、访问过期等参数。 - 调用
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 为例,展示了:
- 如何使用
NamedCacheProperties和NamedCacheOptions来描述差异化的缓存配置。 - 如何为 Redis 利用
RedisCacheManagerBuilderCustomizer注入不同配置。 - 如何为 Caffeine 通过重写
createNativeCaffeineCache为每个名称创建独立实例。
这种设计可以轻松扩展至其他缓存提供者(如 EhCache),核心思想始终不变:让配置跟随缓存名称动态变化,而非全局僵化。在微服务或多业务模块的架构中,命名缓存是一项值得参考的基础设施实践。
阅读最新文章,请关注我的微信公众号杨运交