Spring Boot 缓存体系

Spring Boot 缓存体系


目录

  • 一、为什么需要缓存?
  • [二、Spring Cache 抽象体系总览](#二、Spring Cache 抽象体系总览 "#%E4%BA%8Cspring-cache-%E6%8A%BD%E8%B1%A1%E4%BD%93%E7%B3%BB%E6%80%BB%E8%A7%88")
  • 三、核心概念详解
  • 四、缓存注解详解
  • [五、Spring Boot 缓存自动配置流程](#五、Spring Boot 缓存自动配置流程 "#%E4%BA%94spring-boot-%E7%BC%93%E5%AD%98%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E6%B5%81%E7%A8%8B")
  • [六、运行时调用流程 ------ 注解触发后发生了什么](#六、运行时调用流程 —— 注解触发后发生了什么 "#%E5%85%AD%E8%BF%90%E8%A1%8C%E6%97%B6%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B--%E6%B3%A8%E8%A7%A3%E8%A7%A6%E5%8F%91%E5%90%8E%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88")
  • [七、实战 Demo:从零搭建 Redis 缓存](#七、实战 Demo:从零搭建 Redis 缓存 "#%E4%B8%83%E5%AE%9E%E6%88%98-demo%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA-redis-%E7%BC%93%E5%AD%98")
  • [八、扩展:@CacheableExt 如何实现按缓存名设置过期时间](#八、扩展:@CacheableExt 如何实现按缓存名设置过期时间 "#%E5%85%AB%E6%89%A9%E5%B1%95cacheableext-%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%8C%89%E7%BC%93%E5%AD%98%E5%90%8D%E8%AE%BE%E7%BD%AE%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4")
  • [九、经典踩坑:Redisson 导致 RedisCacheManager 失效](#九、经典踩坑:Redisson 导致 RedisCacheManager 失效 "#%E4%B9%9D%E7%BB%8F%E5%85%B8%E8%B8%A9%E5%9D%91redisson-%E5%AF%BC%E8%87%B4-rediscachemanager-%E5%A4%B1%E6%95%88")
  • 十、总结

一、为什么需要缓存?

1.1 一个生活中的类比

想象你去图书馆借书:

场景 对应技术 说明
每次都去书架上找书 无缓存 每次请求都查数据库,慢
把常用书放在桌上 有缓存 第一次查数据库,结果放入缓存,后续直接读缓存

1.2 缓存的核心思想

复制代码
第一次请求:  调用方法 → 查数据库 → 得到结果 → 存入缓存 → 返回结果
后续请求:    调用方法 → 查缓存 → 命中 → 直接返回结果(不再查数据库)

1.3 没有缓存 vs 有缓存

java 复制代码
// ========== 没有缓存:每次都查数据库 ==========
public User getUserById(Long id) {
    // 每次调用都执行 SQL,数据库压力大
    return userMapper.selectById(id);
}

// ========== 有缓存:第一次查数据库,后续走缓存 ==========
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
    // 只有缓存未命中时才执行 SQL
    return userMapper.selectById(id);
}

二、Spring Cache 抽象体系总览

2.1 核心设计理念

Spring Cache 的设计遵循了一个重要原则:对缓存操作进行抽象,与具体实现解耦。

就像 JDBC 是数据库的抽象层一样,Spring Cache 是缓存的抽象层。你写代码时只关心"我要缓存这个结果",不关心底层用的是 Redis、Caffeine 还是 Ehcache。

2.2 体系架构图

less 复制代码
┌─────────────────────────────────────────────────────────────┐
│                    你的业务代码                                │
│         @Cacheable / @CacheEvict / @CachePut                 │
└──────────────────────────┬──────────────────────────────────┘
                           │ AOP 拦截
                           ▼
┌─────────────────────────────────────────────────────────────┐
│              Spring Cache 抽象层(接口)                       │
│                                                              │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐   │
│  │ CacheManager │  │    Cache     │  │  CacheResolver   │   │
│  │ 缓存管理器   │  │  缓存实例    │  │  缓存解析器      │   │
│  └──────┬──────┘  └──────┬───────┘  └────────┬─────────┘   │
└─────────┼────────────────┼───────────────────┼──────────────┘
          │                │                   │
          ▼                ▼                   ▼
┌─────────────────────────────────────────────────────────────┐
│              具体实现层(可替换)                               │
│                                                              │
│  ┌─────────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │ RedisCacheManager│  │CaffeineCache │  │JCacheCache    │  │
│  │ RedisCache       │  │Manager       │  │Manager        │  │
│  │                  │  │CaffeineCache │  │JCacheCache    │  │
│  └─────────────────┘  └──────────────┘  └───────────────┘  │
└─────────────────────────────────────────────────────────────┘

2.3 核心接口关系

scss 复制代码
CacheManager(缓存管理器)
    │
    ├── getCache(String name)  ──→  Cache(缓存实例)
    │                                    │
    │                                    ├── get(Object key)      → 读取缓存
    │                                    ├── put(Object key, Object value) → 写入缓存
    │                                    └── evict(Object key)   → 删除缓存
    │
    └── getCacheNames()  ──→  获取所有缓存名称

CacheResolver(缓存解析器)
    │
    └── resolveCaches(CacheOperationInvocationContext)  ──→  根据上下文解析出 Cache 集合

三、核心概念详解

3.1 CacheManager ------ 缓存管理器

作用:管理多个缓存实例,是整个缓存体系的"管家"。

java 复制代码
public interface CacheManager {
    // 根据名称获取缓存实例
    // 返回 null 表示不存在该缓存
    @Nullable
    Cache getCache(String name);

    // 获取所有已知的缓存名称
    Collection<String> getCacheNames();
}

不同实现的差异(关键!)

实现类 遇到未知缓存名时的行为 说明
RedisCacheManager 自动创建新的缓存实例 宽松模式,对开发者友好
CaffeineCacheManager 自动创建新的缓存实例 同上
JCacheCacheManager 返回 null,不自动创建 严格模式,遵循 JSR-107 规范
ConcurrentMapCacheManager 可配置是否自动创建 默认自动创建

3.2 Cache ------ 缓存实例

作用:一个具体的缓存区域,类似 Redis 中的一个命名空间。

java 复制代码
public interface Cache {
    // 缓存名称
    String getName();

    // 获取底层实现(如 RedisTemplate)
    Object getNativeCache();

    // 读取缓存
    @Nullable
    ValueWrapper get(Object key);

    // 写入缓存
    void put(Object key, @Nullable Object value);

    // 删除缓存
    void evict(Object key);

    // 清空缓存
    void clear();
}

3.3 CacheResolver ------ 缓存解析器

作用:根据方法调用的上下文信息,解析出应该使用哪些 Cache 实例。

java 复制代码
public interface CacheResolver {
    // 根据调用上下文解析缓存集合
    Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);
}

默认实现SimpleCacheResolver

  • CacheManager 中根据注解中的 cacheNames 获取对应的 Cache 实例。
  • 如果 CacheManager.getCache() 返回 null,则抛出 IllegalArgumentException

四、缓存注解详解

4.1 @Cacheable ------ 读取缓存

作用:方法执行前先查缓存,命中则直接返回,未命中则执行方法并将结果存入缓存。

java 复制代码
@Cacheable(
    value = "userCache",           // 缓存名称(别名 cacheNames)
    key = "#id",                   // 缓存键(SpEL 表达式)
    condition = "#id > 0",         // 满足条件才缓存
    unless = "#result == null",    // 结果满足条件则不缓存
    sync = true                    // 同步模式(防止缓存击穿)
)
public User getUserById(Long id) {
    return userMapper.selectById(id);
}

执行流程

markdown 复制代码
方法被调用
    │
    ▼
① 解析注解,获取 cacheNames 和 key
    │
    ▼
② 通过 CacheResolver → CacheManager → Cache 查找缓存
    │
    ├── 命中 → 直接返回缓存值,不执行方法
    │
    └── 未命中 → 执行方法 → 将结果写入缓存 → 返回结果

4.2 @CachePut ------ 更新缓存

作用:始终执行方法,然后将结果更新到缓存中。

java 复制代码
@CachePut(
    value = "userCache",
    key = "#user.id"
)
public User updateUser(User user) {
    userMapper.updateById(user);
    return user;
}

与 @Cacheable 的区别

注解 是否执行方法 何时写缓存 典型场景
@Cacheable 缓存命中时不执行 缓存未命中时写 查询
@CachePut 始终执行 每次都写 更新

4.3 @CacheEvict ------ 删除缓存

作用:删除缓存中的数据。

java 复制代码
@CacheEvict(
    value = "userCache",
    key = "#id"                     // 删除指定 key
)
public void deleteUser(Long id) {
    userMapper.deleteById(id);
}

@CacheEvict(
    value = "userCache",
    allEntries = true               // 清空整个缓存
)
public void clearAllUserCache() {
    // 不需要操作数据库,只清缓存
}

4.4 @Caching ------ 组合使用

作用:一个方法上组合多个缓存注解。

java 复制代码
@Caching(
    cacheable = @Cacheable(value = "userCache", key = "#id"),
    evict = @CacheEvict(value = "userListCache", allEntries = true)
)
public User getUserAndClearList(Long id) {
    return userMapper.selectById(id);
}

4.5 @CacheConfig ------ 类级别公共配置

作用:在类级别统一配置缓存名称、Key 生成器等,方法上的注解可以覆盖。

java 复制代码
@CacheConfig(cacheNames = "userCache")  // 类中所有缓存注解默认使用此名称
@Service
public class UserService {

    @Cacheable(key = "#id")             // 不需要再写 cacheNames
    public User getUserById(Long id) { ... }

    @Cacheable(value = "userDetailCache", key = "#id")  // 覆盖类级别配置
    public UserDetail getUserDetail(Long id) { ... }
}

4.6 注解参数汇总

参数 说明 示例
value / cacheNames 缓存名称 "userCache"
key 缓存键(SpEL) "#id", "#user.name"
condition 缓存条件(SpEL) "#id > 0"
unless 否定条件(SpEL) "#result == null"
keyGenerator Key 生成器 "myKeyGenerator"
cacheManager 指定 CacheManager "redisCacheManager"
cacheResolver 指定 CacheResolver "myCacheResolver"
sync 同步模式 true
allEntries 清空所有(仅 @CacheEvict) true
beforeInvocation 方法前删除(仅 @CacheEvict) true

4.7 SpEL 表达式常用写法

表达式 说明 示例场景
#p0, #a0 第一个参数 key = "#p0"
#id 参数名(需编译时保留) key = "#id"
#user.name 参数属性 key = "#user.name"
#result 返回值(仅 @CachePut 和 unless) unless = "#result == null"
#root.method 当前方法 调试用
#root.target 当前对象 调试用

五、Spring Boot 缓存自动配置流程

5.1 总览:从启动到 CacheManager 创建

java 复制代码
Spring Boot 应用启动
    │
    ▼
① 加载 spring.factories 中的自动配置类
    │
    ▼
② CacheAutoConfiguration 被触发
    │
    ▼
③ CacheConfigurationImportSelector 导入所有缓存类型的配置类
    │
    ▼
④ 逐个检查每个配置类的 @Conditional 条件
    │
    ▼
⑤ 第一个满足条件的配置类生效,创建对应的 CacheManager
    │
    ▼
⑥ 其余配置类被跳过(@ConditionalOnMissingBean 谦让规则)

5.2 第一步:CacheAutoConfiguration 入口

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

java 复制代码
// Spring Boot 缓存自动配置的入口类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)        // 条件:classpath 上有 CacheManager 接口
@ConditionalOnBean(CacheAspectSupport.class)   // 条件:容器中有缓存切面支持
@EnableConfigurationProperties(CacheProperties.class)  // 绑定配置属性
@Import(CacheAutoConfiguration.CacheConfigurationImportSelector.class)  // 关键!导入选择器
public class CacheAutoConfiguration {

    // 内部类:导入所有缓存类型的配置类
    static class CacheConfigurationImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 遍历所有缓存类型:GENERIC, JCACHE, EHCACHE, REDIS, CAFFEINE, ...
            CacheType[] types = CacheType.values();
            String[] imports = new String[types.length];
            for (int i = 0; i < types.length; i++) {
                // 根据类型获取对应的配置类全限定名
                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
            }
            // 返回所有配置类,让 Spring 去逐个检查条件
            return imports;
        }
    }
}

关键点CacheConfigurationImportSelector 会把所有缓存类型的配置类都导入,但最终哪个生效取决于条件注解。

5.3 第二步:各配置类的条件竞争

Spring Boot 内置的缓存配置类及其生效优先级(从高到低):

优先级 配置类 条件 创建的 CacheManager
1 GenericCacheConfiguration 容器中已有 CacheManager Bean 直接使用已有的
2 JCacheCacheConfiguration classpath 有 javax.cache.CacheManager JCacheCacheManager
3 EhCacheCacheConfiguration classpath 有 Ehcache EhCacheCacheManager
4 HazelcastCacheConfiguration classpath 有 HazelcastInstance HazelcastCacheManager
5 InfinispanCacheConfiguration classpath 有 Infinispan InfinispanCacheManager
6 CouchbaseCacheConfiguration classpath 有 Couchbase CouchbaseCacheManager
7 RedisCacheConfiguration classpath 有 RedisConnectionFactory RedisCacheManager
8 CaffeineCacheConfiguration classpath 有 Caffeine CaffeineCacheManager
9 SimpleCacheConfiguration 无其他条件(兜底) ConcurrentMapCacheManager
10 NoOpCacheConfiguration spring.cache.type=none NoOpCacheManager
java 复制代码
final class CacheConfigurations {
private static final Map<CacheType, Class<?>> MAPPINGS;

    private CacheConfigurations() {
    }

    static String getConfigurationClass(CacheType cacheType) {
        Class<?> configurationClass = (Class)MAPPINGS.get(cacheType);
        Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);
        return configurationClass.getName();
    }

    static CacheType getType(String configurationClassName) {
        for(Map.Entry<CacheType, Class<?>> entry : MAPPINGS.entrySet()) {
            if (((Class)entry.getValue()).getName().equals(configurationClassName)) {
                return (CacheType)entry.getKey();
            }
        }

        throw new IllegalStateException("Unknown configuration class " + configurationClassName);
    }

    //顺序
    static {
        Map<CacheType, Class<?>> mappings = new EnumMap(CacheType.class);
        mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
        mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
        mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
        mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
        mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
        mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
        mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
        mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
        mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
        mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
        MAPPINGS = Collections.unmodifiableMap(mappings);
    }
}

5.4 第三步:RedisCacheConfiguration 的条件详解

org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)       // ① classpath 有 Redis 连接工厂
@AutoConfigureAfter(RedisAutoConfiguration.class)       // ② 在 Redis 自动配置之后
@ConditionalOnBean(RedisConnectionFactory.class)        // ③ 容器中有 RedisConnectionFactory Bean
@ConditionalOnMissingBean(CacheManager.class)           // ④ 容器中没有 CacheManager(谦让规则!)
@Conditional(CacheCondition.class)                      // ⑤ 配置属性条件
class RedisCacheConfiguration {

