Spring 缓存(Spring Cache)是 Spring 提供的一套基于注解的缓存抽象机制,常用于提升系统性能、减少重复查询数据库或接口调用。
✅ 一、基本原理
Spring Cache 通过对方法的返回结果进行缓存,后续相同参数的调用将直接从缓存中读取,而不是再次执行方法。
常用的注解:
| 注解 |
说明 |
@EnableCaching |
开启缓存功能 |
@Cacheable |
有缓存则用缓存,无缓存则调用方法并缓存结果 |
@CachePut |
每次执行方法,并将返回结果放入缓存(更新缓存) |
@CacheEvict |
清除缓存 |
@Caching |
组合多个缓存操作注解 |
✅ 二、使用示例
1. 添加依赖(使用 Caffeine 举例)
1 2 3 4 5 6 7 8 9
| <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. 启用缓存
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableCaching public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
|
3. 使用缓存注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @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)
1 2 3 4 5 6 7 8
| spring: cache: cache-names: user caffeine: 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,模拟一个 用户信息查询缓存的业务场景。
🧱 项目结构(简化单模块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; }
}
|
4️⃣ 服务类 UserService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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
1 2 3 4 5 6 7 8 9 10
| 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:
1
| { "id": 1, "name": "新名字", "email": "new@example.com" }
|
4.删除缓存:DELETE /users/1
→ 控制台输出“删除缓存”
✅ 七、Cache注解详解
✅ @Cacheable 参数详解(用于读取缓存)
1 2 3 4 5 6 7 8
| @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 参数详解(用于更新缓存)
1 2 3 4 5 6
| @CachePut( value = "user", key = "#user.id" ) public User updateUser(User user) { ... }
|
与 @Cacheable 基本相同,但始终执行方法体并更新缓存
适用于“更新数据库并同步更新缓存”的场景
✅ @CacheEvict 参数详解(用于删除缓存)
1 2 3 4 5 6 7 8
| @CacheEvict( value = "user", key = "#id", condition = "#id != null", beforeInvocation = false ) public void deleteUser(Long id) { ... }
|
| 参数 |
说明 |
value |
缓存名 |
key |
指定要删除的 key |
allEntries |
是否清除所有缓存项,如:true 表示清空整个 cache |
beforeInvocation |
是否在方法执行前清除缓存,默认是 false(即执行后才清除) |
| 常见组合用法: |
|
1 2 3
| @CacheEvict(value = "user", allEntries = true) public void clearCache() { }
|
🔄 多个注解组合:@Caching
如果你想组合多个缓存注解(如读一个,清除另一个),可以使用 @Caching:
1 2 3 4 5 6 7 8 9 10
| @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) |
✅ 示例回顾
1 2 3 4 5 6 7 8 9
| @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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 {
@Bean public CacheManager cacheManager() { CaffeineCache userCache = new CaffeineCache("user", Caffeine.newBuilder() .initialCapacity(100) .maximumSize(1000) .expireAfterWrite(60, TimeUnit.SECONDS) .recordStats() .build());
return new ConcurrentMapCacheManager() {
@Override protected Cache createConcurrentMapCache(final String name) { if ("user".equals(name)) { return userCache; } return super.createConcurrentMapCache(name); } }; } }
|
✅ 总结:使用 Bean 的优点
| 优点 |
说明 |
| ✅ 更灵活 |
可用 Java 代码动态定义缓存逻辑 |
| ✅ 无需写配置文件 |
统一管理更清晰 |
| ✅ 支持多个缓存策略 |
每个缓存可用不同的配置 |
🧠 提示:如何支持多个不同策略的 Caffeine 缓存?
要实现 Spring Cache + Caffeine 中不同缓存名使用不同策略的配置方式,咱们可以改写配置,使其更通用且可扩展 —— 比如:
✅ 多缓存名,不同策略的 Caffeine 缓存管理器
👇 示例:每个缓存名对应一个不同的策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Configuration @EnableCaching public class CacheConfig {
@Bean public CacheManager cacheManager() { Map<String, CaffeineCache> cacheMap = new HashMap<>();
cacheMap.put("user", new CaffeineCache("user", Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(60, TimeUnit.SECONDS) .build()));
cacheMap.put("product", new CaffeineCache("product", Caffeine.newBuilder() .maximumSize(500) .expireAfterWrite(5, TimeUnit.MINUTES) .build()));
cacheMap.put("order", new CaffeineCache("order", Caffeine.newBuilder() .maximumSize(200) .expireAfterWrite(10, TimeUnit.MINUTES) .build()));
return new SimpleCacheManager() {{ setCaches(new ArrayList<>(cacheMap.values())); }}; } }
|
✅ 总结对比
| 配置方式 |
特点 |
application.yml 配置 |
简单、适合统一策略 |
自定义 CacheManager Bean |
更灵活、支持不同缓存名自定义策略,适合中大型项目需求 |
✅ recordStats 查看
一、如何启用
1 2 3 4 5 6
| Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(60, TimeUnit.SECONDS) .recordStats() .build();
|
二、如何获取统计数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @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()); }
|
三、如果你想实时查看:建议加个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @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; } }
|