Spring 缓存(Spring Cache)是 Spring 提供的一套基于注解的缓存抽象机制,常用于提升系统性能、减少重复查询数据库或接口调用。
✅ 一、基本原理
Spring Cache 通过对方法的返回结果进行缓存,后续相同参数的调用将直接从缓存中读取,而不是再次执行方法。
常用的注解:
注解 | 说明 |
---|---|
@EnableCaching |
开启缓存功能 |
@Cacheable |
有缓存则用缓存,无缓存则调用方法并缓存结果 |
@CachePut |
每次执行方法,并将返回结果放入缓存(更新缓存) |
@CacheEvict |
清除缓存 |
@Caching |
组合多个缓存操作注解 |
✅ 二、使用示例
1. 添加依赖(使用 Caffeine 举例)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2. 启用缓存
@SpringBootApplication
@EnableCaching // 启用缓存功能注解
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3. 使用缓存注解
@Service
public class UserService {
// 第一次调用查询数据库,结果会被缓存
@Cacheable(cacheNames = "user", key = "#id")
public User getUserById(Long id) {
System.out.println("查询数据库中的用户信息");
return userMapper.selectById(id);
}
// 更新用户后,缓存也要更新
@CachePut(cacheNames = "user", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user;
}
// 删除用户时,清除缓存
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
}
4. 配置缓存(application.yml)
spring:
cache:
cache-names: user # 作用:定义一个或多个缓存名称(缓存空间),在注解如 @Cacheable(cacheNames="user") 中引用。 示例含义:你这里定义了一个缓存名称叫 user,用于用户相关数据缓存。
caffeine:
# 作用:配置 Caffeine 缓存的参数,使用一种类似 Java 配置的 DSL 风格字符串,和 Caffeine.newBuilder().xxx() 一一对应。
# maximumSize=1000 设置缓存最大条目数为 1000,超过后触发淘汰(基于 W-TinyLFU)
# expireAfterWrite=60s 写入后 60 秒过期(不管有没有被访问)
spec: maximumSize=1000,expireAfterWrite=60s
✅ 三、缓存存储方案
Spring Cache 是抽象接口,底层可接入多种缓存方案:
方案 | 特点 |
---|---|
Caffeine | 本地缓存,性能极高,适合单体应用 |
EhCache | 本地缓存,功能丰富但不如 Caffeine 快 |
Redis | 分布式缓存,适合集群部署、高并发环境 |
Guava | 轻量但已不推荐,Caffeine 是它的替代者 |
✅ 四、进阶功能
条件缓存:@Cacheable(condition = “#id > 100”)
缓存为空不存:unless = “#result == null”
组合注解:@Caching(cacheable = {…}, evict = {…})
手动缓存:使用 CacheManager 操作缓存对象
✅ 五、总结
功能场景 | 建议使用 |
---|---|
本地缓存 | Caffeine |
分布式缓存 | Redis |
单体轻量项目 | Spring Cache + Caffeine |
高并发分布式系统 | Redis + 自定义注解 |
✅ 六、实战
一个完整的 Spring Boot 项目示例,集成 Spring Cache + Caffeine,模拟一个 用户信息查询缓存的业务场景。
🧱 项目结构(简化单模块)
spring-cache-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/cache/
│ │ │ ├── CacheApplication.java
│ │ │ ├── controller/
│ │ │ │ └── UserController.java
│ │ │ ├── service/
│ │ │ │ └── UserService.java
│ │ │ └── model/
│ │ │ └── User.java
│ └── resources/
│ └── application.yml
1️⃣ 引入依赖(pom.xml)
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-cache-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
</project>
2️⃣ 启动类 CacheApplication.java
package com.example.cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 开启缓存注解支持
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
3️⃣ 用户模型 User.java
package com.example.cache.model;
public class User {
private Long id;
private String name;
private String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// getter、setter省略
}
4️⃣ 服务类 UserService.java
package com.example.cache.service;
import com.example.cache.model.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 模拟从数据库获取数据
@Cacheable(cacheNames = "user", key = "#id")
public User getUserById(Long id) {
System.out.println("❗查询数据库获取用户信息");
return new User(id, "User" + id, "user" + id + "@example.com");
}
// 更新用户信息并更新缓存
@CachePut(cacheNames = "user", key = "#user.id")
public User updateUser(User user) {
System.out.println("🔄更新用户并刷新缓存");
return user;
}
// 删除用户缓存
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
System.out.println("❌删除缓存");
}
}
5️⃣ 控制器 UserController.java
package com.example.cache.controller;
import com.example.cache.model.User;
import com.example.cache.service.UserService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
@PutMapping("/")
public User updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return "deleted";
}
}
6️⃣ 配置文件 application.yml
server:
port: 8080
spring:
cache:
type: caffeine
cache-names: user
caffeine:
spec: maximumSize=1000,expireAfterWrite=60s
✅ 测试流程
1.第一次请求:GET /users/1
→ 控制台输出“查询数据库获取用户信息”
2.第二次请求:GET /users/1
→ 不再输出,直接使用缓存结果
3.更新用户:PUT /users,提交 JSON:
{ "id": 1, "name": "新名字", "email": "new@example.com" }
4.删除缓存:DELETE /users/1
→ 控制台输出“删除缓存”
✅ 七、Cache注解详解
✅ @Cacheable 参数详解(用于读取缓存)
@Cacheable(
value = "user", // 指定缓存的名称(可以是多个),即 cacheNames 的别名
key = "#id", // SpEL 表达式定义缓存 key
condition = "#id > 0", // 满足条件时才缓存
unless = "#result == null", // 返回值不为 null 才缓存
sync = false // 是否同步加载(避免缓存击穿)
)
public User getUserById(Long id) { ... }
参数 | 说明 |
---|---|
value / cacheNames |
缓存名称,对应 @EnableCaching 配置的缓存管理器(CacheManager)中定义的缓存空间 |
key |
缓存 key,使用 Spring Expression Language(SpEL)表达式(如:#id , #user.name ) |
keyGenerator |
指定 key 生成器(和 key 二选一) |
condition |
缓存条件:满足时才执行缓存,如 #id != null |
unless |
排除条件:结果满足时不缓存,如 #result == null |
sync |
是否启用同步缓存(防止缓存击穿,多线程同时查同一 key)【仅限某些缓存实现支持,如 Caffeine 支持】 |
✅ @CachePut 参数详解(用于更新缓存)
@CachePut(
value = "user",
key = "#user.id"
)
public User updateUser(User user) { ... }
与 @Cacheable 基本相同,但始终执行方法体并更新缓存
适用于“更新数据库并同步更新缓存”的场景
✅ @CacheEvict 参数详解(用于删除缓存)
@CacheEvict(
value = "user",
key = "#id",
condition = "#id != null",
beforeInvocation = false
)
public void deleteUser(Long id) { ... }
参数 | 说明 |
---|---|
value |
缓存名 |
key |
指定要删除的 key |
allEntries |
是否清除所有缓存项,如:true 表示清空整个 cache |
beforeInvocation |
是否在方法执行前清除缓存,默认是 false(即执行后才清除) |
常见组合用法: |
@CacheEvict(value = "user", allEntries = true)
public void clearCache() { }
🔄 多个注解组合:@Caching
如果你想组合多个缓存注解(如读一个,清除另一个),可以使用 @Caching:
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#id")
},
evict = {
@CacheEvict(value = "userList", allEntries = true)
}
)
public User getUserById(Long id) { ... }
📌 SpEL 表达式说明
表达式 | 说明 |
---|---|
#p0 / #a0 |
第一个参数 |
#id |
名称为 id 的参数 |
#user.name |
参数 user 的 name 属性 |
#result |
方法返回值(only unless ) |
✅ 示例回顾
@Cacheable(value = "user", key = "#id", unless = "#result == null")
public User getUser(Long id) { ... }
@CachePut(value = "user", key = "#user.id")
public User updateUser(User user) { ... }
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) { ... }
✅ 八、使用细节详解
⚙️ 1. Cache 配置类
springboot 可以有properties配置方式,改成bean方式配置
✅ 使用 Java Config 自定义 Caffeine Cache
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
/**
* 创建 Caffeine 缓存管理器
* 自定义一个 Spring 的缓存管理器 CacheManager,
* 当缓存名称为 "user" 时,使用手动创建的 CaffeineCache 实例;
* 否则使用默认的内存 Map 缓存。
*
*/
@Bean
public CacheManager cacheManager() {
CaffeineCache userCache = new CaffeineCache("user", // 创建了一个名为 user 的缓存实例,使用了 Caffeine 提供的构建器
Caffeine.newBuilder()
.initialCapacity(100) // 缓存容器的初始大小,优化扩容性能
.maximumSize(1000) // 最多缓存 1000 条记录,超出会使用 Caffeine 的 W-TinyLFU 淘汰算法移除最不常用的条目。
.expireAfterWrite(60, TimeUnit.SECONDS) // 写入后 60 秒自动过期(不管是否访问)。
.recordStats() // 启用统计功能(命中率、缓存数量等,调试和监控可用)。
.build()); // 最终构建出一个 Cache<Object, Object>。
// Caffeine 缓存被包装成 Spring 的 CaffeineCache 实例(Spring 使用自己的缓存接口 org.springframework.cache.Cache 进行统一抽象)。
return new ConcurrentMapCacheManager() { // 创建了一个匿名内部类 ConcurrentMapCacheManager(Spring 默认的基于内存的缓存管理器),并重写了其 createConcurrentMapCache 方法:
@Override
protected Cache createConcurrentMapCache(final String name) {
// 每当系统使用 @Cacheable(cacheNames = "user") 时 会触发 createConcurrentMapCache("user") 判断名称是否是 "user",是就返回我们手动构建的 CaffeineCache。
if ("user".equals(name)) {
return userCache;
}
// 如果是其他缓存名,则走父类默认实现(使用 ConcurrentHashMap 的简单内存缓存,不带过期等特性)。
return super.createConcurrentMapCache(name);
}
};
}
}
✅ 总结:使用 Bean 的优点
优点 | 说明 |
---|---|
✅ 更灵活 | 可用 Java 代码动态定义缓存逻辑 |
✅ 无需写配置文件 | 统一管理更清晰 |
✅ 支持多个缓存策略 | 每个缓存可用不同的配置 |
🧠 提示:如何支持多个不同策略的 Caffeine 缓存?
要实现 Spring Cache + Caffeine 中不同缓存名使用不同策略的配置方式,咱们可以改写配置,使其更通用且可扩展 —— 比如:
✅ 多缓存名,不同策略的 Caffeine 缓存管理器
👇 示例:每个缓存名对应一个不同的策略
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// 构建不同的缓存实例
Map<String, CaffeineCache> cacheMap = new HashMap<>();
// user 缓存:60秒后过期,最大1000条
cacheMap.put("user", new CaffeineCache("user",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.build()));
// product 缓存:5分钟过期,最大500条
cacheMap.put("product", new CaffeineCache("product",
Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build()));
// order 缓存:10分钟后失效,最大200条
cacheMap.put("order", new CaffeineCache("order",
Caffeine.newBuilder()
.maximumSize(200)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build()));
// 创建一个自定义 CacheManager,支持上面这些策略
return new SimpleCacheManager() {{
setCaches(new ArrayList<>(cacheMap.values()));
}};
}
}
✅ 总结对比
配置方式 | 特点 |
---|---|
application.yml 配置 |
简单、适合统一策略 |
自定义 CacheManager Bean |
更灵活、支持不同缓存名自定义策略,适合中大型项目需求 |
✅ recordStats 查看
一、如何启用
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.recordStats() // ✅ 开启统计
.build();
二、如何获取统计数据
@Autowired
private CacheManager cacheManager;
public void printUserCacheStats() {
CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache("user");
com.github.benmanes.caffeine.cache.Cache<?, ?> nativeCache = caffeineCache.getNativeCache();
CacheStats stats = nativeCache.stats();
System.out.println("命中次数:" + stats.hitCount());
System.out.println("未命中次数:" + stats.missCount());
System.out.println("命中率:" + stats.hitRate());
System.out.println("加载成功次数:" + stats.loadSuccessCount());
System.out.println("加载失败次数:" + stats.loadFailureCount());
System.out.println("平均加载时间:" + stats.averageLoadPenalty() + "ns");
System.out.println("被驱逐次数:" + stats.evictionCount());
}
三、如果你想实时查看:建议加个接口
@RestController
@RequestMapping("/cache")
public class CacheStatsController {
@Autowired
private CacheManager cacheManager;
@GetMapping("/stats/{name}")
public Map<String, Object> getCacheStats(@PathVariable String name) {
CaffeineCache cache = (CaffeineCache) cacheManager.getCache(name);
com.github.benmanes.caffeine.cache.Cache<?, ?> nativeCache = cache.getNativeCache();
CacheStats stats = nativeCache.stats();
Map<String, Object> result = new HashMap<>();
result.put("hitCount", stats.hitCount());
result.put("missCount", stats.missCount());
result.put("hitRate", stats.hitRate());
result.put("evictionCount", stats.evictionCount());
result.put("loadSuccessCount", stats.loadSuccessCount());
result.put("loadFailureCount", stats.loadFailureCount());
result.put("averageLoadPenalty(ns)", stats.averageLoadPenalty());
return result;
}
}