    @Bean
    RedisCacheManager cacheManager(
            CacheProperties cacheProperties,
            RedisCacheConfiguration redisCacheConfiguration,
            ObjectProvider<RedisCacheManagerBuilderCustomizer> customizers) {

        // 创建 RedisCacheManager 的 Builder
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultCacheConfiguration);

        // 应用所有自定义器(包括我们的 RedisCacheBuilderCustomizer)
        customizers.orderedStream().forEach(customizer -> customizer.customize(builder));

        // 构建 RedisCacheManager
        return builder.build();
    }
}

条件④是关键@ConditionalOnMissingBean(CacheManager.class) 意味着------如果容器中已经存在任何 CacheManager 类型的 Bean,这个配置类就不生效

5.5 完整启动时序图

java 复制代码
时间线 ──────────────────────────────────────────────────────────→

Spring Boot 启动
    │
    ├─ 1. 加载 spring.factories
    │      └─ 发现 CacheAutoConfiguration
    │
    ├─ 2. 执行 CacheConfigurationImportSelector
    │      └─ 导入所有缓存配置类
    │
    ├─ 3. 【关键分叉】检查各配置类的条件
    │      │
    │      ├─ RedissonAutoConfiguration 先执行(@AutoConfigureBefore)
    │      │   └─ 创建 JCacheCacheManager  ← 抢先注册了 CacheManager!
    │      │
    │      ├─ RedisCacheConfiguration 检查条件
    │      │   ├─ @ConditionalOnClass(RedisConnectionFactory)     → ✅
    │      │   ├─ @ConditionalOnBean(RedisConnectionFactory)      → ✅
    │      │   ├─ @ConditionalOnMissingBean(CacheManager.class)   → ❌ JCacheCacheManager 已存在!
    │      │   └─ ❌ 条件不满足,整个配置类被跳过
    │      │
    │      └─ 最终容器中只有 JCacheCacheManager
    │
    ├─ 4. CacheExtAnnotationBeanPostProcessor 扫描注解
    │      └─ 收集 @CacheableExt 的缓存配置
    │
    ├─ 5. RedisCacheBuilderCustomizer 无法执行
    │      └─ 因为 RedisCacheManager 没有被创建,customize() 不会被调用
    │
    └─ 6. 应用启动完成
           └─ 缓存体系使用 JCacheCacheManager(不支持自动创建缓存)

