1. 概述

本文将手把手带你实现一个自定义的 Spring Cache KeyGenerator,解决缓存键冲突的常见“坑”。

如果你对 Spring Cache 还不熟悉,建议先阅读前置文章:Spring Cache 入门指南

2. KeyGenerator 的作用与默认行为

KeyGenerator 是 Spring Cache 中负责为缓存数据生成唯一键的核心组件。每次缓存读写时,都依赖这个 key 来定位数据。

默认实现SimpleKeyGenerator
它基于方法参数生成 key。例如,findUserById(123) 生成的 key 主要由参数 123 决定。

⚠️ 潜在问题

  • 如果两个不同方法使用了相同的缓存名(如 @Cacheable("books"))且参数类型一致,极有可能发生 key 冲突
  • 一旦 key 冲突,缓存数据会被错误覆盖,导致“缓存污染”——这是线上踩坑的高发区

举个例子:

@Cacheable("books")
List<Book> getBooksByAuthor(String author) { ... }

@Cacheable("books")
List<Book> getBooksByCategory(String category) { ... }

getBooksByAuthor("fiction")getBooksByCategory("fiction") 被调用时,它们生成的 key 可能相同,导致彼此覆盖。❌

3. 自定义 KeyGenerator

3.1 接口契约

KeyGenerator 接口仅需实现一个方法:

Object generate(Object target, Method method, Object... params)

参数说明:

  • target:目标对象(被代理的实例)
  • method:当前执行的方法
  • params:方法参数数组

⚠️ 实现不当会导致缓存混乱,务必保证 key 的唯一性与可预测性

3.2 实现方案

下面是一个简单粗暴但实用的自定义实现:

public class CustomKeyGenerator implements KeyGenerator {
 
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getSimpleName() + "_"
          + method.getName() + "_"
          + StringUtils.arrayToDelimitedString(params, "_");
    }
}

🔍 key 组成逻辑

[类名]_[方法名]_[参数值拼接]

比如调用 BookService.getBooks() 无参方法,生成的 key 为:

BookService_getBooks_

如果方法有参,如 getUserById(1001),则 key 为:

UserService_getUserById_1001

✅ 优势:类名+方法名前缀极大降低了 key 冲突概率,适合大多数业务场景。

3.3 配置方式

有两种方式启用自定义 KeyGenerator

方式一:全局配置(推荐)

在配置类中声明为 Spring Bean,并继承 CachingConfigurerSupport

@EnableCaching
@Configuration
public class ApplicationConfig extends CachingConfigurerSupport {
    
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        Cache booksCache = new ConcurrentMapCache("books");
        cacheManager.setCaches(Arrays.asList(booksCache));
        return cacheManager;
    }
  
    @Bean("customKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new CustomKeyGenerator();
    }
}

🔔 注意 Bean 名为 customKeyGenerator,后续通过名称引用。

方式二:局部指定(按需使用)

仅对特定方法启用自定义 key 生成策略:

@Component
public class BookService {
  
    @Cacheable(value = "books", keyGenerator = "customKeyGenerator")
    public List<Book> getBooks() {
        List<Book> books = new ArrayList<>();
        books.add(new Book("The Counterfeiters", "André Gide"));
        books.add(new Book("Peer Gynt and Hedda Gabler", "Henrik Ibsen"));
        return books;
    }
}

✅ 使用场景:某些核心接口需要更精细的缓存控制,其他走默认即可。

4. 总结

  • SimpleKeyGenerator 在多方法共享缓存名时容易引发 key 冲突,生产环境需警惕 ❌
  • 自定义 KeyGenerator 是解决该问题的简单有效手段 ✅
  • 推荐使用 类名 + 方法名 + 参数 的组合方式生成 key,清晰且低冲突
  • 可通过全局配置或局部注解灵活启用

本文完整示例代码已托管至 GitHub:https://github.com/techblog/spring-boot-caching-demo


原始标题:Spring Cache - Creating a Custom KeyGenerator