【SpringCache 提供的一套基于注解的緩存抽象機制】

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;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85752.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85752.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85752.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HRI-2025 | 大模型驅動的個性化可解釋機器人人機交互研究

作者&#xff1a;Ferran Gebelli 1 ^{1} 1, Lavinia Hriscu 2 ^{2} 2, Raquel Ros 1 ^{1} 1, Sverin Lemaignan 1 ^{1} 1, Alberto Sanfeliu 2 ^{2} 2, Anais Garrell 2 ^{2} 2單位&#xff1a; 1 ^{1} 1PAL Robotics&#xff0c; 2 ^{2} 2IRI (UPC-CSIC)論文標題&#xff1a;P…

Gitee Wiki:重塑關鍵領域軟件研發的知識管理范式

在數字化轉型浪潮席卷全球的當下&#xff0c;關鍵領域軟件研發正面臨前所未有的知識管理挑戰。傳統文檔管理模式的局限性日益凸顯&#xff0c;知識傳承的斷層問題愈發嚴重&#xff0c;團隊協作效率的瓶頸亟待突破。Gitee Wiki作為新一代知識管理平臺&#xff0c;正在通過技術創…

JVM 內存溢出 詳解

內存溢出 內存溢出指的是內存中某一塊區域的使用量超過了允許使用的最大值&#xff0c;從而使用內存時因空間不足而失敗&#xff0c;虛擬機一般會拋出指定的錯誤。 在Java虛擬機中&#xff0c;只有程序計數器不會出現內存溢出的情況&#xff0c;因為每個線程的程序計數器只保…

dvwa8——SQL Injection(Blind)

由題目得這一關用盲注寫 LOW: 先用bp抓包一下 , 看到這low是get提交 , f12打開hackbar 輸入?id1時報錯 嘗試閉合 , 回顯正常 開始注入 1.order by 判斷列數,3的時候開始回顯報錯,所以有兩列 ?id1 order by 2--&SubmitSubmit# 2.無回顯位置可以爆出,我們通過盲注來繼…

探索分布式存儲與通信:去中心化共享及通訊(DSAC)

在當今數字化時代&#xff0c;分布式系統的重要性愈發凸顯。它不僅能提升數據的存儲安全性和可靠性&#xff0c;還能增強通信的效率和隱私性。于是我做了這個去中心化共享及通訊的程序&#xff0c;它構建了一個強大的分布式存儲和通信網絡&#xff0c;下面我們就來詳細了解其實…

ass字幕嵌入mp4帶偏移

# 格式轉化文件&#xff0c;包含多種文件的互相轉化&#xff0c;主要與視頻相關 from pathlib import Path import subprocess import random import os import reclass Utils(object):staticmethoddef get_decimal_part(x: float) -> float:s format(x, .15f) # 格式化為…

05 APP 自動化- Appium 單點觸控 多點觸控

文章目錄 一、單點觸控查看指針的指針位置實現手勢密碼&#xff1a; 二、多點觸控 一、單點觸控 查看指針的指針位置 方便查看手勢密碼-九宮格每個點的坐標 實現手勢密碼&#xff1a; 執行手勢操作&#xff1a; 按壓起點 -> 移動到下一點 -> 依次移動 -> 釋放&am…

【軟件】在 macOS 上安裝 MySQL

在 macOS 上安裝 MySQL 有多種方法&#xff0c;以下是兩種常見的安裝方式&#xff1a;通過 Homebrew 安裝和通過安裝包安裝。以下是詳細的步驟&#xff1a; 一、通過 Homebrew 安裝 MySQL Homebrew 是 macOS 的包管理器&#xff0c;使用它安裝 MySQL 非常方便。 1.安裝 Home…

第11節 Node.js 模塊系統

為了讓Node.js的文件可以相互調用&#xff0c;Node.js提供了一個簡單的模塊系統。 模塊是Node.js 應用程序的基本組成部分&#xff0c;文件和模塊是一一對應的。換言之&#xff0c;一個 Node.js 文件就是一個模塊&#xff0c;這個文件可能是JavaScript 代碼、JSON 或者編譯過的…

力扣熱題100之二叉樹的直徑