六、运行时调用流程 ------ 注解触发后发生了什么

6.1 AOP 代理机制

Spring Cache 的注解之所以能生效,是因为 AOP(面向切面编程) 机制。

javascript 复制代码
你的代码                          Spring 生成的代理
┌──────────────┐                ┌──────────────────────────────────┐
│              │                │  代理对象(Proxy)                 │
│  UserService │    ──→         │                                  │
│              │                │  getUserById(id) {               │
│              │                │    ① 检查缓存                     │
│              │                │    ② 命中 → 返回缓存值             │
│              │                │    ③ 未命中 → 调用真实方法          │
│              │                │    ④ 将结果写入缓存                │
│              │                │    ⑤ 返回结果                     │
│              │                │  }                               │
└──────────────┘                └──────────────────────────────────┘

6.2 完整调用链(类级别)

当调用 userService.getUserById(1L) 时,完整的类调用链如下:

scss 复制代码
① Controller 调用方法
    │
    ▼
② JDK 动态代理 / CGLIB 代理拦截调用
    │  类:org.springframework.aop.framework.CglibAopProxy
    │  或:org.springframework.aop.framework.JdkDynamicAopProxy
    │
    ▼
③ CacheInterceptor 拦截器执行
    │  类:org.springframework.cache.interceptor.CacheInterceptor
    │  方法:invoke(MethodInvocation)
    │
    ▼
