SpringBoot 使用 Caffeine 本地缓存
本文来源:http://www.mydlq.club/article/56/
. 一、本地缓存介绍
. 二、缓存组件 Caffeine 介绍
. 1、Caffeine 性能
. 2、Caffeine 配置说明
. 3、软引用与弱引用
. 三、SpringBoot 集成 Caffeine 两种方式
. 四、SpringBoot 集成 Caffeine 方式一
. 1、Maven 引入相关依赖
. 2、配置缓存配置类
. 3、定义测试的实体对象
. 4、定义服务接口类和实现类
. 5、测试的 Controller 类
. 五、SpringBoot 集成 Caffeine 方式二
. 1、Maven 引入相关依赖
. 2、配置缓存配置类
. 3、定义测试的实体对象
. 4、定义服务接口类和实现类
. 5、测试的 Controller 类
环境配置:
JDK 版本:1.8
Caffeine 版本:2.8.0
SpringBoot 版本:2.2.2.RELEASE
参考地址:
Spring Boot缓存实战 Caffeine
Caffeine Cache-高性能Java本地缓存组件
博文示例项目 Github 地址:https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-caffeine-cache-example
一、本地缓存介绍
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。
之前介绍过 Redis 这种 NoSql 作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。
二、缓存组件 Caffeine 介绍
按 Caffeine Github 文档描述,Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboot 2.x) 后,spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。
1、Caffeine 性能
可以通过下图观测到,在下面缓存组件中 Caffeine 性能是其中最好的。
2、Caffeine 配置说明
参数 | 类型 | 描述 |
---|---|---|
initialCapacity | integer | 初始的缓存空间大小 |
maximumSize | long | 缓存的最大条数 |
maximumWeight | long | 缓存的最大权重 |
expireAfterAccess | duration | 最后一次写入或访问后经过固定时间过期 |
refreshAfterWrite | duration | 最后一次写入后经过固定时间过期 |
refreshAfterWrite | duration | 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 |
weakKeys | boolean | 打开 key 的弱引用 |
weakValues | boolean | 打开 value 的弱引用 |
softValues | boolean | 打开 value 的软引用 |
recordStats | - | 开发统计功能 |
注意:
weakValues
和softValues
不可以同时使用。maximumSize
和maximumWeight
不可以同时使用。expireAfterWrite
和expireAfterAccess
同事存在时,以expireAfterWrite
为准。
3、软引用与弱引用
软引用: 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
弱引用: 弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
1// 软引用
2Caffeine.newBuilder().softValues().build();
3
4// 弱引用
5Caffeine.newBuilder().weakKeys().weakValues().build();
三、SpringBoot 集成 Caffeine 两种方式
SpringBoot 有俩种使用 Caffeine 作为缓存的方式:
方式一: 直接引入 Caffeine 依赖,然后使用 Caffeine 方法实现缓存。
方式二: 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现缓存。
下面将介绍下,这俩中集成方式都是如何实现的。
四、SpringBoot 集成 Caffeine 方式一
1、Maven 引入相关依赖
1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5
6 <parent>
7 <groupId>org.springframework.boot</groupId>
8 <artifactId>spring-boot-starter-parent</artifactId>
9 <version>2.2.2.RELEASE</version>
10 </parent>
11
12 <groupId>mydlq.club</groupId>
13 <artifactId>springboot-caffeine-cache-example-1</artifactId>
14 <version>0.0.1</version>
15 <name>springboot-caffeine-cache-example-1</name>
16 <description>Demo project for Spring Boot Cache</description>
17
18 <properties>
19 <java.version>1.8</java.version>
20 </properties>
21
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-web</artifactId>
26 </dependency>
27 <dependency>
28 <groupId>com.github.ben-manes.caffeine</groupId>
29 <artifactId>caffeine</artifactId>
30 </dependency>
31 <dependency>
32 <groupId>org.projectlombok</groupId>
33 <artifactId>lombok</artifactId>
34 </dependency>
35 </dependencies>
36
37 <build>
38 <plugins>
39 <plugin>
40 <groupId>org.springframework.boot</groupId>
41 <artifactId>spring-boot-maven-plugin</artifactId>
42 </plugin>
43 </plugins>
44 </build>
45
46</project>
2、配置缓存配置类
1import com.github.benmanes.caffeine.cache.Cache;
2import com.github.benmanes.caffeine.cache.Caffeine;
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5import java.util.concurrent.TimeUnit;
6
7@Configuration
8public class CacheConfig {
9
10 @Bean
11 public Cache<String, Object> caffeineCache() {
12 return Caffeine.newBuilder()
13 // 设置最后一次写入或访问后经过固定时间过期
14 .expireAfterWrite(60, TimeUnit.SECONDS)
15 // 初始的缓存空间大小
16 .initialCapacity(100)
17 // 缓存的最大条数
18 .maximumSize(1000)
19 .build();
20 }
21
22}
3、定义测试的实体对象
1import lombok.Data;
2import lombok.ToString;
3
4@Data
5@ToString
6public class UserInfo {
7 private Integer id;
8 private String name;
9 private String sex;
10 private Integer age;
11}
4、定义服务接口类和实现类
UserInfoService
1import mydlq.club.example.entity.UserInfo;
2
3public interface UserInfoService {
4
5 /**
6 * 增加用户信息
7 *
8 * @param userInfo 用户信息
9 */
10 void addUserInfo(UserInfo userInfo);
11
12 /**
13 * 获取用户信息
14 *
15 * @param id 用户ID
16 * @return 用户信息
17 */
18 UserInfo getByName(Integer id);
19
20 /**
21 * 修改用户信息
22 *
23 * @param userInfo 用户信息
24 * @return 用户信息
25 */
26 UserInfo updateUserInfo(UserInfo userInfo);
27
28 /**
29 * 删除用户信息
30 *
31 * @param id 用户ID
32 */
33 void deleteById(Integer id);
34
35}
UserInfoServiceImpl
1import com.github.benmanes.caffeine.cache.Cache;
2import lombok.extern.slf4j.Slf4j;
3import mydlq.club.example.entity.UserInfo;
4import mydlq.club.example.service.UserInfoService;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.stereotype.Service;
7import org.springframework.util.StringUtils;
8import java.util.HashMap;
9
10@Slf4j
11@Service
12public class UserInfoServiceImpl implements UserInfoService {
13
14 /**
15 * 模拟数据库存储数据
16 */
17 private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();
18
19 @Autowired
20 Cache<String, Object> caffeineCache;
21
22 @Override
23 public void addUserInfo(UserInfo userInfo) {
24 log.info("create");
25 userInfoMap.put(userInfo.getId(), userInfo);
26 // 加入缓存
27 caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
28 }
29
30 @Override
31 public UserInfo getByName(Integer id) {
32 // 先从缓存读取
33 caffeineCache.getIfPresent(id);
34 UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));
35 if (userInfo != null){
36 return userInfo;
37 }
38 // 如果缓存中不存在,则从库中查找
39 log.info("get");
40 userInfo = userInfoMap.get(id);
41 // 如果用户信息不为空,则加入缓存
42 if (userInfo != null){
43 caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
44 }
45 return userInfo;
46 }
47
48 @Override
49 public UserInfo updateUserInfo(UserInfo userInfo) {
50 log.info("update");
51 if (!userInfoMap.containsKey(userInfo.getId())) {
52 return null;
53 }
54 // 取旧的值
55 UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
56 // 替换内容
57 if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
58 oldUserInfo.setAge(userInfo.getAge());
59 }
60 if (!StringUtils.isEmpty(oldUserInfo.getName())) {
61 oldUserInfo.setName(userInfo.getName());
62 }
63 if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
64 oldUserInfo.setSex(userInfo.getSex());
65 }
66 // 将新的对象存储,更新旧对象信息
67 userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
68 // 替换缓存中的值
69 caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);
70 return oldUserInfo;
71 }
72
73 @Override
74 public void deleteById(Integer id) {
75 log.info("delete");
76 userInfoMap.remove(id);
77 // 从缓存中删除
78 caffeineCache.asMap().remove(String.valueOf(id));
79 }
80
81}
5、测试的 Controller 类
1import mydlq.club.example.entity.UserInfo;
2import mydlq.club.example.service.UserInfoService;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.web.bind.annotation.*;
5
6@RestController
7@RequestMapping
8public class UserInfoController {
9
10 @Autowired
11 private UserInfoService userInfoService;
12
13 @GetMapping("/userInfo/{id}")
14 public Object getUserInfo(@PathVariable Integer id) {
15 UserInfo userInfo = userInfoService.getByName(id);
16 if (userInfo == null) {
17 return "没有该用户";
18 }
19 return userInfo;
20 }
21
22 @PostMapping("/userInfo")
23 public Object createUserInfo(@RequestBody UserInfo userInfo) {
24 userInfoService.addUserInfo(userInfo);
25 return "SUCCESS";
26 }
27
28 @PutMapping("/userInfo")
29 public Object updateUserInfo(@RequestBody UserInfo userInfo) {
30 UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
31 if (newUserInfo == null){
32 return "不存在该用户";
33 }
34 return newUserInfo;
35 }
36
37 @DeleteMapping("/userInfo/{id}")
38 public Object deleteUserInfo(@PathVariable Integer id) {
39 userInfoService.deleteById(id);
40 return "SUCCESS";
41 }
42
43}
五、SpringBoot 集成 Caffeine 方式二
1、Maven 引入相关依赖
1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5
6 <parent>
7 <groupId>org.springframework.boot</groupId>
8 <artifactId>spring-boot-starter-parent</artifactId>
9 <version>2.2.2.RELEASE</version>
10 </parent>
11
12 <groupId>mydlq.club</groupId>
13 <artifactId>springboot-caffeine-cache-example-2</artifactId>
14 <version>0.0.1</version>
15 <name>springboot-caffeine-cache-example-2</name>
16 <description>Demo project for Spring Boot caffeine</description>
17
18 <properties>
19 <java.version>1.8</java.version>
20 </properties>
21
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-web</artifactId>
26 </dependency>
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-starter-cache</artifactId>
30 </dependency>
31 <dependency>
32 <groupId>com.github.ben-manes.caffeine</groupId>
33 <artifactId>caffeine</artifactId>
34 </dependency>
35 <dependency>
36 <groupId>org.projectlombok</groupId>
37 <artifactId>lombok</artifactId>
38 </dependency>
39 </dependencies>
40
41 <build>
42 <plugins>
43 <plugin>
44 <groupId>org.springframework.boot</groupId>
45 <artifactId>spring-boot-maven-plugin</artifactId>
46 </plugin>
47 </plugins>
48 </build>
49
50</project>
2、配置缓存配置类
1@Configuration
2public class CacheConfig {
3
4 /**
5 * 配置缓存管理器
6 *
7 * @return 缓存管理器
8 */
9 @Bean("caffeineCacheManager")
10 public CacheManager cacheManager() {
11 CaffeineCacheManager cacheManager = new CaffeineCacheManager();
12 cacheManager.setCaffeine(Caffeine.newBuilder()
13 // 设置最后一次写入或访问后经过固定时间过期
14 .expireAfterAccess(60, TimeUnit.SECONDS)
15 // 初始的缓存空间大小
16 .initialCapacity(100)
17 // 缓存的最大条数
18 .maximumSize(1000));
19 return cacheManager;
20 }
21
22}
3、定义测试的实体对象
1@Data
2@ToString
3public class UserInfo {
4 private Integer id;
5 private String name;
6 private String sex;
7 private Integer age;
8}
4、定义服务接口类和实现类
服务接口
1import mydlq.club.example.entity.UserInfo;
2
3public interface UserInfoService {
4
5 /**
6 * 增加用户信息
7 *
8 * @param userInfo 用户信息
9 */
10 void addUserInfo(UserInfo userInfo);
11
12 /**
13 * 获取用户信息
14 *
15 * @param id 用户ID
16 * @return 用户信息
17 */
18 UserInfo getByName(Integer id);
19
20 /**
21 * 修改用户信息
22 *
23 * @param userInfo 用户信息
24 * @return 用户信息
25 */
26 UserInfo updateUserInfo(UserInfo userInfo);
27
28 /**
29 * 删除用户信息
30 *
31 * @param id 用户ID
32 */
33 void deleteById(Integer id);
34
35}
服务实现类
1import lombok.extern.slf4j.Slf4j;
2import mydlq.club.example.entity.UserInfo;
3import mydlq.club.example.service.UserInfoService;
4import org.springframework.cache.annotation.CacheConfig;
5import org.springframework.cache.annotation.CacheEvict;
6import org.springframework.cache.annotation.CachePut;
7import org.springframework.cache.annotation.Cacheable;
8import org.springframework.stereotype.Service;
9import org.springframework.util.StringUtils;
10import java.util.HashMap;
11
12@Slf4j
13@Service
14@CacheConfig(cacheNames = "caffeineCacheManager")
15public class UserInfoServiceImpl implements UserInfoService {
16
17 /**
18 * 模拟数据库存储数据
19 */
20 private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();
21
22 @Override
23 @CachePut(key = "#userInfo.id")
24 public void addUserInfo(UserInfo userInfo) {
25 log.info("create");
26 userInfoMap.put(userInfo.getId(), userInfo);
27 }
28
29 @Override
30 @Cacheable(key = "#id")
31 public UserInfo getByName(Integer id) {
32 log.info("get");
33 return userInfoMap.get(id);
34 }
35
36 @Override
37 @CachePut(key = "#userInfo.id")
38 public UserInfo updateUserInfo(UserInfo userInfo) {
39 log.info("update");
40 if (!userInfoMap.containsKey(userInfo.getId())) {
41 return null;
42 }
43 // 取旧的值
44 UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
45 // 替换内容
46 if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
47 oldUserInfo.setAge(userInfo.getAge());
48 }
49 if (!StringUtils.isEmpty(oldUserInfo.getName())) {
50 oldUserInfo.setName(userInfo.getName());
51 }
52 if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
53 oldUserInfo.setSex(userInfo.getSex());
54 }
55 // 将新的对象存储,更新旧对象信息
56 userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
57 // 返回新对象信息
58 return oldUserInfo;
59 }
60
61 @Override
62 @CacheEvict(key = "#id")
63 public void deleteById(Integer id) {
64 log.info("delete");
65 userInfoMap.remove(id);
66 }
67
68}
5、测试的 Controller 类
1import mydlq.club.example.entity.UserInfo;
2import mydlq.club.example.service.UserInfoService;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.web.bind.annotation.*;
5
6@RestController
7@RequestMapping
8public class UserInfoController {
9
10 @Autowired
11 private UserInfoService userInfoService;
12
13 @GetMapping("/userInfo/{id}")
14 public Object getUserInfo(@PathVariable Integer id) {
15 UserInfo userInfo = userInfoService.getByName(id);
16 if (userInfo == null) {
17 return "没有该用户";
18 }
19 return userInfo;
20 }
21
22 @PostMapping("/userInfo")
23 public Object createUserInfo(@RequestBody UserInfo userInfo) {
24 userInfoService.addUserInfo(userInfo);
25 return "SUCCESS";
26 }
27
28 @PutMapping("/userInfo")
29 public Object updateUserInfo(@RequestBody UserInfo userInfo) {
30 UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
31 if (newUserInfo == null){
32 return "不存在该用户";
33 }
34 return newUserInfo;
35 }
36
37 @DeleteMapping("/userInfo/{id}")
38 public Object deleteUserInfo(@PathVariable Integer id) {
39 userInfoService.deleteById(id);
40 return "SUCCESS";
41 }
42
43}
往期推荐
如果你喜欢本文,欢迎关注我们
专注分享关于Spring的一切
关注我,加入Spring技术交流群