

🌈个人首页: 神马都会亿点点的毛毛张

毛毛张今天要介绍的是本地缓存之王!Caffeine!SpringBoot整合Caffeine本地缓存及Spring Cache注解的使用
文章目录
- 1.Caffeine本地缓存
- 2.SpringBoot集成Caffeine
- 2.1 SpringBoot集成Caffeine方式1-推荐
- 2.1.1 创建项目
- 2.1.2 导入依赖
- 2.1.3 配置文件application.yaml
- 2.1.4 实体类UserInfo
- 2.1.5 配置缓存配置类CacheManagerConfig
- 2.1.6 定义服务接口类和实现类
- 2.1.7 控制层
- 2.1.8 启动类
- 2.2 SpringBoot集成Caffeine方式2
- 2.2.1 创建项目
- 2.2.2 导入依赖
- 2.2.3 配置文件application.yaml
- 2.2.4 实体类UserInfo
- 2.2.5 配置缓存配置类CacheManagerConfig
- 2.2.6 定义服务接口类和实现类
- 2.1.7 控制层
- 2.1.8 启动类
- 2.3 接口测试
- 3.配置详解
- 3.1 配置依赖
- 3.2 配置Caffeine缓存管理器
- 3.2.1 通过配置类的方式配置
- Caffeine 配置说明
- 3.2.2 通过配置文件的方式配置-不推荐
- 3.2.3 总结
- 3.3 Spring Cache框架注解介绍和使用
- 3.3.1 @EnableCaching
- 3.3.2 @Cacheable
- 注解简介
- **简单使用案例**
- 案例1:
- 案例2:
- 案例三:
- 3.3.3 @CachePut
- 注解简介
- 简单使用案例
- 3.3.4 @CacheEvict
- 注解简介
- 简单使用案例
- 3.3.5 @Caching
- 注解简介
- 简单使用案例
- 3.3.6 @CacheConfig
- 4.可能的问题
- 4.1 缓存不刷新
- 5.Caffeine缓存进阶
- 参考文献
1.Caffeine本地缓存
1.1 本地缓存技术选型
- 缓存是一种常用的技术,通过将常用数据存储在缓存中,可以在一定程度上提升数据存取的速度,这正是局部性原理的应用。传统的缓存大多是分布式的,例如 Redis,虽然性能强大,但需要额外的服务支持,且在数据传输上会增加一定的耗时。对于一些小型应用,使用 Redis 可能会显得过于复杂和资源浪费,此时可以考虑使用本地缓存。
- 常见的本地缓存技术选型包括:
- 使用 ConcurrentHashMap 作为缓存:这是最简单的缓存实现方式,但功能较为单一,没有内存淘汰策略,需要开发人员进行定制化开发。
- Guava Cache:Guava 是 Google 团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性都有保障,应用广泛。Guava Cache 支持最大容量限制、过期删除策略(基于插入时间和访问时间)、简单统计功能等,基于 LRU 算法实现。
- Caffeine 缓存:Caffeine 是一个高性能的 Java 本地缓存库,设计用于提供快速响应时间和高并发处理能力。它具有类似于 Guava 缓存的简单易用的 API,同时提供了许多额外的功能和性能优化。Caffeine 支持缓存大小限制、缓存过期策略、异步加载数据等特性,可以帮助开发人员在应用程序中有效地管理和优化缓存。Caffeine 还提供了可自定义的缓存策略和监听器,以帮助开发人员根据实际需求定制缓存行为。
- 基于 Ehcache 实现本地缓存:Ehcache 是一个流行的 Java 开源缓存框架,用于在应用程序中管理缓存数据。它被广泛用于提高应用程序性能,减少数据库访问频率,和减少网络开销。与 Caffeine 和 Guava Cache 相比,Ehcache 的功能更加丰富,扩展性更强。
- 总结:使用 Caffeine 作为本地缓存可以显著提升缓存数据的访问效率和读取性能。然而,本地缓存的使用受限于本地缓存的大小,对于缓存数据量大或数据结构复杂的情况,建议使用第三方缓存服务,例如 Redis。
1.2 Caffeine缓存介绍
- Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。缓存和ConcurrentMap有点相似,但还是有所区别。最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存
Cache
通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,LoadingCache
和AsyncLoadingCache
因为其自动加载缓存的能力将会变得非常实用。 - Caffeine提供了灵活的构造器去创建一个拥有下列特性的缓存:
- 为了提高集成度,扩展模块提供了JSR-107 JCache和Guava适配器。JSR-107规范了基于Java 6的API,在牺牲了功能和性能的代价下使代码更加规范。Guava的Cache是Caffeine的原型库并且Caffeine提供了适配器以供简单的迁移策略。
- 总结: Caffeine 是基于 JAVA 8 的高性能缓存库。并且在Spring5 (SpringBoot2.x) 后,Spring 官方放弃了 Guava,而使用了性能更优秀的Caffeine作为默认缓存组件。
- 缓存性能对比图:可以通过下图观测到,在下面缓存组件中 Caffeine 性能是其中最好的
caffeine Wiki · GitHub" />
2.SpringBoot集成Caffeine
- SpringBoot提供了对缓存的良好支持,并且可以轻松地整合Caffeine本地缓存作为缓存提供者,结合Spring Cache注解,可以实现对方法级别的缓存,从而提高系统的性能和响应速度。
- SpringBoot有两种使用Caffeine作为缓存的方式:
下面将介绍下,这两种集成方式都是如何实现的,毛毛张首先教大家构建出来一个代码,然后再解释具体的配置说明
2.1 SpringBoot集成Caffeine方式1-推荐
2.1.1 创建项目
- 首先创建一个SpringBoot项目,SpringBoot版本为2.7.6,JDK版本为1.8,具体的步骤毛毛张就不详细说了,下图是毛毛张整个项目的目录结构:
2.1.2 导入依赖
- 和缓存相关的依赖:
<!-- Spring Boot 缓存启动器依赖,用于支持 Spring Boot 的缓存功能 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Caffeine 缓存库依赖,Spring Boot 缓存的一个实现 --> <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId> </dependency>
- 整个项目的完整依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><!-- 定义 POM 模型的版本,固定为 4.0.0 --><modelVersion>4.0.0</modelVersion><groupId>com.zzx</groupId><artifactId>springboot-cache-demo1</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-cache-demo1</name><description>springboot-cache-demo1</description><!-- 定义项目的父项目,继承父项目的配置和依赖管理 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-parent</artifactId><version>2.7.6</version></parent><!-- 定义项目的属性,可在 POM 中引用 --><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.6</spring-boot.version></properties><!-- 定义项目的依赖项 --><dependencies><!-- Spring Boot Web 启动器依赖,用于创建基于 Spring Boot 的 Web 应用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot 缓存启动器依赖,用于支持 Spring Boot 的缓存功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- Caffeine 缓存库依赖,Spring Boot 缓存的一个实现 --><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><!-- Lombok 依赖,用于减少 Java 代码中的样板代码,如 getter、setter 等 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!-- optional 为 true 表示该依赖不会传递给依赖本项目的其他项目 --><optional>true</optional></dependency></dependencies><!-- 依赖管理部分,用于统一管理依赖的版本 --><dependencyManagement><dependencies><!-- 引入 Spring Boot 依赖管理 POM,确保依赖的版本一致性 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><!-- 使用之前定义的 Spring Boot 版本号 --><version>${spring-boot.version}</version><!-- 依赖类型为 POM --><type>pom</type><!-- 导入范围,表示导入该 POM 的依赖管理 --><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><!-- Spring Boot Maven 插件,用于打包和运行 Spring Boot 应用 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.zzx.SpringbootCacheDemo1Application</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
2.1.3 配置文件application.yaml
- 配置文件:
# 应用服务 WEB 访问端口 server:port: 8080
2.1.4 实体类UserInfo
- 创建实体类对象:
java">package com.zzx.entity;import lombok.Data; import lombok.ToString;import java.io.Serializable;@Data @ToString public class UserInfo implements Serializable {private Integer id;private String name;private String sex;private Integer age; }
2.1.5 配置缓存配置类CacheManagerConfig
- 缓存配置类:
java">package com.zzx.config;import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@EnableCaching @Configuration public class CacheManagerConfig {@Bean("caffeineCacheManager")public CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder()// 设置过期时间,写入后五分钟过期.expireAfterWrite(5, TimeUnit.MINUTES)// 初始化缓存空间大小.initialCapacity(100)// 最大的缓存条数.maximumSize(200));return cacheManager;} }
2.1.6 定义服务接口类和实现类
-
服务接口类:
java">package com.zzx.service;import com.zzx.entity.UserInfo;public interface UserInfoService {/*** 增加用户信息** @param userInfo 用户信息*/UserInfo addUserInfo(UserInfo userInfo);/*** 获取用户信息** @param id 用户ID* @return 用户信息*/UserInfo getByName(Integer id);/*** 修改用户信息** @param userInfo 用户信息* @return 用户信息*/UserInfo updateUserInfo(UserInfo userInfo);/*** 删除用户信息** @param id 用户ID*/void deleteById(Integer id);}
-
服务实现类:
java">package com.zzx.service.impl;import com.zzx.entity.UserInfo; import com.zzx.service.UserInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils;import java.util.HashMap;@Slf4j @Service @CacheConfig(cacheNames = "userInfo") public class UserInfoServiceImpl implements UserInfoService {/*** 模拟数据库存储数据*/private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();@Override@CachePut(key = "#userInfo.id")public UserInfo addUserInfo(UserInfo userInfo) {userInfoMap.put(userInfo.getId(), userInfo);log.info("数据添加成功!");log.info(userInfoMap.toString());return userInfo;}@Override@Cacheable(key = "#id")public UserInfo getByName(Integer id) {//如果经过这个函数就说明没有走缓存UserInfo userInfo = userInfoMap.get(id);log.info("没有走缓存!");return userInfo;}@Override@CachePut(key = "#userInfo.id")public UserInfo updateUserInfo(UserInfo userInfo) {if (!userInfoMap.containsKey(userInfo.getId())) {return null;}// 取旧的值UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());// 替换内容if (!StringUtils.isEmpty(oldUserInfo.getAge())) {oldUserInfo.setAge(userInfo.getAge());}if (!StringUtils.isEmpty(oldUserInfo.getName())) {oldUserInfo.setName(userInfo.getName());}if (!StringUtils.isEmpty(oldUserInfo.getSex())) {oldUserInfo.setSex(userInfo.getSex());}// 将新的对象存储,更新旧对象信息userInfoMap.put(oldUserInfo.getId(), oldUserInfo);log.info("数据更新成功");log.info(userInfoMap.toString());// 返回新对象信息return oldUserInfo;}@Override@CacheEvict(key = "#id")public void deleteById(Integer id) {log.info("delete");userInfoMap.remove(id);}}
2.1.7 控制层
- 控制层代码:
java">package com.zzx.controller;import com.zzx.entity.UserInfo; import com.zzx.service.UserInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;@Slf4j @RestController @RequestMapping public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@GetMapping("/userInfo/{id}")public Object getUserInfo(@PathVariable Integer id) {log.info("查询用户接口");UserInfo userInfo = userInfoService.getByName(id);if (userInfo == null) {return "没有该用户";}return userInfo;}@PostMapping("/userInfo")public Object createUserInfo(@RequestBody UserInfo userInfo) {log.info("创建用户接口");userInfoService.addUserInfo(userInfo);return "SUCCESS";}@PutMapping("/userInfo")public Object updateUserInfo(@RequestBody UserInfo userInfo) {log.info("更新用户接口");UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);if (newUserInfo == null){return "不存在该用户";}return newUserInfo;}@DeleteMapping("/userInfo/{id}")public Object deleteUserInfo(@PathVariable Integer id) {log.info("删除用户接口");userInfoService.deleteById(id);return "SUCCESS";}}
2.1.8 启动类
- 启动类代码:
java">package com.zzx;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching;@EnableCaching @SpringBootApplication public class SpringbootCacheDemo1Application {public static void main(String[] args) {SpringApplication.run(SpringbootCacheDemo1Application.class, args);}}
2.2 SpringBoot集成Caffeine方式2
2.2.1 创建项目
- 首先创建一个SpringBoot项目,SpringBoot版本为2.7.6,JDK版本为1.8,具体的步骤毛毛张就不详细说了,下图是毛毛张整个项目的目录结构:
2.2.2 导入依赖
- 和缓存相关的依赖:
<!-- Caffeine 缓存库依赖,Spring Boot 缓存的一个实现 --> <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId> </dependency>
- 整个项目的完整依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.zzx</groupId><artifactId>springboot-cache-demo2</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-cache-demo2</name><description>springboot-cache-demo2</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.6</spring-boot.version></properties><dependencies><!-- Spring Boot Web 启动器依赖,用于创建基于 Spring Boot 的 Web 应用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Caffeine 缓存库依赖,Spring Boot 缓存的一个实现 --><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.zzx.SpringbootCacheDemo2Application</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
2.2.3 配置文件application.yaml
- 配置文件:
# 应用服务 WEB 访问端口 server:port: 8080
2.2.4 实体类UserInfo
- 创建实体类对象:
java">package com.zzx.entity;import lombok.Data; import lombok.ToString;import java.io.Serializable;@Data @ToString public class UserInfo implements Serializable {private Integer id;private String name;private String sex;private Integer age; }
2.2.5 配置缓存配置类CacheManagerConfig
- 缓存配置类:
java">package com.zzx.config;import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit;@Configuration public class CacheConfig {@Beanpublic Cache<String, Object> caffeineCache() {return Caffeine.newBuilder()// 设置最后一次写入或访问后经过固定时间过期.expireAfterWrite(60, TimeUnit.SECONDS)// 初始的缓存空间大小.initialCapacity(100)// 缓存的最大条数.maximumSize(1000).build();}}
2.2.6 定义服务接口类和实现类
- 服务接口类:
java">package com.zzx.service;import com.zzx.entity.UserInfo;public interface UserInfoService {/*** 增加用户信息** @param userInfo 用户信息*/UserInfo addUserInfo(UserInfo userInfo);/*** 获取用户信息** @param id 用户ID* @return 用户信息*/UserInfo getByName(Integer id);/*** 修改用户信息** @param userInfo 用户信息* @return 用户信息*/UserInfo updateUserInfo(UserInfo userInfo);/*** 删除用户信息** @param id 用户ID*/void deleteById(Integer id);}
- 服务实现类:
java">package com.zzx.service.impl;import com.zzx.entity.UserInfo; import com.zzx.service.UserInfoService; import com.github.benmanes.caffeine.cache.Cache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.HashMap;@Slf4j @Service @CacheConfig(cacheNames = "userInfo") public class UserInfoServiceImpl implements UserInfoService {/*** 模拟数据库存储数据*/private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();@AutowiredCache<String, Object> caffeineCache;@Overridepublic UserInfo addUserInfo(UserInfo userInfo) {log.info("create");userInfoMap.put(userInfo.getId(), userInfo);// 加入缓存caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);return userInfo;}@Overridepublic UserInfo getByName(Integer id) {// 先从缓存读取caffeineCache.getIfPresent(id);UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));if (userInfo != null){return userInfo;}// 如果缓存中不存在,则从库中查找log.info("get");userInfo = userInfoMap.get(id);// 如果用户信息不为空,则加入缓存if (userInfo != null){caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);}return userInfo;}@Overridepublic UserInfo updateUserInfo(UserInfo userInfo) {log.info("update");if (!userInfoMap.containsKey(userInfo.getId())) {return null;}// 取旧的值UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());// 替换内容if (!StringUtils.isEmpty(oldUserInfo.getAge())) {oldUserInfo.setAge(userInfo.getAge());}if (!StringUtils.isEmpty(oldUserInfo.getName())) {oldUserInfo.setName(userInfo.getName());}if (!StringUtils.isEmpty(oldUserInfo.getSex())) {oldUserInfo.setSex(userInfo.getSex());}// 将新的对象存储,更新旧对象信息userInfoMap.put(oldUserInfo.getId(), oldUserInfo);// 替换缓存中的值caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);return oldUserInfo;}@Overridepublic void deleteById(Integer id) {log.info("delete");userInfoMap.remove(id);// 从缓存中删除caffeineCache.asMap().remove(String.valueOf(id));}}
2.1.7 控制层
- 控制层代码:
java">package com.zzx.controller;import com.zzx.entity.UserInfo; import com.zzx.service.UserInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;@Slf4j @RestController @RequestMapping public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@GetMapping("/userInfo/{id}")public Object getUserInfo(@PathVariable Integer id) {log.info("查询用户接口");UserInfo userInfo = userInfoService.getByName(id);if (userInfo == null) {return "没有该用户";}return userInfo;}@PostMapping("/userInfo")public Object createUserInfo(@RequestBody UserInfo userInfo) {log.info("创建用户接口");userInfoService.addUserInfo(userInfo);return "SUCCESS";}@PutMapping("/userInfo")public Object updateUserInfo(@RequestBody UserInfo userInfo) {log.info("更新用户接口");UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);if (newUserInfo == null){return "不存在该用户";}return newUserInfo;}@DeleteMapping("/userInfo/{id}")public Object deleteUserInfo(@PathVariable Integer id) {log.info("删除用户接口");userInfoService.deleteById(id);return "SUCCESS";}}
2.1.8 启动类
- 启动类代码:
java">package com.zzx;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching;@EnableCaching @SpringBootApplication public class SpringbootCacheDemo1Application {public static void main(String[] args) {SpringApplication.run(SpringbootCacheDemo1Application.class, args);}}
2.3 接口测试
第一次查询ID为1的用户:http://localhost:8080/userInfo/1
(Get请求)
-
返回结果:
-
控制台输出:
第二次查询ID为1的用户:http://localhost:8080/userInfo/1
(Get请求)
第三次添加ID为1的用户:
-
返回结果:说明添加成功
-
控制台输出:
第四次查询ID为1的用户:http://localhost:8080/userInfo/1
(Get请求)
第五次删除ID为1的用户:http://localhost:8080/userInfo/1
(Delete请求)
- 返回结果:用户删除成功,缓存也删除成功
- 控制台输出:删除成功,那么第六次查的时候就找不到该用户了,并且不会走缓存
第六次查询ID为1的用户:http://localhost:8080/userInfo/1
(Get请求)
3.配置详解
- 上面毛毛张介绍了SpringBoot集成Caffeine的两种方式,但是对于SpringBoot的项目更推荐方式1,所以下面毛毛张将着重围绕方式1的配置来进行介绍;对于方式2,可以在非 Spring 项目中使用。
3.1 配置依赖
- 使用Caffeine需要在
pom.xml
文件中添加Caffeine
的依赖,如果结合Spring Cache注解还需要添加Spring Cache的依赖,主要是下面两个依赖:<!-- Spring Boot 缓存启动器依赖,用于支持 Spring Boot 的缓存功能 即Spring Cache的依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Caffeine 缓存库依赖,Spring Boot 缓存的一个实现 --> <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId> </dependency>
3.2 配置Caffeine缓存管理器
3.2.1 通过配置类的方式配置
java">package com.zzx.config;import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@EnableCaching
@Configuration
public class CacheManagerConfig {//将cacheManager方法返回的对象注册为 Spring 容器中的 Bean,Bean 的名称为caffeineCacheManager@Bean("caffeineCacheManager")public CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder()// 设置过期时间,写入后五分钟过期.expireAfterWrite(5, TimeUnit.MINUTES)// 初始化缓存空间大小.initialCapacity(100)// 最大的缓存条数.maximumSize(200));return cacheManager;}
}
Caffeine 配置说明
配置参数总览:
参数 | 类型 | 描述 |
---|---|---|
initialCapacity | integer | 缓存的初始空间大小,预先分配一定空间存储缓存项,减少扩容操作以提高性能。 |
maximumSize | long | 缓存的最大条数,当缓存中的条目数量达到该值时,Caffeine 会按指定策略清理缓存。 |
maximumWeight | long | 缓存的最大权重,允许为每个缓存项分配不同权重,当所有缓存项权重总和超该值时触发淘汰。 |
expireAfterAccess | duration | 缓存项在最后一次读或写操作后,经过指定时间过期。 |
expireAfterWrite | duration | 缓存项在最后一次写操作后,经过指定时间过期。 |
refreshAfterWrite | duration | 缓存项在创建或最近一次更新后,经过指定时间间隔自动刷新,刷新在后台异步进行。 |
weakKeys | boolean | 打开 key 的弱引用。弱引用对象生命周期短暂,垃圾回收器扫描时,只要发现只具弱引用的对象,无论内存是否充足都会回收其内存。 |
weakValues | boolean | 打开 value 的弱引用。 |
softValues | boolean | 打开 value 的软引用。若对象仅具软引用,内存充足时垃圾回收器不回收,内存不足时则回收其内存。 |
recordStats | boolean | 开启统计功能,用于监控缓存的命中率、加载时间等统计信息。 |
配置注意事项:
weakValues
和softValues
不能同时使用,因为它们是不同的引用类型,同时使用会产生冲突。maximumSize
和maximumWeight
不能同时使用,它们是不同的缓存容量限制方式,同时使用会导致冲突。expireAfterWrite
和expireAfterAccess
同事存在时,以expireAfterWrite
为准。
软引用与弱引用:
- 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
- 弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
java">// 软引用
Caffeine.newBuilder().softValues().build();// 弱引用
Caffeine.newBuilder().weakKeys().weakValues().build();
3.2.2 通过配置文件的方式配置-不推荐
# 应用程序基本信息配置
spring:cache:type: caffeine # 指定使用 Caffeine 作为缓存实现caffeine:spec: initialCapacity=100,maximumSize=200,expireAfterWrite=5m # Caffeine 缓存具体参数配置# 开启缓存自动配置(通常 Spring Boot 默认开启,这里列出供参考)
cache:enabled: true
3.2.3 总结
- 上面毛毛张介绍了两种方式配置Caffeine缓存管理器,大家觉得哪种方式好呢?
- 答案:第一种方式,为什么呢?因为若要配置多个缓存,第一种方式可通过创建多个带有不同
@Bean
名称的CacheManager
方法实现。比如在当前配置基础上,新增一个@Bean("anotherCaffeineCacheManager")
注解的方法,在该方法里创建新的CaffeineCacheManager
实例,并使用Caffeine.newBuilder()
配置不同的缓存参数,像设置不同的过期时间、初始容量和最大缓存条数等,例如.expireAfterWrite(10, TimeUnit.MINUTES).initialCapacity(200).maximumSize(300)
,最后返回新配置好的CaffeineCacheManager
实例,这样就可在 Spring 容器中注册多个不同配置的缓存管理器,供不同场景使用。然而第二种方式通过配置类的方式就比较复杂。
- 答案:第一种方式,为什么呢?因为若要配置多个缓存,第一种方式可通过创建多个带有不同
3.3 Spring Cache框架注解介绍和使用
- Caffeine缓存库结合Spring Cache框架来使用,主要通过下面五个注解来实现
3.3.1 @EnableCaching
作用: 该注解的作用是开启Spring缓存功能,一般放在启动类上,或者也可以放在上面的配置类上,如下面代码所示:
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;@EnableCaching
@Configuration
public class CacheConfig {@Beanpublic Cache<String, Object> caffeineCache() {....}
}
3.3.2 @Cacheable
注解简介
作用:
属性解释:
属性 / 方法名 | 解释 |
---|---|
value | 缓存名,必填,指定缓存存储的命名空间,可将不同类型的缓存进行区分 |
cacheNames | 与 value 作用相同,二选一使用,用于指定缓存的名称 |
key | 可选属性,使用 SpEL 表达式自定义缓存的键,默认使用方法的参数作为键。例如 #id 表示使用方法的 id 参数作为键 |
keyGenerator | key 的生成器,与 key 二选一。可自定义实现 KeyGenerator 接口来生成复杂的键 |
cacheManager | 指定使用的缓存管理器的名称,即@Bean("缓存管理器名称") 。在一个 Spring 应用中,可能会配置多个不同的 CacheManager 实例,每个实例对应不同的缓存实现(如 Caffeine、Redis 等)或者不同的缓存配置。通过 cacheManager 属性,可以明确告知 Spring 使用哪个 CacheManager 来处理当前方法的缓存操作。 |
cacheResolver | 指定缓存解析器,用于解析缓存的相关信息,如缓存名称、键等 |
condition | 条件表达式,当表达式结果为 true 时才进行缓存操作 |
unless | 条件表达式,当表达式结果为 true 时,即使方法执行完成也不将结果存入缓存 |
sync | 布尔值,是否使用异步模式,默认 false 。设置为 true 时,在缓存未命中时会异步执行方法并更新缓存 |
注意事项:
cacheManager
关注的是使用哪个具体的CacheManager
实例来管理缓存,它决定了缓存的底层实现和配置;而value
和cacheNames
关注的是缓存数据存放在哪个命名空间下,用于区分不同类型的缓存数据。cacheManager
需要指定CacheManager
实例在 Spring 容器中的名称;而value
和cacheNames
直接以字符串形式指定缓存的名称,二者功能相同,使用时二选一即可。
简单使用案例
案例1:
java">@Cacheable(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "say")
public String sayHello(String name) {return "hello " + name + "-->" + UUID.randomUUID();
}
注意:key为SpEL表达式,因此如果要写字符串时要用单引号括起来。如果name参数为高启强,缓存key的值为p_高启强
第一次请求:http://localhost:8080/api/cache/say?name=高启强
返回信息:hello 高启强-->79c86d44-abbc-4892-b66f-f7786d2df0c0
第二次请求:http://localhost:8080/api/cache/say?name=高启强
返回信息:hello 高启强-->79c86d44-abbc-4892-b66f-f7786d2df0c0
两次返回信息相同,说明缓存生效了!
案例2:
java">@Cacheable(value = "condition", cacheManager = "caffeineCacheManager", key = "#age", condition = "#age % 2 == 0")
@GetMapping(path = "condition")
public String setByCondition(Integer age) {return "condition: " + age + "-->" + UUID.randomUUID();
}
当age为偶数时才写缓存,否则不写。
请求奇数5:http://localhost:8080/api/cache/condition?age=5
页面打印返回信息:condition: 5-->1b10c7aa-e7da-4d0a-976c-9a28056ae268
再次请求奇数5:http://localhost:8080/api/cache/condition?age=5
页面打印返回信息:condition: 5-->f8d2c8f7-33e8-42fa-aef3-08d3f97f7592
说明请求奇数时,不写缓存。
请求奇数6:http://localhost:8080/api/cache/condition?age=6
页面打印返回信息:condition: 6-->500aece9-9a6f-4f77-b3ff-78b317c9fbdf
再次请求奇数6:http://localhost:8080/api/cache/condition?age=6
页面打印返回信息:condition: 6-->500aece9-9a6f-4f77-b3ff-78b317c9fbdf
说明请求偶数时,写缓存。
案例三:
java">@Cacheable(value = "unless", cacheManager = "caffeineCacheManager", key = "#age", unless = "#age % 2 == 0")
@GetMapping(path = "unless")
public String setByUnless(Integer age) {return "unless: " + age + "-->" + UUID.randomUUID();
}
与案例二相反,不满足条件时,才写入缓存。
3.3.3 @CachePut
注解简介
作用:
注意事项:
- 使用该注解的时候一定要有返回值,如果没有返回值就不会把对象缓存在注解中
属性解释:
属性 / 方法名 | 解释 |
---|---|
value | 缓存名,必填,指定缓存存储的命名空间 |
cacheNames | 与 value 作用相同,二选一,用于指定缓存的名称 |
key | 可选属性,使用 SpEL 表达式自定义缓存的键 |
keyGenerator | 与 key 二选一,可自定义实现 KeyGenerator 接口来生成键 |
cacheManager | 指定使用的缓存管理器的名称,即@Bean("缓存管理器名称") 。在一个 Spring 应用中,可能会配置多个不同的 CacheManager 实例,每个实例对应不同的缓存实现(如 Caffeine、Redis 等)或者不同的缓存配置。通过 cacheManager 属性,可以明确告知 Spring 使用哪个 CacheManager 来处理当前方法的缓存操作。 |
cacheResolver | 指定缓存解析器,用于解析缓存相关信息 |
condition | 条件表达式,结果为 true 时才进行缓存更新操作 |
unless | 条件表达式,结果为 true 时,不将方法结果存入缓存 |
简单使用案例
java">@CachePut(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "cachePut")
public String cachePut(String name) {return "hello " + name + "-->" + UUID.randomUUID();
}
测试流程:
第一次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->3fcc2d80-ce32-46bb-8c51-de79cfd7e8fe
第二次请求:http://localhost:8080/api/cache/cachePut?name=高启虎
页面打印返回信息:hello 高启虎-->9e459753-f1e2-4372-a707-bc48b173c28c
第三次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->9e459753-f1e2-4372-a707-bc48b173c28c
测试结果分析:第一次请求@Cacheable注解,第二次请求@CachePut注解更新了缓存内容,第三次再次请求@Cacheable注解返回的是第二次请求更新的内容。
3.3.4 @CacheEvict
注解简介
属性 / 方法名 | 解释 |
---|---|
value | 缓存名,必填,指定要清除的缓存所在的命名空间 |
cacheNames | 与 value 作用相同,二选一,指定要清除的缓存名称 |
key | 可选属性,使用 SpEL 表达式指定要清除的缓存键 |
keyGenerator | 与 key 二选一,确定要清除的缓存键的生成方式 |
cacheManager | 指定使用的缓存管理器的名称,即@Bean("缓存管理器名称") 。在一个 Spring 应用中,可能会配置多个不同的 CacheManager 实例,每个实例对应不同的缓存实现(如 Caffeine、Redis 等)或者不同的缓存配置。通过 cacheManager 属性,可以明确告知 Spring 使用哪个 CacheManager 来处理当前方法的缓存操作。 |
cacheResolver | 指定缓存解析器,用于解析要清除的缓存相关信息 |
condition | 条件表达式,结果为 true 时才执行缓存清除操作 |
allEntries | 布尔值,默认 false 。设置为 true 时,会清除指定缓存命名空间下的所有缓存条目 |
beforeInvocation | 布尔值,默认 false 。设置为 true 时,在方法执行前就清除缓存;否则在方法执行后清除 |
简单使用案例
java">@CacheEvict(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "evict")
public String evict(String name) {return "hello " + name + "-->" + UUID.randomUUID();
}
测试流程:
第一次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->52a64043-d3c4-45c6-9118-679a2e47dcef
第二次请求:http://localhost:8080/api/cache/evict?name=高启虎
页面打印返回信息:hello 高启虎-->3baadce2-1283-4362-8765-c7ce81f3fc25
第三次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->3baadce2-1283-4362-8765-c7ce81f3fc25
测试结果分析:第一次请求@Cacheable注解,第二次请求@CacheEvict注解删除了缓存内容,第三次再次请求@Cacheable注解返回的内容也更新了。
3.3.5 @Caching
注解简介
简单使用案例
java">@Caching(cacheable = @Cacheable(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name"), evict = @CacheEvict(value = "condition", cacheManager = "caffeineCacheManager", key = "#age"))
@GetMapping(path = "caching")
public String caching(String name, Integer age) {return "caching " + name + "-->" + UUID.randomUUID();
}
3.3.6 @CacheConfig
- 在实际开发中,当项目里需要使用缓存的地方逐渐增多时,为了避免在每个缓存注解里重复指定
value
(也就是缓存的命名空间),可以使用@CacheConfig
注解。 - 使用方式:将
@CacheConfig(cacheNames = {"cacheName"})
注解添加到类上,就能统一为该类里所有使用缓存注解的方法指定value
值。在这种情况下,方法上的缓存注解就可以省略value
属性。不过,如果在方法的缓存注解里依旧明确写上了value
值,那么会以方法上的value
值为准。 - SpringBoot集成Caffeine方式1的代码中毛毛张就是这样写的
4.可能的问题
4.1 缓存不刷新
5.Caffeine缓存进阶
- 本章将详细介绍 Caffeine 的缓存添加策略、移除策略以及驱逐策略。
5.0 定义实体类
java">public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User(name=" + name + ", age=" + age + ")";}
}
5.1 缓存添加策略
- Caffeine 提供了四种缓存添加策略:手动加载、自动加载、手动异步加载和自动异步加载。
5.1.1 手动加载
手动加载通过 cache.get(key, k -> value)
和 cache.put(key, value)
方法实现。
-
cache.get(key, k -> value)
:如果缓存中不存在指定的key
,则会通过计算生成值并写入缓存;如果存在,则直接返回缓存值。如果生成过程中抛出异常,则返回null
。 -
cache.put(key, value)
:直接写入或更新缓存中的值,已存在的值会被覆盖。
示例代码:
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;import java.util.concurrent.TimeUnit;public class ManualLoadingExample {public static void main(String[] args) {Cache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.build();// 查找缓存,如果不存在则生成System.out.println(cache.getIfPresent("111")); // 输出:nullcache.get("111", key -> new User("张三", 23)); // 缓存中没有 "111",生成并写入缓存cache.put("222", new User("李四", 23)); // 添加或更新缓存// 输出结果System.out.println(cache.getIfPresent("111")); // 输出:User(name=张三, age=23)System.out.println(cache.getIfPresent("222")); // 输出:User(name=李四, age=23)}
}
输出结果:
null
User(name=张三, age=23)
User(name=李四, age=23)
5.1.2 自动加载
自动加载通过 LoadingCache
实现,它在初始化时通过 CacheLoader
配置生成策略。
示例代码:
java">import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;import java.util.concurrent.TimeUnit;public class AutoLoadingExample {public static void main(String[] args) {LoadingCache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.build(key -> new User("张三", 23)); // 自动加载逻辑// 获取缓存值,如果不存在则自动生成System.out.println(cache.get("111")); // 输出:User(name=张三, age=23)}
}
输出结果:
User(name=张三, age=23)
5.1.3 手动异步加载
手动异步加载通过 AsyncCache
实现,支持异步生成缓存值并返回 CompletableFuture
。
示例代码:
java">import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.Caffeine;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;public class ManualAsyncLoadingExample {public static void main(String[] args) throws Exception {AsyncCache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.buildAsync();// 查找缓存,如果不存在则生成cache.get("111", key -> new User("张三", 23)); // 缓存中没有 "111",生成并写入缓存CompletableFuture<User> userFuture = cache.get("111"); // 异步获取缓存值// 添加或更新缓存CompletableFuture<User> newUserFuture = new CompletableFuture<>();newUserFuture.complete(new User("李四", 23));cache.put("222", newUserFuture);// 输出结果System.out.println(userFuture.get()); // 输出:User(name=张三, age=23)System.out.println(cache.get("222").get()); // 输出:User(name=李四, age=23)}
}
输出结果:
User(name=张三, age=23)
User(name=李四, age=23)
5.1.4 自动异步加载
自动异步加载通过 AsyncLoadingCache
实现,支持异步加载并自动处理缓存生成。
示例代码:
java">import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;public class AutoAsyncLoadingExample {public static void main(String[] args) throws Exception {AsyncLoadingCache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.buildAsync(key -> new User("张三", 23)); // 自动加载逻辑// 异步获取缓存值,如果不存在则自动生成CompletableFuture<User> userFuture = cache.get("111");System.out.println(userFuture.get()); // 输出:User(name=张三, age=23)}
}
输出结果:
User(name=张三, age=23)
5.2 缓存移除策略
- 缓存移除分为显式移除和监听器移除。
5.2.1 显式移除
显式移除支持单个移除、批量移除和全部移除。
- 单个移除:
cache.invalidate(key)
- 批量移除:
cache.invalidateAll(keys)
- 全部移除:
cache.invalidateAll()
示例代码:
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;import java.util.concurrent.TimeUnit;public class ExplicitRemovalExample {public static void main(String[] args) {Cache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.build();// 添加缓存cache.get("111", key -> new User("张三", 23));System.out.println(cache.getIfPresent("111")); // 输出:User(name=张三, age=23)// 单个移除cache.invalidate("111");System.out.println(cache.getIfPresent("111")); // 输出:null// 批量移除cache.put("222", new User("李四", 23));cache.put("333", new User("王五", 23));cache.invalidateAll(new String[]{"222", "333"});System.out.println(cache.getIfPresent("222")); // 输出:nullSystem.out.println(cache.getIfPresent("333")); // 输出:null// 全部移除cache.put("444", new User("赵六", 23));cache.invalidateAll();System.out.println(cache.getIfPresent("444")); // 输出:null}
}
输出结果:
User(name=张三, age=23)
null
null
null
null
5.2.2 移除监听器
移除监听器通过 Caffeine.removalListener(RemovalListener)
定义,用于在元素被移除时执行操作。
示例代码:
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;import java.util.concurrent.TimeUnit;public class RemovalListenerExample {public static void main(String[] args) {Cache<String, User> cache = Caffeine.newBuilder().expireAfterWrite(500, TimeUnit.MILLISECONDS) // 设置缓存过期时间.maximumSize(10) // 设置缓存最大容量.removalListener((key, value, cause) -> {System.out.println("Key: " + key + " removed due to " + cause);}).build();// 添加缓存for (int i = 0; i < 15; i++) {cache.get(i + "", key -> new User(key + "", 23));}// 手动移除cache.invalidate("5");cache.invalidate("6");}
}
输出结果:
Key: 0 removed due to SIZE
Key: 1 removed due to SIZE
Key: 2 removed due to SIZE
Key: 3 removed due to SIZE
Key: 4 removed due to SIZE
Key: 5 removed due to EXPLICIT
Key: 6 removed due to EXPLICIT
5.3 驱逐策略
- Caffeine 提供了三种驱逐策略:基于容量、基于时间和基于引用。
5.3.1 基于容量
基于容量的驱逐策略分为两种:
- 基于个数:通过
Caffeine.maximumSize(long)
设置缓存的最大个数。 - 基于权重:通过
Caffeine.weigher(Weigher)
和Caffeine.maximumWeight(long)
设置每个元素的权重和最大权重。
示例代码(基于个数):
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;import java.util.concurrent.TimeUnit;public class SizeBasedEvictionExample {public static void main(String[] args) {Cache<String, User> cache = Caffeine.newBuilder().maximumSize(10) // 设置缓存最大容量.removalListener((key, value, cause) -> {System.out.println("Key: " + key + " removed due to " + cause);}).build();// 添加缓存for (int i = 0; i < 15; i++) {cache.get(i + "", key -> new User(key + "", 23));}}
}
输出结果:
Key: 0 removed due to SIZE
Key: 1 removed due to SIZE
Key: 2 removed due to SIZE
Key: 3 removed due to SIZE
Key: 4 removed due to SIZE
示例代码(基于权重):
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import com.github.benmanes.caffeine.cache.Weigher;import java.util.concurrent.TimeUnit;public class WeightBasedEvictionExample {public static void main(String[] args) {Cache<String, User> cache = Caffeine.newBuilder().maximumWeight(100) // 设置最大权重.weigher((key, value) -> value.getAge()) // 设置权重计算逻辑.removalListener((key, value, cause) -> {System.out.println("Key: " + key + " removed due to " + cause);}).build();// 添加缓存for (int i = 0; i < 6; i++) {cache.get(i + "", key -> new User(key + "", 23));}}
}
输出结果:
Key: 0 removed due to SIZE
Key: 1 removed due to SIZE
5.3.2 基于时间
基于时间的驱逐策略包括:
expireAfterAccess(long, TimeUnit)
:基于访问时间的驱逐。expireAfterWrite(long, TimeUnit)
:基于写入时间的驱逐。expireAfter(Expiry)
:自定义过期策略。
示例代码(expireAfterAccess
):
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;import java.util.concurrent.TimeUnit;public class AccessBasedEvictionExample {public static void main(String[] args) throws InterruptedException {Cache<String, User> cache = Caffeine.newBuilder().expireAfterAccess(2, TimeUnit.SECONDS) // 设置访问过期时间.removalListener((key, value, cause) -> {System.out.println("Key: " + key + " removed due to " + cause);}).build();// 添加缓存for (int i = 0; i < 6; i++) {cache.get(i + "", key -> new User(key + "", 23));}// 等待缓存过期Thread.sleep(5000);System.out.println(cache.getIfPresent("1")); // 输出:null}
}
输出结果:
复制
Key: 0 removed due to EXPIRED
Key: 1 removed due to EXPIRED
Key: 2 removed due to EXPIRED
Key: 3 removed due to EXPIRED
Key: 4 removed due to EXPIRED
Key: 5 removed due to EXPIRED
null
5.3.3 基于引用
Caffeine 支持基于引用的驱逐策略,包括弱引用和软引用。
- 弱引用:通过
Caffeine.weakKeys()
或Caffeine.weakValues()
实现,允许 GC 在适当的时候回收缓存元素。 - 软引用:通过
Caffeine.softValues()
实现,允许 GC 在内存不足时回收缓存元素。
示例代码(弱引用):
java">import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;public class ReferenceBasedEvictionExample {public static void main(String[] args) {// 弱引用键和值Cache<String, User> cache = Caffeine.newBuilder().weakKeys().weakValues().build(key -> new User(key, 23));// 添加缓存cache.get("111", key -> new User(key, 23));// 当 key 和 value 都没有强引用时,缓存会被自动回收System.gc(); // 触发垃圾回收}
}
参考文献
- https://blog.csdn.net/qq_45607784/article/details/135409207
- https://zhuanlan.zhihu.com/p/109226599
- https://www.cnblogs.com/dw3306/p/15881537.html
- https://blog.csdn.net/pig_boss/article/details/140472989
- https://blog.csdn.net/qq_45825178/article/details/137643858
- Caffeine缓存不刷新问题_caffeine不生效-CSDN博客

🌈欢迎和毛毛张一起探讨和交流!
联系方式点击下方个人名片