④ CacheAspectSupport 执行核心缓存逻辑
    │  类:org.springframework.cache.interceptor.CacheAspectSupport
    │  方法:execute(CacheOperationInvoker, Object, Method, Object[])
    │
    ├── 4.1 获取方法上的缓存操作(注解信息)
    │      类:org.springframework.cache.interceptor.CacheOperationSource
    │      方法:getCacheOperations(Method, Class<?>)
    │
    ├── 4.2 创建缓存操作上下文
    │      类:org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts
    │
    ├── 4.3 解析需要使用的缓存
    │      类:org.springframework.cache.interceptor.CacheResolver
    │      实现:org.springframework.cache.interceptor.AbstractCacheResolver
    │      方法:resolveCaches(CacheOperationInvocationContext)
    │      │
    │      ├── 4.3.1 从注解获取 cacheNames
    │      │
    │      ├── 4.3.2 调用 CacheManager.getCache(name)
    │      │      │
    │      │      ├── 如果是 RedisCacheManager:
    │      │      │   → 缓存不存在时自动创建 RedisCache
    │      │      │   → 返回 Cache 实例 ✅
    │      │      │
    │      │      └── 如果是 JCacheCacheManager(如何没有提前配置的缓存名称):
    │      │          → 缓存不存在时返回 null
    │      │          → 抛出 IllegalArgumentException ❌ ← 报错点!
    │      │
    │      └── 4.3.3 返回 Cache 集合
    │
    ├── 4.4 查找缓存
    │      类:org.springframework.cache.Cache
    │      方法:get(Object key)
    │
    ├── 4.5 缓存命中 → 直接返回
    │
    └── 4.6 缓存未命中 → 执行真实方法 → 写入缓存
           │
           ├── 执行目标方法
           │
           └── 写入缓存
               类:org.springframework.cache.Cache
               方法:put(Object key, Object value)

