Spring Boot 2.x 把 Guava 干掉了选择本地缓存之王 Caffeine(推荐)

 更新时间:2021年01月20日 09:32:28   作者:超级小豆丁  
这篇文章主要介绍了Spring Boot 2.x 把 Guava 干掉了选择本地缓存之王 Caffeine,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

环境配置:

  • JDK 版本:1.8
  • Caffeine 版本:2.8.0
  • SpringBoot 版本:2.2.2.RELEASE

一、本地缓存介绍

缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。

之前介绍过 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 - 开发统计功能

注意:

  • weakValuessoftValues 不可以同时使用。
  • maximumSizemaximumWeight 不可以同时使用。
  • expireAfterWriteexpireAfterAccess 同事存在时,以 expireAfterWrite 为准。

3、软引用与弱引用

  • 软引用: 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
  • 弱引用: 弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
// 软引用
Caffeine.newBuilder().softValues().build();

// 弱引用
Caffeine.newBuilder().weakKeys().weakValues().build();

三、SpringBoot 集成 Caffeine 两种方式

SpringBoot 有俩种使用 Caffeine 作为缓存的方式:

方式一: 直接引入 Caffeine 依赖,然后使用 Caffeine 方法实现缓存。

方式二: 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现缓存。

下面将介绍下,这俩中集成方式都是如何实现的。

Spring Boot 基础就不介绍了,推荐看下这个教程:

https://github.com/javastacks/spring-boot-best-practice

四、SpringBoot 集成Caffeine 方式一

1、Maven 引入相关依赖

<?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>

 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.2.2.RELEASE</version>
 </parent>

 <groupId>mydlq.club</groupId>
 <artifactId>springboot-caffeine-cache-example-1</artifactId>
 <version>0.0.1</version>
 <name>springboot-caffeine-cache-example-1</name>
 <description>Demo project for Spring Boot Cache</description>

 <properties>
 <java.version>1.8</java.version>
 </properties>

 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>com.github.ben-manes.caffeine</groupId>
 <artifactId>caffeine</artifactId>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 </dependency>
 </dependencies>

 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>

</project>

2、配置缓存配置类

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {

 @Bean
 public Cache<String, Object> caffeineCache() {
 return Caffeine.newBuilder()
 // 设置最后一次写入或访问后经过固定时间过期
 .expireAfterWrite(60, TimeUnit.SECONDS)
 // 初始的缓存空间大小
 .initialCapacity(100)
 // 缓存的最大条数
 .maximumSize(1000)
 .build();
 }

}

3、定义测试的实体对象

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class UserInfo {
 private Integer id;
 private String name;
 private String sex;
 private Integer age;
}

4、定义服务接口类和实现类

UserInfoService

import mydlq.club.example.entity.UserInfo;

public interface UserInfoService {

 /**
 * 增加用户信息
 *
 * @param userInfo 用户信息
 */
 void 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);

}

UserInfoServiceImpl

import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;

@Slf4j
@Service
public class UserInfoServiceImpl implements UserInfoService {

 /**
 * 模拟数据库存储数据
 */
 private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();

 @Autowired
 Cache<String, Object> caffeineCache;

 @Override
 public void addUserInfo(UserInfo userInfo) {
 log.info("create");
 userInfoMap.put(userInfo.getId(), userInfo);
 // 加入缓存
 caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
 }

 @Override
 public 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;
 }

 @Override
 public 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;
 }

 @Override
 public void deleteById(Integer id) {
 log.info("delete");
 userInfoMap.remove(id);
 // 从缓存中删除
 caffeineCache.asMap().remove(String.valueOf(id));
 }

}

5、测试的 Controller 类

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping
public class UserInfoController {

 @Autowired
 private UserInfoService userInfoService;

 @GetMapping("/userInfo/{id}")
 public Object getUserInfo(@PathVariable Integer id) {
 UserInfo userInfo = userInfoService.getByName(id);
 if (userInfo == null) {
 return "没有该用户";
 }
 return userInfo;
 }

 @PostMapping("/userInfo")
 public Object createUserInfo(@RequestBody UserInfo userInfo) {
 userInfoService.addUserInfo(userInfo);
 return "SUCCESS";
 }

 @PutMapping("/userInfo")
 public Object updateUserInfo(@RequestBody UserInfo userInfo) {
 UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
 if (newUserInfo == null){
 return "不存在该用户";
 }
 return newUserInfo;
 }

 @DeleteMapping("/userInfo/{id}")
 public Object deleteUserInfo(@PathVariable Integer id) {
 userInfoService.deleteById(id);
 return "SUCCESS";
 }

}

五、SpringBoot 集成 Caffeine 方式二

1、Maven 引入相关依赖

<?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>

 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.2.2.RELEASE</version>
 </parent>

 <groupId>mydlq.club</groupId>
 <artifactId>springboot-caffeine-cache-example-2</artifactId>
 <version>0.0.1</version>
 <name>springboot-caffeine-cache-example-2</name>
 <description>Demo project for Spring Boot caffeine</description>

 <properties>
 <java.version>1.8</java.version>
 </properties>

 <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>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 </dependency>
 </dependencies>

 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>

</project>

2、配置缓存配置类

@Configuration
public class CacheConfig {

