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,然后让CacheResolverCacheManager要合适的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.propertiesapplication.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)

应用场景CacheInterceptorinvoke()方法定义了缓存操作的骨架流程。
代码体现
为什么用模板方法模式?
  • 缓存操作的核心流程是固定的(查缓存→执行方法→更缓存),但具体的缓存操作(如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)

应用场景KeyGeneratorCacheResolver接口的设计。
代码体现
为什么用策略模式?
  • 不同场景下,缓存Key的生成规则可能不同(有的需要包含方法名,有的需要包含用户ID)。
  • 策略模式将Key生成算法封装为不同的KeyGenerator实现类,用户可以根据需求选择或自定义策略。
  • 好处:算法灵活切换,新增Key生成规则无需修改现有代码,符合"开闭原则"。
适用场景借鉴:当有多种算法或规则可供选择时,比如:
  • 排序策略(快速排序、冒泡排序、归并排序)
  • 支付策略(支付宝、微信支付、银行卡支付)
  • 日志输出策略(控制台输出、文件输出、远程输出)

4.2 核心架构思想:背后的设计哲学

4.2.1 抽象与封装:屏蔽差异,聚焦业务

核心思想:通过接口抽象定义缓存操作的标准,封装不同缓存技术的实现细节。
具体体现
  • 定义Cache接口统一缓存操作(get/put/evict),无论底层是Redis还是Caffeine,调用方式一致。
  • 开发者只需面向CacheCacheManager接口编程,无需关心Redis的set命令或Caffeine的put方法。
为什么这么设计?
  • 不同缓存技术的API差异很大(Redis有Jedis/Lettuce,Caffeine有自己的API),直接使用会导致代码与具体技术强耦合。
  • 抽象能降低认知成本:开发者只需学习一套接口,就能操作各种缓存。
  • 抽象能提高代码复用:缓存逻辑(如注解解析、Key生成)可以复用,无需为每种缓存技术重复开发。
从中学习:当需要兼容多种实现时,先定义抽象接口,再封装具体实现。例如:
  • 消息队列抽象(定义MessageQueue接口,封装RabbitMQ、Kafka的差异)
  • 存储抽象(定义Storage接口,封装本地文件、S3、OSS的差异)

4.2.2 约定优于配置:减少冗余,提升效率

核心思想:提供合理的默认实现,用户只需在需要定制时才配置,而非从零开始。
具体体现
  • 默认KeyGeneratorSimpleKeyGenerator,无需配置即可生成基本Key。
  • 默认CacheManager:根据类路径依赖自动选择(有Redis依赖则用RedisCacheManager,否则用ConcurrentMapCacheManager)。
  • 注解默认值:@CacheablecacheNames可通过@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默认的KeyGeneratorSimpleKeyGenerator)生成规则是:
  • 无参数:返回SimpleKey.EMPTY
  • 一个参数:直接用参数本身作为Key
  • 多个参数:创建包含所有参数的SimpleKey对象(重写了equalshashCode
但实际开发中,默认规则可能不够用,比如:
  • 不同方法的参数相同,可能导致Key冲突(比如getUserById(1)getOrderById(1)的Key都是1)。
  • 需要包含方法名或类名来区分不同方法的缓存。
这时候就需要自定义KeyGenerator,比如生成包含类名、方法名和参数的Key:
使用时,通过keyGenerator属性指定使用自定义生成器:

5.2 缓存穿透:如何避免"空查询"压垮数据库?

缓存穿透是指查询一个不存在的数据(如ID=-1的用户),此时缓存不会命中,请求会直接打到数据库,当这类请求量大时,可能压垮数据库。
解决方案:缓存null值。当数据库查询结果为null时,也将其存入缓存,这样后续相同请求会命中缓存,不会再访问数据库。
Spring Boot Cache支持通过配置开启缓存null值:
代码中通过unless属性控制是否缓存null(默认会缓存,除非显式排除):

5.3 缓存雪崩:如何防止缓存集中失效?

缓存雪崩是指大量缓存在同一时间过期,导致大量请求同时穿透到数据库,造成数据库压力骤增。
解决方案
  1. 过期时间加随机值:让缓存的过期时间分散,避免集中过期。
  1. 多级缓存:结合本地缓存和分布式缓存,本地缓存未过期时可作为兜底。
  1. 缓存预热:系统启动时主动加载热点数据到缓存。
以Redis为例,配置带随机值的过期时间:

六、高级实战:自定义扩展与二级缓存实现

6.1 自定义Redis+EhCache二级缓存:兼顾性能与一致性

二级缓存是指同时使用本地缓存(如EhCache/Caffeine)和分布式缓存(如Redis),查询时先查本地缓存,未命中再查Redis,最后查数据库。这样既能利用本地缓存的高性能,又能通过Redis保证分布式一致性。
实现思路
  1. 自定义Cache实现类,封装本地缓存和Redis缓存的操作。
  1. 自定义CacheManager,创建二级缓存实例。
  1. 查询时先查本地缓存,未命中查Redis;更新/删除时同时更新本地缓存和Redis。
完整代码实现
EhCache配置文件(ehcache.xml)
工作流程

6.2 扩展Redis批量缓存操作:提升批量查询性能

默认的Spring Cache注解只支持单条数据的缓存操作,对于批量查询(如List<User> getUsersByIds(List<Long> ids)),需要手动实现批量缓存逻辑,否则会导致"缓存风暴"(多次单条查询缓存)。
实现思路
  1. 自定义批量缓存注解(如@BatchCacheable)。
  1. 实现批量缓存拦截器,解析注解并执行批量查询缓存、批量更新缓存逻辑。
  1. 适配Redis的批量操作(如mget/mset)提升性能。
核心代码实现
批量缓存流程
通过批量缓存操作,原本需要N次缓存查询的批量接口,现在只需1次批量查询,大幅减少Redis交互次数,提升性能。

七、总结:Spring Boot Cache的最佳实践

Spring Boot Cache通过优雅的抽象和注解驱动,极大简化了缓存开发,但要用好它,需要注意以下几点:
  1. 合理选型缓存技术:分布式场景用Redis,本地热点缓存用Caffeine,复杂本地缓存用EhCache。
  1. 避免缓存陷阱:通过缓存null值防止穿透,过期时间加随机值防止雪崩,Key生成规则要唯一。
  1. 善用扩展点:自定义KeyGenerator、CacheManager满足个性化需求,复杂场景实现二级缓存或批量缓存。
  1. 理解设计思想:学习Spring Cache的抽象封装、策略模式、扩展点设计,应用到自己的项目中,提升代码的可扩展性和可维护性。
缓存是一把"双刃剑",用得好能显著提升性能,用不好则可能导致数据不一致、内存溢出等问题。希望本文能帮助你更好地掌握Spring Boot Cache,让缓存成为应用性能的"助推器"而非"绊脚石"。
 
聊聊一个优雅组件配置设计该长啥样:从SpringBoot @Configuration到开闭原则,一次说透!MySQL行值表达式:从“一脸懵”到“玩明白”的实战指南; Mysql RVC,Mysql元组比较,Row Value Constructor/Comparison
Loading...
目录
0%
Honesty
Honesty
花には咲く日があり、人には少年はいない
统计
文章数:
103
目录
0%