6.3 关键源码解析

CacheInterceptor ------ 入口
java 复制代码
// Spring Cache 拦截器,实现了 MethodInterceptor 接口
// 当代理对象的方法被调用时,会先进入这个拦截器
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor {

    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        // 将方法调用封装为 CacheOperationInvoker
        CacheOperationInvoker aopAllianceInvoker = () -> {
            try {
                return invocation.proceed();  // 执行真实方法
            } catch (Throwable ex) {
                throw new CacheOperationInvoker.ThrowableWrapper(ex);
            }
        };
        // 委托给父类 CacheAspectSupport 执行
        return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
    }
}
CacheAspectSupport ------ 核心逻辑
java 复制代码
// 缓存切面支持类,包含缓存操作的核心逻辑
public abstract class CacheAspectSupport extends AbstractCacheInvoker
        implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {

    @Nullable
    protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
        // 1. 获取方法上的缓存操作(注解信息)
        Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);

        // 2. 如果没有缓存操作,直接执行方法
        if (CollectionUtils.isEmpty(operations)) {
            return invoker.invoke();
        }

        // 3. 创建缓存操作上下文
        CacheOperationContexts contexts = new CacheOperationContexts(operations, method, args, target, targetClass);

        // 4. 处理 @CacheEvict(beforeInvocation=true)
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true, NO_RESULT);

        // 5. 查找 @Cacheable 的缓存
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        List<CachePutRequest> cachePutRequests = new LinkedList<>();
        if (cacheHit == null) {  // 缓存未命中
            collectPutRequests(contexts.get(CacheableOperation.class), NO_RESULT, cachePutRequests);
        }

        Object returnValue;
        if (cacheHit != null && !hasCachePut(contexts)) {
            // 缓存命中,直接返回
            returnValue = cacheHit.get();
        } else {
            // 缓存未命中,执行真实方法
            returnValue = invokeOperation(invoker);
        }

        // 6. 处理 @CachePut
        collectPutRequests(contexts.get(CachePutOperation.class), returnValue, cachePutRequests);

        // 7. 执行缓存写入
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(returnValue);
        }

        // 8. 处理 @CacheEvict(beforeInvocation=false)
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, returnValue);

        return returnValue;
    }
}
AbstractCacheResolver ------ 报错发生点
java 复制代码
// 缓存解析器的抽象基类
// 作用:根据方法调用的上下文信息,解析出应该使用哪些 Cache 实例
public abstract class AbstractCacheResolver implements CacheResolver, InitializingBean {

