问题引入
最近在开发企业员工管理系统时,遇到了这样一个错误:
Cannot find cache named 'menusTree' for Builder[public com.ems.common.result.Result com.ems.controller.admin.MenuController.allMenus()] caches=[menusTree] | key='#root.methodName' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
这个错误发生在使用@Cacheable
注解缓存菜单树数据时。虽然我们已经:
- 在主应用类上添加了
@EnableCaching
注解 - 配置了Redis相关的连接信息
- 创建了
CacheConfiguration
配置类 - 但是Spring仍然无法找到名为
menusTree
的缓存。
问题解决
经过排查,发现是缺少了一个关键配置:需要在application.yml
中明确指定缓存类型:
spring:
cache:
type: redis
深入分析
为什么有时不需要配置cache.type也能正常工作?这涉及Spring Boot的自动配置机制:
1. 自动配置原理:
- Spring Boot通过
CacheAutoConfiguration
自动配置缓存 - 根据类路径下的依赖自动选择缓存提供者
- 按预定义优先级选择缓存实现
2. 可能出现问题的情况:
- 项目中存在多个缓存实现依赖
- Spring Boot版本差异
- 自动配置条件不满足
想更加全面了解Spring Boot的自动配置机制可见以前的博客一文搞懂!SpringBoot自动装配原理
Spring Cache 核心概念
通过这个问题,让我们深入了解Spring Cache的使用:
1. 导入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- SpringCache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 可选:FastJSON序列化 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
2. 启用缓存
要使用Spring Cache
,首先需要在启动类上添加@EnableCaching
注解:
@SpringBootApplication
@EnableCaching //开启缓存注解
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.配置文件
spring:
cache:
type: redis
redis:
time-to-live: 3600000 # 缓存过期时间(可选)
key-prefix: "cache:" # 缓存key前缀(可选)
data:
redis:
host: localhost
port: 6379
password: your_password
4. 核心注解
Spring Cache提供了几个重要的注解:
@Cacheable
主要用于查询操作的缓存,在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放在缓存中
@Cacheable(
cacheNames = "menu", // 缓存名称
key = "#id", // 缓存key
unless = "#result == null", // 不缓存null值
condition = "#id > 0" // 缓存条件
)
public Menu getMenuById(Long id) {
return menuMapper.selectById(id);
}
@CachePut
更新缓存,无论缓存是否存在,都会执行方法并将结果存入缓存
@CachePut(
cacheNames = "menu",
key = "#menu.id"
)
public Menu updateMenu(Menu menu) {
menuMapper.updateById(menu);
return menu;
}
@CacheEvict
清除缓存,将一条或多条数据从缓存中删除
@CacheEvict(
cacheNames = "menu",
allEntries = true // 清除所有条目
)
public void clearMenuCache() {
log.info("清除菜单缓存");
}
4. 缓存配置
在实际应用中,我们通常需要配置缓存管理器。以Redis为例:
/**
* 配置类,配置SpringCache的序列化方式
* 需要导入fastjson依赖
*/
@Configuration
public class CacheConfiguration {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
// 先载入配置文件中的配置信息
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
// 根据配置文件中的定义,初始化 Redis Cache 配
if (redisProperties.getTimeToLive() != null) {
redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive());
}
if (StringUtils.hasText(redisProperties.getKeyPrefix())) {
redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
}
// Fastjson 的通用 RedisSerializer 实现
GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
// 设置 Value 的序列化方式
return redisCacheConfiguration
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
}
}
5. SpEL表达式
Spring Cache支持强大的SpEL表达式来定义缓存的key:
#root.methodName
: 当前方法名#root.method
: 当前方法对象#root.target
: 当前被调用的对象#root.targetClass
: 当前被调用的对象的类#参数名
: 方法参数#p0
: 第一个参数#result
: 方法结果(仅适用于@CachePut
和@CacheEvict
的unless属性)
最佳实践
- 显式配置
spring.cache.type
,避免冲突 - 合理设置缓存过期时间:避免数据长期不一致
- 选择合适的序列化方式:在性能和可读性之间找到平衡
- 设计合理的缓存键:使用有意义的前缀,避免键冲突
- 注意缓存穿透和雪崩:使用unless属性处理null值,设置随机过期时间
总结
Spring Cache
提供了一种声明式的缓存解决方案,能够显著提升应用性能。在使用时要注意:
- 确保正确配置缓存类型和缓存管理器
- 合理使用缓存注解和SpEL表达式
- 注意缓存的一致性和过期策略
- 考虑缓存对内存的影响
通过这次错误的解决过程,我们不仅修复了问题,也深入理解了Spring Cache的工作机制和使用方法。在实际开发中,合理使用缓存可以大大提升应用性能,但同时也要注意缓存带来的数据一致性等问题。