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