    private CacheManager cacheManager;  // 持有 CacheManager 的引用

    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        // 1. 从注解中获取缓存名称
        Collection<String> cacheNames = getCacheNames(context);
        if (cacheNames == null) {
            return Collections.emptyList();
        }

        // 2. 逐个获取 Cache 实例
        Collection<Cache> caches = new ArrayList<>(cacheNames.size());
        for (String cacheName : cacheNames) {
            // 关键!调用 CacheManager.getCache()
            Cache cache = this.cacheManager.getCache(cacheName);
            if (cache == null) {
                // ⚠️ 如果返回 null,直接抛异常!
                // 这就是报错 "Cannot find cache named 'xxx'" 的地方
                throw new IllegalArgumentException(
                    "Cannot find cache named '" + cacheName + "' for " + context.getOperation());
            }
            caches.add(cache);
        }
        return caches;
    }
}
RedisCacheManager ------ 自动创建缓存
java 复制代码
// Redis 实现的 CacheManager
// 特点:遇到未知的缓存名称时,会自动创建一个新的 RedisCache
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    // 默认缓存配置
    private final RedisCacheConfiguration defaultCacheConfiguration;
    // 每个缓存名称对应的专属配置
    private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;

    @Override
    protected RedisCache getMissingCache(String name) {
        // 遇到未知的缓存名称时,自动创建!
        // 这就是为什么 RedisCacheManager 不会报错的原因
        return createCache(name, defaultCacheConfiguration);
    }

    @Override
    protected Cache lookupCache(String name) {
        Cache cache = super.lookupCache(name);
        if (cache == null) {
            // 缓存不存在,调用 getMissingCache 创建
            cache = getMissingCache(name);
            if (cache != null) {
                this.cacheMap.put(name, cache);  // 放入缓存池
            }
        }
        return cache;
    }
}
JCacheCacheManager ------ 不自动创建缓存
java 复制代码
// JSR-107 (JCache) 规范实现的 CacheManager
// 特点:遇到未知的缓存名称时,返回 null,不会自动创建
public class JCacheCacheManager implements CacheManager {

    private final javax.cache.CacheManager cacheManager;

