type
status
date
slug
summary
tags
category
icon
password
catalog
sort
缓存是提升应用性能的"利器",尤其是在数据查询频繁、数据库压力大的场景中,用好缓存能让应用响应速度提升数倍甚至数十倍。Spring Boot提供的Cache模块,通过优雅的抽象和注解驱动,让开发者无需深入了解各种缓存技术的细节,就能轻松实现缓存功能。今天我们就从基础概念到实战技巧,全面剖析Spring Boot Cache模块的核心原理和使用方法,尤其会深入探讨其设计模式和架构思想背后的深层逻辑。
一、Spring Boot Cache核心概念:缓存抽象的魅力
提到缓存,你可能会想到Redis、EhCache、Caffeine等具体的缓存技术。这些技术各有特点,但对于开发者来说,直接使用它们会面临一个问题:换一种缓存技术,代码可能就要大改。Spring Boot Cache的核心思想就是**"缓存抽象"**——通过定义统一的接口,屏蔽不同缓存技术的差异,让开发者专注于业务逻辑,而不是具体的缓存实现。
1.1 核心接口:缓存世界的"规则制定者"
Spring Boot Cache定义了一系列核心接口,这些接口就像缓存世界的"规则",无论底层用哪种缓存技术,都要遵守这些规则。理解这些接口,是掌握Spring Boot Cache的关键。
接口名称 | 核心作用 | 通俗理解 |
Cache | 定义了缓存的基本操作(get、put、evict等) | 单个缓存实例,相当于一个"缓存容器" |
CacheManager | 负责创建和管理 Cache 实例 | 缓存的"大管家",管理所有缓存容器 |
CacheResolver | 根据缓存操作的元数据,决定使用哪个 Cache 实例 | 缓存的"导航仪",决定用哪个缓存容器 |
KeyGenerator | 生成缓存的键(Key) | 缓存键的"生成器",给数据生成唯一标识 |
CacheOperation | 封装 @Cacheable 等注解的元数据(如缓存名称、key规则等) | 缓存操作的"说明书",记录要执行的缓存动作 |
CacheInterceptor | 通过AOP拦截方法,执行缓存逻辑(查缓存、更缓存等) | 缓存的"执行者",负责按说明书执行缓存操作 |
这些接口的关系可以用一张架构图来表示:
简单来说,整个流程是这样的:当我们调用一个加了缓存注解的方法时,
CacheInterceptor
会先拦截这个方法,通过CacheOperation
获取注解里的配置信息,再用KeyGenerator
生成缓存Key,然后让CacheResolver
找CacheManager
要合适的Cache
实例,最后通过Cache
实例操作底层的具体缓存(比如Redis)。1.2 注解驱动:一行注解搞定缓存逻辑
Spring Boot Cache最方便的地方就是注解驱动——只需在方法上添加注解,就能自动实现缓存逻辑,无需手动写"查缓存-没命中查数据库-存缓存"的模板代码。常用的缓存注解有这些:
注解名称 | 核心作用 | 使用场景 |
@Cacheable | 方法执行前查缓存,命中则返回缓存值;未命中则执行方法,再把结果存缓存 | 查询操作(如根据ID查用户) |
@CachePut | 方法执行后,将结果存入缓存(不影响方法本身执行) | 更新操作(如更新用户信息后同步更新缓存) |
@CacheEvict | 清除缓存中的指定数据 | 删除操作(如删除用户后清除对应缓存) |
@Caching | 组合多个缓存操作(比如既查缓存又清缓存) | 复杂操作(如查询后需要清除关联缓存) |
@CacheConfig | 类级别配置缓存属性(如默认缓存名称、Key生成器) | 统一配置类中所有方法的缓存属性 |
举个例子,用这些注解实现用户服务的缓存逻辑,代码会非常简洁:
只需几行注解,就完成了缓存的查询、更新、清除逻辑,是不是很简单?这就是Spring Boot Cache的魅力所在。
二、Spring Boot Cache的启动与配置:从"开关"到"定制"
要使用Spring Boot Cache,首先得开启它的功能,然后根据需求配置缓存管理器。这部分我们就来看看Spring Boot Cache是如何启动的,以及如何配置不同的缓存实现。
2.1 @EnableCaching:缓存功能的"总开关"
使用Spring Boot Cache的第一步,是在配置类上添加
@EnableCaching
注解——这就像按下了缓存功能的"启动按钮"。这个注解的作用是告诉Spring:"我要启用缓存功能了,麻烦帮我加载相关的配置"。2.1.1 @EnableCaching的底层逻辑
@EnableCaching
的核心是通过@Import
注解导入了一个叫CachingConfigurationSelector
的类。这个类的作用是根据配置,选择加载不同的缓存配置类。我们来看简化后的源码:CachingConfigurationSelector
会根据mode
属性(默认是PROXY
)选择导入对应的配置类。如果是PROXY
模式,就导入ProxyCachingConfiguration
——这个类是Spring Cache代理模式的核心配置类,里面定义了缓存功能所需的各种核心Bean。2.2 ProxyCachingConfiguration:缓存的"核心配置中心"
ProxyCachingConfiguration
是一个用@Configuration
注解标记的配置类,它的作用是创建缓存功能的核心组件(比如缓存拦截器、缓存增强器等)。我们可以把它理解为缓存的"核心配置中心"。2.2.1 核心Bean的创建过程
ProxyCachingConfiguration
会创建三个关键Bean,它们共同支撑起缓存的运行:这三个Bean的关系可以用流程图表示:
简单来说,
cacheOperationSource
负责"读注解"(解析@Cacheable
等注解的配置),cacheInterceptor
负责"执行逻辑"(按注解配置操作缓存),cacheAdvisor
负责"织入AOP"(让拦截器能拦截到目标方法)。2.3 缓存配置规则:定制你的缓存行为
Spring Boot Cache支持通过配置文件(如
application.properties
或application.yml
)来定制缓存行为。常用的配置项有这些:配置项 | 作用 | 示例 |
spring.cache.type | 指定缓存类型(如redis、ehcache、caffeine等) | spring.cache.type=redis |
spring.cache.cache-names | 预定义缓存名称(启动时就创建这些缓存) | spring.cache.cache-names=users,orders |
spring.cache.redis.time-to-live | Redis缓存默认过期时间 | spring.cache.redis.time-to-live=30m (30分钟) |
spring.cache.redis.key-prefix | Redis缓存Key的前缀 | spring.cache.redis.key-prefix=app1: |
spring.cache.redis.cache-null-values | 是否缓存null值(防止缓存穿透) | spring.cache.redis.cache-null-values=true |
spring.cache.caffeine.spec | Caffeine缓存的配置(如过期时间) | spring.cache.caffeine.spec=expireAfterWrite=5m |
举个Redis缓存的配置示例(
application.yml
):三、缓存技术选型:哪种缓存适合你的场景?
Spring Boot Cache支持多种缓存技术,不同的缓存各有特点,适合不同的场景。选择合适的缓存技术,能让你的应用性能最大化。下面我们对比几种常用的缓存技术,帮你做出选择。
3.1 常用缓存技术对比
缓存技术 | 核心特点 | 优点 | 缺点 | 适用场景 |
Redis | 分布式缓存,基于内存,支持多种数据结构(String、Hash、List等) | 1. 高性能(单节点QPS可达10万+)<br>2. 支持分布式部署,缓存一致性好<br>3. 支持持久化(RDB/AOF)<br>4. 过期策略丰富 | 1. 需要单独部署和维护Redis服务<br>2. 存在网络IO开销(相比本地缓存)<br>3. 大Value存储可能影响性能 | 1. 分布式系统缓存(多服务共享缓存)<br>2. 需要持久化的缓存场景<br>3. 高并发读写场景(如秒杀库存缓存) |
EhCache | 本地缓存,纯Java实现,支持内存+磁盘两级存储 | 1. 无需单独部署,嵌入应用即可<br>2. 支持磁盘持久化,适合大数据量缓存<br>3. 支持复杂的缓存策略(如LRU、LFU) | 1. 不支持分布式(各节点缓存独立,易不一致)<br>2. 磁盘存储性能不如内存<br>3. 配置相对复杂 | 1. 单体应用本地缓存<br>2. 需要磁盘持久化的场景(如报表数据缓存)<br>3. 低并发本地缓存需求 |
Caffeine | 本地缓存,基于Java 8,采用W-TinyLFU淘汰算法 | 1. 性能极佳(Java本地缓存性能天花板)<br>2. API简洁友好,易集成<br>3. 内存占用优化好,命中率高 | 1. 不支持分布式<br>2. 无持久化(重启后缓存丢失)<br>3. 仅支持内存存储 | 1. 本地热点数据缓存(如首页热门商品)<br>2. 高并发读场景(读多写少)<br>3. 临时缓存(无需持久化) |
ConcurrentMapCache | Spring内置缓存,基于 ConcurrentHashMap 实现 | 1. 零依赖,Spring框架自带<br>2. 轻量简单,无需配置 | 1. 功能简陋(无过期策略、无持久化)<br>2. 缓存数据常驻内存,可能OOM<br>3. 不适合生产环境 | 1. 测试环境临时使用<br>2. 极简单的本地缓存场景(如开发调试) |
3.2 选型建议:按需选择,组合最优
- 分布式系统优先选Redis:分布式场景下,Redis的分布式一致性和高性能是其他缓存无法替代的。例如微服务架构中,用户登录信息、商品库存等需要跨服务共享的缓存,必须用Redis。
- 本地缓存首选Caffeine:如果是单体应用或需要本地热点缓存,Caffeine的性能优势明显。例如电商首页的热门商品列表,访问量极大且变化不频繁,用Caffeine缓存能显著减少数据库压力。
- 复杂场景组合使用:实际开发中常组合多种缓存,比如"Redis+本地缓存"二级缓存:本地缓存减轻Redis压力,Redis保证分布式一致性。例如:用户信息先查本地Caffeine缓存,未命中再查Redis,都未命中再查数据库,查询后同时更新Redis和本地缓存。
四、Spring Boot Cache的设计模式与架构思想:为什么这么设计?
Spring Boot Cache的底层实现蕴含了丰富的设计模式和架构思想,这些设计让它具备高扩展性、低耦合的特点。深入理解这些设计,不仅能帮你更好地使用Spring Cache,更能在自己的项目中借鉴这些思想。
4.1 核心设计模式:解决什么问题?为什么选它?
4.1.1 模板方法模式(Template Method Pattern)
应用场景:
CacheInterceptor
的invoke()
方法定义了缓存操作的骨架流程。代码体现:
为什么用模板方法模式?
- 缓存操作的核心流程是固定的(查缓存→执行方法→更缓存),但具体的缓存操作(如
get
/put
)因底层缓存技术(Redis/EhCache)而异。
- 模板方法模式将不变的流程骨架定义在父类(
CacheInterceptor
),将可变的具体实现延迟到子类或接口实现(Cache
接口的不同实现类)。
- 好处:保证流程一致性,同时允许灵活替换底层缓存实现,符合"开闭原则"(对扩展开放,对修改关闭)。
适用场景借鉴:当业务流程固定但具体步骤实现可变时,比如:
- 支付流程(固定步骤:验证→扣款→通知,但不同支付方式实现不同)
- 数据导出流程(固定步骤:查询→转换→导出,但导出格式不同)
4.1.2 工厂模式(Factory Pattern)
应用场景:
CacheManager
作为工厂,负责创建和管理Cache
实例。代码体现:
为什么用工厂模式?
- 不同缓存技术的
Cache
实例创建逻辑不同(Redis需要连接Redis服务器,Caffeine需要初始化本地缓存容器)。
- 工厂模式将
Cache
的创建逻辑封装在CacheManager
中,上层代码(如CacheInterceptor
)只需调用getCache()
即可获取缓存实例,无需关心具体创建细节。
- 好处:解耦对象创建和使用,更换缓存技术时只需替换
CacheManager
,上层代码无需修改。
适用场景借鉴:当需要创建多种类型的对象,且创建逻辑复杂时,比如:
- 日志工厂(创建FileLog、ConsoleLog、RemoteLog等不同日志实例)
- 数据库连接工厂(创建MySQL、PostgreSQL、Oracle等不同数据库连接)
4.1.3 代理模式(Proxy Pattern)
应用场景:通过AOP代理为目标方法织入缓存逻辑。
实现逻辑:
Spring通过
cacheAdvisor
(缓存增强器)为加了@Cacheable
等注解的方法创建代理对象,当调用目标方法时,代理对象会先执行缓存逻辑(查缓存、更缓存),再调用目标方法。为什么用代理模式?
- 缓存逻辑是横切关注点(需要在多个方法中重复执行),如果直接写在业务方法中,会导致代码冗余和耦合。
- 代理模式可以在不修改目标方法代码的前提下,为方法添加额外功能(缓存逻辑)。
- 好处:业务代码与缓存逻辑分离,提高代码复用性和可维护性。
适用场景借鉴:需要为方法添加通用横切功能时,比如:
- 日志记录(调用方法前后记录日志)
- 权限校验(调用方法前检查权限)
- 事务管理(方法执行前后开启/提交事务)
4.1.4 策略模式(Strategy Pattern)
应用场景:
KeyGenerator
和CacheResolver
接口的设计。代码体现:
为什么用策略模式?
- 不同场景下,缓存Key的生成规则可能不同(有的需要包含方法名,有的需要包含用户ID)。
- 策略模式将Key生成算法封装为不同的
KeyGenerator
实现类,用户可以根据需求选择或自定义策略。
- 好处:算法灵活切换,新增Key生成规则无需修改现有代码,符合"开闭原则"。
适用场景借鉴:当有多种算法或规则可供选择时,比如:
- 排序策略(快速排序、冒泡排序、归并排序)
- 支付策略(支付宝、微信支付、银行卡支付)
- 日志输出策略(控制台输出、文件输出、远程输出)
4.2 核心架构思想:背后的设计哲学
4.2.1 抽象与封装:屏蔽差异,聚焦业务
核心思想:通过接口抽象定义缓存操作的标准,封装不同缓存技术的实现细节。
具体体现:
- 定义
Cache
接口统一缓存操作(get
/put
/evict
),无论底层是Redis还是Caffeine,调用方式一致。
- 开发者只需面向
Cache
和CacheManager
接口编程,无需关心Redis的set
命令或Caffeine的put
方法。
为什么这么设计?
- 不同缓存技术的API差异很大(Redis有
Jedis
/Lettuce
,Caffeine有自己的API),直接使用会导致代码与具体技术强耦合。
- 抽象能降低认知成本:开发者只需学习一套接口,就能操作各种缓存。
- 抽象能提高代码复用:缓存逻辑(如注解解析、Key生成)可以复用,无需为每种缓存技术重复开发。
从中学习:当需要兼容多种实现时,先定义抽象接口,再封装具体实现。例如:
- 消息队列抽象(定义
MessageQueue
接口,封装RabbitMQ、Kafka的差异)
- 存储抽象(定义
Storage
接口,封装本地文件、S3、OSS的差异)
4.2.2 约定优于配置:减少冗余,提升效率
核心思想:提供合理的默认实现,用户只需在需要定制时才配置,而非从零开始。
具体体现:
- 默认
KeyGenerator
:SimpleKeyGenerator
,无需配置即可生成基本Key。
- 默认
CacheManager
:根据类路径依赖自动选择(有Redis依赖则用RedisCacheManager
,否则用ConcurrentMapCacheManager
)。
- 注解默认值:
@Cacheable
的cacheNames
可通过@CacheConfig
类级别统一配置,无需每个方法重复写。
为什么这么设计?
- 大多数场景下,默认配置已经能满足需求,强制配置会增加开发负担。
- 约定能减少决策成本:开发者无需纠结"Key生成器用哪个",直接用默认即可。
- 约定不排斥定制:当默认不满足需求时,用户可通过自定义Bean覆盖默认配置。
从中学习:设计框架或组件时,优先提供"开箱即用"的默认方案,同时预留定制入口。例如:
- ORM框架(默认主键生成策略,允许自定义)
- Web框架(默认端口、默认参数解析,允许配置修改)
4.2.3 扩展点设计:开放扩展,拥抱变化
核心思想:通过预留扩展点,允许用户根据需求自定义组件,而无需修改框架源码。
具体体现:
- 自定义
KeyGenerator
:实现KeyGenerator
接口并注册为Bean,即可替代默认实现。
- 自定义
CacheManager
:创建自定义CacheManager
Bean,Spring会自动使用它而非默认实现。
- 自定义
CacheErrorHandler
:实现缓存异常处理逻辑(如Redis宕机时降级到数据库)。
为什么这么设计?
- 框架无法预见所有业务场景(比如特殊的Key生成规则、复杂的缓存异常处理)。
- 扩展点能让框架适应多样化需求,延长生命周期。
- 扩展点遵循"好莱坞原则":"不要找我们,我们会找你"——框架主动发现并使用用户的扩展组件。
从中学习:设计组件时,识别可能变化的部分并抽象为扩展点。例如:
- 权限框架:预留自定义权限校验扩展点
- 任务调度:预留任务执行前/后扩展点
4.2.4 条件化配置:智能适配,减少配置
核心思想:根据环境自动选择合适的配置,减少手动配置成本。
具体体现:
Spring Boot的
CacheAutoConfiguration
通过@Conditional
注解实现条件化配置:为什么这么设计?
- 不同环境的依赖不同(开发环境可能用Caffeine,生产环境用Redis),手动切换配置繁琐易出错。
- 条件化配置能自动适配环境,开发者无需关心"当前用什么缓存",框架会智能选择。
从中学习:当组件需要在不同环境/依赖下表现不同时,用条件化配置自动适配。例如:
- 日志配置(有Logback依赖用Logback,有Log4j依赖用Log4j)
- 数据库配置(有MySQL依赖用MySQL驱动,有PostgreSQL依赖用PostgreSQL驱动)
五、Spring Boot Cache实战技巧:隐藏的细节与坑点
使用Spring Boot Cache时,有些细节如果不注意,可能会导致缓存失效、数据不一致等问题。下面我们就来聊聊这些容易踩的坑和实用技巧。
5.1 缓存Key的生成:别让Key"悄悄"出错
缓存的Key是定位缓存数据的关键,如果Key生成不符合预期,缓存就会失效。Spring Boot Cache默认的
KeyGenerator
(SimpleKeyGenerator
)生成规则是:- 无参数:返回
SimpleKey.EMPTY
- 一个参数:直接用参数本身作为Key
- 多个参数:创建包含所有参数的
SimpleKey
对象(重写了equals
和hashCode
)
但实际开发中,默认规则可能不够用,比如:
- 不同方法的参数相同,可能导致Key冲突(比如
getUserById(1)
和getOrderById(1)
的Key都是1)。
- 需要包含方法名或类名来区分不同方法的缓存。
这时候就需要自定义KeyGenerator,比如生成包含类名、方法名和参数的Key:
使用时,通过
keyGenerator
属性指定使用自定义生成器:5.2 缓存穿透:如何避免"空查询"压垮数据库?
缓存穿透是指查询一个不存在的数据(如ID=-1的用户),此时缓存不会命中,请求会直接打到数据库,当这类请求量大时,可能压垮数据库。
解决方案:缓存null值。当数据库查询结果为null时,也将其存入缓存,这样后续相同请求会命中缓存,不会再访问数据库。
Spring Boot Cache支持通过配置开启缓存null值:
代码中通过
unless
属性控制是否缓存null(默认会缓存,除非显式排除):5.3 缓存雪崩:如何防止缓存集中失效?
缓存雪崩是指大量缓存在同一时间过期,导致大量请求同时穿透到数据库,造成数据库压力骤增。
解决方案:
- 过期时间加随机值:让缓存的过期时间分散,避免集中过期。
- 多级缓存:结合本地缓存和分布式缓存,本地缓存未过期时可作为兜底。
- 缓存预热:系统启动时主动加载热点数据到缓存。
以Redis为例,配置带随机值的过期时间:
六、高级实战:自定义扩展与二级缓存实现
6.1 自定义Redis+EhCache二级缓存:兼顾性能与一致性
二级缓存是指同时使用本地缓存(如EhCache/Caffeine)和分布式缓存(如Redis),查询时先查本地缓存,未命中再查Redis,最后查数据库。这样既能利用本地缓存的高性能,又能通过Redis保证分布式一致性。
实现思路:
- 自定义
Cache
实现类,封装本地缓存和Redis缓存的操作。
- 自定义
CacheManager
,创建二级缓存实例。
- 查询时先查本地缓存,未命中查Redis;更新/删除时同时更新本地缓存和Redis。
完整代码实现:
EhCache配置文件(ehcache.xml):
工作流程:
6.2 扩展Redis批量缓存操作:提升批量查询性能
默认的Spring Cache注解只支持单条数据的缓存操作,对于批量查询(如
List<User> getUsersByIds(List<Long> ids)
),需要手动实现批量缓存逻辑,否则会导致"缓存风暴"(多次单条查询缓存)。实现思路:
- 自定义批量缓存注解(如
@BatchCacheable
)。
- 实现批量缓存拦截器,解析注解并执行批量查询缓存、批量更新缓存逻辑。
- 适配Redis的批量操作(如
mget
/mset
)提升性能。
核心代码实现:
批量缓存流程:
通过批量缓存操作,原本需要N次缓存查询的批量接口,现在只需1次批量查询,大幅减少Redis交互次数,提升性能。
七、总结:Spring Boot Cache的最佳实践
Spring Boot Cache通过优雅的抽象和注解驱动,极大简化了缓存开发,但要用好它,需要注意以下几点:
- 合理选型缓存技术:分布式场景用Redis,本地热点缓存用Caffeine,复杂本地缓存用EhCache。
- 避免缓存陷阱:通过缓存null值防止穿透,过期时间加随机值防止雪崩,Key生成规则要唯一。
- 善用扩展点:自定义KeyGenerator、CacheManager满足个性化需求,复杂场景实现二级缓存或批量缓存。
- 理解设计思想:学习Spring Cache的抽象封装、策略模式、扩展点设计,应用到自己的项目中,提升代码的可扩展性和可维护性。
缓存是一把"双刃剑",用得好能显著提升性能,用不好则可能导致数据不一致、内存溢出等问题。希望本文能帮助你更好地掌握Spring Boot Cache,让缓存成为应用性能的"助推器"而非"绊脚石"。
- 作者:Honesty
- 链接:https://blog.hehouhui.cn/archives/spring-boot-cache-cacheable-redis-optimization-guide
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。