題目 給你一棵二叉樹的根節點&#xff0c;返回該樹的 直徑 。 二叉樹的 直徑 是指樹中任意兩個節點之間最長路徑的 長度 。這條路徑可能經過也可能不經過根節點 root 。 兩節點之間路徑的 長度 由它們之間邊數表示。 代碼 方法&#xff1a;遞歸 計算二叉樹的直徑可以理解…

OpenCV CUDA模塊圖像處理------創建CUDA加速的Canny邊緣檢測器對象createCannyEdgeDetector()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 該函數用于創建一個 CUDA 加速的 Canny 邊緣檢測器對象&#xff08;CannyEdgeDetector&#xff09;&#xff0c;可以在 GPU 上高效執行 Canny 邊…

unix/linux,sudo,其內部結構機制

我們現在深入sudo的“引擎室”,探究其內部的結構和運作機制。這就像我們從觀察行星運動,到深入研究萬有引力定律的數學表達和物理內涵一樣,是理解事物本質的關鍵一步。 sudo 的內部結構與機制詳解 sudo 的執行流程可以看作是一系列精心設計的步驟,確保了授權的準確性和安…

什么是 TOML?

&#x1f6e0; Rust 配置文件實戰&#xff1a;TOML 語法詳解與結構體映射&#xff08; 在 Rust 中&#xff0c;Cargo.toml 是每個項目的心臟。它不僅定義了項目的名稱、版本和依賴項&#xff0c;還使用了一種輕巧易讀的配置語言&#xff1a;TOML。 本文將深入解析 TOML 的語法…

react native webview加載本地HTML,解決iOS無法加載成功問題

在react native中使用 “react-native-webview”: “^13.13.5”,加載HTML文件 Android: 將HTML文件放置到android/src/main/assets目錄&#xff0c;訪問 {uri: file:///android_asset/markmap/index.html}ios: 在IOS中可以直接可以直接放在react native項目下&#xff0c;訪問…

數據結構(JAVA版)練習題

&#xff08;題目難易程度與題號順序無關哦&#xff09; 目錄 1、多關鍵字排序 2、集合類的綜合應用問題 3、數組排序 4、球的相關計算問題 5、利用類對象計算日期 6、日期計算問題 7、星期日期的計算 8、計算坐標平面上兩點距離 9、異常處理設計問題 10、Java源文件…

04-redis-分布式鎖-redisson

1 基本概念 百度百科&#xff1a;控制分布式系統之間同步訪問共享資源方式。 在分布式系統中&#xff0c;常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源&#xff0c;那么訪問這些資源的時候&#xff0c;往往需要互斥來防止…

性能優化 - 案例篇:緩存_Guava#LoadingCache設計

文章目錄 Pre引言1. 緩存基本概念2. Guava 的 LoadingCache2.1 引入依賴與初始化2.2 手動 put 與自動加載&#xff08;CacheLoader&#xff09;2.2.1 示例代碼 2.3 緩存移除與監聽&#xff08;invalidate removalListener&#xff09; 3. 緩存回收策略3.1 基于容量的回收&…

使用jstack排查CPU飆升的問題記錄

最近&#xff0c;看到短視頻傳播了一個使用jstack來協助排查CPU飆升的案例。我也是比較感興趣&#xff0c;參考了視頻博主的流程&#xff0c;自己做了下對應案例的實戰演練&#xff0c;在此&#xff0c;想做一下&#xff0c;針對相關問題模擬與排查演練的實戰過程記錄。 案例中…

Sql Server 中常用語句

1.創建用戶數據庫 --創建數據庫 use master --切換到master數據庫 go-- 終止所有與SaleManagerDB數據庫的連接 alter database SaleManagerDB set single_user with rollback immediate goif exists (select * from sysdatabases where nameSaleManagerDB) drop database Sal…

聯通專線賦能,億林網絡裸金屬服務器:中小企業 IT 架構升級優選方案

在當今數字化飛速發展的時代&#xff0c;中小企業面臨著日益增長的業務需求與復雜多變的市場競爭環境。如何構建高效、穩定且具性價比的 IT 架構&#xff0c;成為眾多企業突破發展瓶頸的關鍵所在。而億林網絡推出的 24 核 32G 裸金屬服務器&#xff0c;搭配聯通專線的千兆共享帶…