    @Override
    @Nullable
    public Cache getCache(String name) {
        // 直接从底层 JCache CacheManager 中查找
        javax.cache.Cache<Object, Object> cache = this.cacheManager.getCache(name);
        if (cache == null) {
            // ⚠️ 找不到就返回 null,不会自动创建!
            return null;
        }
        return new JCacheCache(cache);
    }
}

6.4 两种 CacheManager 的行为对比

scss 复制代码
调用 cacheManager.getCache("product:detailConfig")
    │
    ├── RedisCacheManager
    │      │
    │      ├── lookupCache("product:detailConfig")
    │      │   └── 缓存池中没有 → getMissingCache()
    │      │       └── 自动创建 RedisCache → 放入缓存池 → 返回 ✅
    │      │
    │      └── 后续正常执行:查 Redis → 未命中 → 执行方法 → 写入缓存
    │
    └── JCacheCacheManager
           │
           ├── getCache("product:detailConfig")
           │   └── 底层 JCache CacheManager 中没有 → 返回 null
           │
           └── AbstractCacheResolver 中 cache == null
               └── 抛出 IllegalArgumentException ❌
                   "Cannot find cache named 'product:detailConfig'"

总结

Spring Cache 核心概念速查

概念 作用 类比
CacheManager 管理多个缓存实例 图书馆管理员
Cache 一个具体的缓存区域 一个书架
CacheResolver 根据上下文解析缓存 找书架的导航员
@Cacheable 读缓存(未命中时写入) 先查书架,没有再去仓库
@CachePut 写缓存(始终执行方法) 更新书架上的书
@CacheEvict 删缓存 从书架上撤掉书

Spring Boot 缓存自动配置核心流程

复制代码
启动 → CacheAutoConfiguration → 导入所有配置类 → 条件竞争 → 创建 CacheManager

运行时核心调用链

arduino 复制代码
方法调用 → AOP 代理 → CacheInterceptor → CacheAspectSupport
    → CacheResolver → CacheManager.getCache() → Cache.get/put/evict

关键注意事项

  1. 必须加 @EnableCaching,否则缓存注解不生效
  2. 缓存的对象必须实现 Serializable,因为需要序列化存入 Redis
  3. 注意 CacheManager 的竞争,多个缓存组件可能抢夺 CacheManager 的位置
  4. 推荐设置 spring.cache.type=redis,避免自动配置的不确定性
  5. @Cacheablesync=true 可以防止缓存击穿(同一时刻大量请求查同一个 key)
  6. unless = "#result == null" 可以防止缓存空值(除非有意防止缓存穿透)

常见问题排查清单

现象 可能原因 排查方法
缓存注解不生效 未加 @EnableCaching 检查启动类
缓存注解不生效 方法是 private 的 Spring AOP 无法代理私有方法
缓存注解不生效 同一个类内部调用 内部调用不经过代理,缓存不生效
Cannot find cache named xxx CacheManager 不是 RedisCacheManager 用 CacheDebugRunner 诊断
缓存数据乱码 序列化方式不对 配置 RedisCacheConfiguration
缓存 key 不符合预期 SpEL 表达式写错 检查 key 属性
缓存过期时间不对 未配置 spring.cache.type=redis 检查 application.yml
相关推荐
百珏7 小时前
[灰度发布]:全链路透传组件:APM、自研方案与 Java Agent 的实现取舍
后端·设计模式·架构
正在走向自律7 小时前
DISTINCT 去重查询为什么这么慢?聊聊我能理解的几种优化思路
后端
OpsEye7 小时前
数据库连接池爆了,这3个命令能救你一次
运维·数据库·后端
绝知此事7 小时前
【产品更名】通义灵码升级为 Qoder CN:AI 编码助手新时代,附大模型收费与 Spring Boot 支持全对比
人工智能·spring boot·后端·idea·ai编程
~|Bernard|7 小时前
GO语言中哪些类型是可比较类型的(==和!=)
开发语言·后端·golang
用户6757049885028 小时前
Celery 太重了?这可能是你一直在找的 asyncio 任务队列
后端·python·消息队列
Cloud_Shy6188 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十一章 Python 包跟踪器 下篇)
前端·后端·python·数据分析·excel
神奇小汤圆8 小时前
为什么Redis能称霸缓存界?揭秘其每秒10万+读写的核心技术
后端
楼田莉子8 小时前
C++17新特性:结构化绑定/inline变量/if相关的变化
c++·后端·学习