【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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;搭配联通专线的千兆共享带…