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: 8080spring:cache:type: caffeinecache-names: usercaffeine: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 表達式定義緩存 keycondition = "#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 緩存。**/@Beanpublic 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 方法:@Overrideprotected 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 {@Beanpublic 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 {@Autowiredprivate 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;}
}