查看原文
其他

SpringBoot 使用 Caffeine 本地缓存

超级小豆丁 SpringForAll社区 2021-05-26
点击上方☝SpringForAll社区 轻松关注!
及时获取有趣有料的技术文章

本文来源: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 配置说明

参数类型描述
initialCapacityinteger初始的缓存空间大小
maximumSizelong缓存的最大条数
maximumWeightlong缓存的最大权重
expireAfterAccessduration最后一次写入或访问后经过固定时间过期
refreshAfterWriteduration最后一次写入后经过固定时间过期
refreshAfterWriteduration创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
weakKeysboolean打开 key 的弱引用
weakValuesboolean打开 value 的弱引用
softValuesboolean打开 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}






● Github推出了GitHub CLI

● (很全面)SpringBoot 集成 Apollo 配置中心

● 你知道如何成为一名靠谱的架构师不?

● Tomcat 在 SpringBoot 中是如何启动的?

● SpringBoot 深度调优,让你的项目飞起来!

● 8种经常被忽视的SQL错误用法,你有没有踩过坑?

● Java面试应该知道之深入理解Java的接口和抽象类

● Spring系列之beanFactory与ApplicationContext

● 多线程同步的五种方法

● redis应用场景

● 手把手带你剖析 Springboot 启动原理!

● Java多线程:synchronized关键字和Lock

● Java多线程:多线程基础知识

● Kafka基本架构及原理

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存