 /**
 * 配置缓存管理器
 *
 * @return 缓存管理器
 */
 @Bean("caffeineCacheManager")
 public CacheManager cacheManager() {
 CaffeineCacheManager cacheManager = new CaffeineCacheManager();
 cacheManager.setCaffeine(Caffeine.newBuilder()
 // 设置最后一次写入或访问后经过固定时间过期
 .expireAfterAccess(60, TimeUnit.SECONDS)
 // 初始的缓存空间大小
 .initialCapacity(100)
 // 缓存的最大条数
 .maximumSize(1000));
 return cacheManager;
 }

}

3、定义测试的实体对象

@Data
@ToString
public class UserInfo {
 private Integer id;
 private String name;
 private String sex;
 private Integer age;
}

4、定义服务接口类和实现类

服务接口

import mydlq.club.example.entity.UserInfo;

public interface UserInfoService {

 /**
 * 增加用户信息
 *
 * @param userInfo 用户信息
 */
 void 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);

}

服务实现类

import lombok.extern.slf4j.Slf4j;
import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
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 = "caffeineCacheManager")
public class UserInfoServiceImpl implements UserInfoService {

 /**
 * 模拟数据库存储数据
 */
 private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();

 @Override
 @CachePut(key = "#userInfo.id")
 public void addUserInfo(UserInfo userInfo) {
 log.info("create");
 userInfoMap.put(userInfo.getId(), userInfo);
 }

 @Override
 @Cacheable(key = "#id")
 public UserInfo getByName(Integer id) {
 log.info("get");
 return userInfoMap.get(id);
 }

 @Override
 @CachePut(key = "#userInfo.id")
 public 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);
 // 返回新对象信息
 return oldUserInfo;
 }

 @Override
 @CacheEvict(key = "#id")
 public void deleteById(Integer id) {
 log.info("delete");
 userInfoMap.remove(id);
 }

}

5、测试的 Controller 类

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping
public class UserInfoController {

 @Autowired
 private UserInfoService userInfoService;

 @GetMapping("/userInfo/{id}")
 public Object getUserInfo(@PathVariable Integer id) {
 UserInfo userInfo = userInfoService.getByName(id);
 if (userInfo == null) {
 return "没有该用户";
 }
 return userInfo;
 }

 @PostMapping("/userInfo")
 public Object createUserInfo(@RequestBody UserInfo userInfo) {
 userInfoService.addUserInfo(userInfo);
 return "SUCCESS";
 }

 @PutMapping("/userInfo")
 public Object updateUserInfo(@RequestBody UserInfo userInfo) {
 UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
 if (newUserInfo == null){
 return "不存在该用户";
 }
 return newUserInfo;
 }

 @DeleteMapping("/userInfo/{id}")
 public Object deleteUserInfo(@PathVariable Integer id) {
 userInfoService.deleteById(id);
 return "SUCCESS";
 }

}

参考地址:

https://www.jianshu.com/p/c72fb0c787fc
https://www.cnblogs.com/rickiyang/p/11074158.html
https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-caffeine-cache-example

到此这篇关于Spring Boot 2.x 把 Guava 干掉了选择本地缓存之王 Caffeine的文章就介绍到这了,更多相关Spring Boot 2.x Caffeine内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java 泛型的详解及实例

    java 泛型的详解及实例

    这篇文章主要介绍了java 泛型的详解及实例的相关资料,希望通过本文大家能彻底掌握泛型的使用方法,需要的朋友可以参考下
    2017-08-08
  • JVM 方法调用之动态分派(详解)

    JVM 方法调用之动态分派(详解)

    下面小编就为大家带来一篇JVM 方法调用之动态分派(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • java实现简单学生成绩管理系统

    java实现简单学生成绩管理系统

    这篇文章主要为大家详细介绍了java实现简单学生成绩管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Java利用Phantomjs实现生成图片的功能

    Java利用Phantomjs实现生成图片的功能

    这篇文章主要介绍了Java利用Phantomjs实现生成图片的功能,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-08-08
  • 养成良好java代码编码规范

    养成良好java代码编码规范

    这篇文章主要介绍了如何养成良好java代码编码规范,规范需要平时编码过程中注意,是一个慢慢养成的好习惯,下面小编就带大家来一起详细了解一下吧
    2019-06-06
  • mybatis项目实现动态表名的三种方法

    mybatis项目实现动态表名的三种方法

    有时在开发过程中java代码中的表名和数据库的表名并不是一致的,此时我们就需要动态的设置表名,本文主要介绍了mybatis项目实现动态表名的三种方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • springboot3.4和mybatis plus的版本问题的解决

    springboot3.4和mybatis plus的版本问题的解决

    本文主要介绍了springboot3.4和mybatis plus的版本问题的解决,主要由于Spring Boot 3.4与MyBatis-Plus版本不匹配导致分页功能问题,下面就来解决这个问题,感兴趣的可以了解一下
    2025-03-03
  • 浅谈springMVC接收前端json数据的总结

    浅谈springMVC接收前端json数据的总结

    下面小编就为大家分享一篇浅谈springMVC接收前端json数据的总结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • Java之Spring Boot创建和使用

    Java之Spring Boot创建和使用

    Spring 的诞生就是为了简化 Java 程序的开发的.Spring Boot 的诞生就是为了简化 Spring 程序开发的,对Springboot感兴趣的同学可以借鉴本文
    2023-04-04
  • IntelliJ IDEA设置代码的快捷编辑模板Live Templates

    IntelliJ IDEA设置代码的快捷编辑模板Live Templates

    今天小编就为大家分享一篇关于IntelliJ IDEA设置代码的快捷编辑模板Live Templates,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10

最新评论