• SpringBoot2.x拥抱本地缓存之王Caffeine


    Java技术架构

    作者:超级小豆丁来源:http://www.mydlq.club/article/56/

    环境配置:

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

    一、本地缓存介绍

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

    之前介绍过 Redis 这种 NoSql 作为缓存组件,它能够很好地作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。

    本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。

    二、缓存组件 Caffeine 介绍

    按 Caffeine Github 文档描述,Caffeine 是基于 JAVA 8 的高性能缓存库。

    并且在 Spring5 (Spring Boot 2.x) 后,Spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。

    1、Caffeine 性能

    可以通过下图观测到,在下面缓存组件中 Caffeine 性能是其中最好的。

    2、Caffeine 配置说明

    注意:

    • weakValues 和 softValues 不可以同时使用。
    • maximumSize 和 maximumWeight 不可以同时使用。
    • expireAfterWrite 和 expireAfterAccess 同事存在时,以 expireAfterWrite 为准。

    3、软引用与弱引用

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

    三、SpringBoot 集成 Caffeine 两种方式

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

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

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

    关注公众号Java技术栈,回复:面试,可以获取我整理的 Spring Boot 系列面试题和答案。下面将介绍下,这两种集成方式都是如何实现的。

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

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

    四、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.     <parent>
    6.         <groupId>org.springframework.boot</groupId>
    7.         <artifactId>spring-boot-starter-parent</artifactId>
    8.         <version>2.2.2.RELEASE</version>
    9.     </parent>
    10.     <groupId>mydlq.club</groupId>
    11.     <artifactId>springboot-caffeine-cache-example-1</artifactId>
    12.     <version>0.0.1</version>
    13.     <name>springboot-caffeine-cache-example-1</name>
    14.     <description>Demo project for Spring Boot Cache</description>
    15.     <properties>
    16.         <java.version>1.8</java.version>
    17.     </properties>
    18.     <dependencies>
    19.         <dependency>
    20.             <groupId>org.springframework.boot</groupId>
    21.             <artifactId>spring-boot-starter-web</artifactId>
    22.         </dependency>
    23.         <dependency>
    24.             <groupId>com.github.ben-manes.caffeine</groupId>
    25.             <artifactId>caffeine</artifactId>
    26.         </dependency>
    27.         <dependency>
    28.             <groupId>org.projectlombok</groupId>
    29.             <artifactId>lombok</artifactId>
    30.         </dependency>
    31.     </dependencies>
    32.     <build>
    33.         <plugins>
    34.             <plugin>
    35.                 <groupId>org.springframework.boot</groupId>
    36.                 <artifactId>spring-boot-maven-plugin</artifactId>
    37.             </plugin>
    38.         </plugins>
    39.     </build>
    40. </project>

    2、配置缓存配置类

    1. import com.github.benmanes.caffeine.cache.Cache;
    2. import com.github.benmanes.caffeine.cache.Caffeine;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import java.util.concurrent.TimeUnit;
    6. @Configuration
    7. public class CacheConfig {
    8.     @Bean
    9.     public Cache<StringObjectcaffeineCache() {
    10.         return Caffeine.newBuilder()
    11.                 // 设置最后一次写入或访问后经过固定时间过期
    12.                 .expireAfterWrite(60TimeUnit.SECONDS)
    13.                 // 初始的缓存空间大小
    14.                 .initialCapacity(100)
    15.                 // 缓存的最大条数
    16.                 .maximumSize(1000)
    17.                 .build();
    18.     }
    19. }

    3、定义测试的实体对象

    1. import lombok.Data;
    2. import lombok.ToString;
    3. @Data
    4. @ToString
    5. public class UserInfo {
    6.     private Integer id;
    7.     private String name;
    8.     private String sex;
    9.     private Integer age;
    10. }

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

    UserInfoService

    1. import mydlq.club.example.entity.UserInfo;
    2. public interface UserInfoService {
    3.     /**
    4.      * 增加用户信息
    5.      *
    6.      * @param userInfo 用户信息
    7.      */
    8.     void addUserInfo(UserInfo userInfo);
    9.     /**
    10.      * 获取用户信息
    11.      *
    12.      * @param id 用户ID
    13.      * @return 用户信息
    14.      */
    15.     UserInfo getByName(Integer id);
    16.     /**
    17.      * 修改用户信息
    18.      *
    19.      * @param userInfo 用户信息
    20.      * @return 用户信息
    21.      */
    22.     UserInfo updateUserInfo(UserInfo userInfo);
    23.     /**
    24.      * 删除用户信息
    25.      *
    26.      * @param id 用户ID
    27.      */
    28.     void deleteById(Integer id);
    29. }

    UserInfoServiceImpl

    1. import com.github.benmanes.caffeine.cache.Cache;
    2. import lombok.extern.slf4j.Slf4j;
    3. import mydlq.club.example.entity.UserInfo;
    4. import mydlq.club.example.service.UserInfoService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Service;
    7. import org.springframework.util.StringUtils;
    8. import java.util.HashMap;
    9. @Slf4j
    10. @Service
    11. public class UserInfoServiceImpl implements UserInfoService {
    12.     /**
    13.      * 模拟数据库存储数据
    14.      */
    15.     private HashMap<IntegerUserInfo> userInfoMap = new HashMap<>();
    16.     @Autowired
    17.     Cache<StringObject> caffeineCache;
    18.     @Override
    19.     public void addUserInfo(UserInfo userInfo) {
    20.         log.info("create");
    21.         userInfoMap.put(userInfo.getId(), userInfo);
    22.         // 加入缓存
    23.         caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
    24.     }
    25.     @Override
    26.     public UserInfo getByName(Integer id) {
    27.         // 先从缓存读取
    28.         caffeineCache.getIfPresent(id);
    29.         UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));
    30.         if (userInfo != null){
    31.             return userInfo;
    32.         }
    33.         // 如果缓存中不存在,则从库中查找
    34.         log.info("get");
    35.         userInfo = userInfoMap.get(id);
    36.         // 如果用户信息不为空,则加入缓存
    37.         if (userInfo != null){
    38.             caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
    39.         }
    40.         return userInfo;
    41.     }
    42.     @Override
    43.     public UserInfo updateUserInfo(UserInfo userInfo) {
    44.         log.info("update");
    45.         if (!userInfoMap.containsKey(userInfo.getId())) {
    46.             return null;
    47.         }
    48.         // 取旧的值
    49.         UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
    50.         // 替换内容
    51.         if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
    52.             oldUserInfo.setAge(userInfo.getAge());
    53.         }
    54.         if (!StringUtils.isEmpty(oldUserInfo.getName())) {
    55.             oldUserInfo.setName(userInfo.getName());
    56.         }
    57.         if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
    58.             oldUserInfo.setSex(userInfo.getSex());
    59.         }
    60.         // 将新的对象存储,更新旧对象信息
    61.         userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
    62.         // 替换缓存中的值
    63.         caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);
    64.         return oldUserInfo;
    65.     }
    66.     @Override
    67.     public void deleteById(Integer id) {
    68.         log.info("delete");
    69.         userInfoMap.remove(id);
    70.         // 从缓存中删除
    71.         caffeineCache.asMap().remove(String.valueOf(id));
    72.     }
    73. }

    5、测试的 Controller 类

    1. import mydlq.club.example.entity.UserInfo;
    2. import mydlq.club.example.service.UserInfoService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.*;
    5. @RestController
    6. @RequestMapping
    7. public class UserInfoController {
    8.     @Autowired
    9.     private UserInfoService userInfoService;
    10.     @GetMapping("/userInfo/{id}")
    11.     public Object getUserInfo(@PathVariable Integer id) {
    12.         UserInfo userInfo = userInfoService.getByName(id);
    13.         if (userInfo == null) {
    14.             return "没有该用户";
    15.         }
    16.         return userInfo;
    17.     }
    18.     @PostMapping("/userInfo")
    19.     public Object createUserInfo(@RequestBody UserInfo userInfo) {
    20.         userInfoService.addUserInfo(userInfo);
    21.         return "SUCCESS";
    22.     }
    23.     @PutMapping("/userInfo")
    24.     public Object updateUserInfo(@RequestBody UserInfo userInfo) {
    25.         UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
    26.         if (newUserInfo == null){
    27.             return "不存在该用户";
    28.         }
    29.         return newUserInfo;
    30.     }
    31.     @DeleteMapping("/userInfo/{id}")
    32.     public Object deleteUserInfo(@PathVariable Integer id) {
    33.         userInfoService.deleteById(id);
    34.         return "SUCCESS";
    35.     }
    36. }

    五、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.     <parent>
    6.         <groupId>org.springframework.boot</groupId>
    7.         <artifactId>spring-boot-starter-parent</artifactId>
    8.         <version>2.2.2.RELEASE</version>
    9.     </parent>
    10.     <groupId>mydlq.club</groupId>
    11.     <artifactId>springboot-caffeine-cache-example-2</artifactId>
    12.     <version>0.0.1</version>
    13.     <name>springboot-caffeine-cache-example-2</name>
    14.     <description>Demo project for Spring Boot caffeine</description>
    15.     <properties>
    16.         <java.version>1.8</java.version>
    17.     </properties>
    18.     <dependencies>
    19.         <dependency>
    20.             <groupId>org.springframework.boot</groupId>
    21.             <artifactId>spring-boot-starter-web</artifactId>
    22.         </dependency>
    23.         <dependency>
    24.             <groupId>org.springframework.boot</groupId>
    25.             <artifactId>spring-boot-starter-cache</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.     <build>
    37.         <plugins>
    38.             <plugin>
    39.                 <groupId>org.springframework.boot</groupId>
    40.                 <artifactId>spring-boot-maven-plugin</artifactId>
    41.             </plugin>
    42.         </plugins>
    43.     </build>
    44. </project>

    2、配置缓存配置类

    1. @Configuration
    2. public class CacheConfig {
    3.     /**
    4.      * 配置缓存管理器
    5.      *
    6.      * @return 缓存管理器
    7.      */
    8.     @Bean("caffeineCacheManager")
    9.     public CacheManager cacheManager() {
    10.         CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    11.         cacheManager.setCaffeine(Caffeine.newBuilder()
    12.                 // 设置最后一次写入或访问后经过固定时间过期
    13.                 .expireAfterAccess(60TimeUnit.SECONDS)
    14.                 // 初始的缓存空间大小
    15.                 .initialCapacity(100)
    16.                 // 缓存的最大条数
    17.                 .maximumSize(1000));
    18.         return cacheManager;
    19.     }
    20. }

    3、定义测试的实体对象

    1. @Data
    2. @ToString
    3. public class UserInfo {
    4.     private Integer id;
    5.     private String name;
    6.     private String sex;
    7.     private Integer age;
    8. }

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

    服务接口:

    1. import mydlq.club.example.entity.UserInfo;
    2. public interface UserInfoService {
    3.     /**
    4.      * 增加用户信息
    5.      *
    6.      * @param userInfo 用户信息
    7.      */
    8.     void addUserInfo(UserInfo userInfo);
    9.     /**
    10.      * 获取用户信息
    11.      *
    12.      * @param id 用户ID
    13.      * @return 用户信息
    14.      */
    15.     UserInfo getByName(Integer id);
    16.     /**
    17.      * 修改用户信息
    18.      *
    19.      * @param userInfo 用户信息
    20.      * @return 用户信息
    21.      */
    22.     UserInfo updateUserInfo(UserInfo userInfo);
    23.     /**
    24.      * 删除用户信息
    25.      *
    26.      * @param id 用户ID
    27.      */
    28.     void deleteById(Integer id);
    29. }

    服务实现类

    1. import lombok.extern.slf4j.Slf4j;
    2. import mydlq.club.example.entity.UserInfo;
    3. import mydlq.club.example.service.UserInfoService;
    4. import org.springframework.cache.annotation.CacheConfig;
    5. import org.springframework.cache.annotation.CacheEvict;
    6. import org.springframework.cache.annotation.CachePut;
    7. import org.springframework.cache.annotation.Cacheable;
    8. import org.springframework.stereotype.Service;
    9. import org.springframework.util.StringUtils;
    10. import java.util.HashMap;
    11. @Slf4j
    12. @Service
    13. @CacheConfig(cacheNames = "caffeineCacheManager")
    14. public class UserInfoServiceImpl implements UserInfoService {
    15.     /**
    16.      * 模拟数据库存储数据
    17.      */
    18.     private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();
    19.     @Override
    20.     @CachePut(key = "#userInfo.id")
    21.     public void addUserInfo(UserInfo userInfo) {
    22.         log.info("create");
    23.         userInfoMap.put(userInfo.getId(), userInfo);
    24.     }
    25.     @Override
    26.     @Cacheable(key = "#id")
    27.     public UserInfo getByName(Integer id) {
    28.         log.info("get");
    29.         return userInfoMap.get(id);
    30.     }
    31.     @Override
    32.     @CachePut(key = "#userInfo.id")
    33.     public UserInfo updateUserInfo(UserInfo userInfo) {
    34.         log.info("update");
    35.         if (!userInfoMap.containsKey(userInfo.getId())) {
    36.             return null;
    37.         }
    38.         // 取旧的值
    39.         UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
    40.         // 替换内容
    41.         if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
    42.             oldUserInfo.setAge(userInfo.getAge());
    43.         }
    44.         if (!StringUtils.isEmpty(oldUserInfo.getName())) {
    45.             oldUserInfo.setName(userInfo.getName());
    46.         }
    47.         if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
    48.             oldUserInfo.setSex(userInfo.getSex());
    49.         }
    50.         // 将新的对象存储,更新旧对象信息
    51.         userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
    52.         // 返回新对象信息
    53.         return oldUserInfo;
    54.     }
    55.     @Override
    56.     @CacheEvict(key = "#id")
    57.     public void deleteById(Integer id) {
    58.         log.info("delete");
    59.         userInfoMap.remove(id);
    60.     }
    61. }

    5、测试的 Controller 类

    1. import mydlq.club.example.entity.UserInfo;
    2. import mydlq.club.example.service.UserInfoService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.*;
    5. @RestController
    6. @RequestMapping
    7. public class UserInfoController {
    8.     @Autowired
    9.     private UserInfoService userInfoService;
    10.     @GetMapping("/userInfo/{id}")
    11.     public Object getUserInfo(@PathVariable Integer id) {
    12.         UserInfo userInfo = userInfoService.getByName(id);
    13.         if (userInfo == null) {
    14.             return "没有该用户";
    15.         }
    16.         return userInfo;
    17.     }
    18.     @PostMapping("/userInfo")
    19.     public Object createUserInfo(@RequestBody UserInfo userInfo) {
    20.         userInfoService.addUserInfo(userInfo);
    21.         return "SUCCESS";
    22.     }
    23.     @PutMapping("/userInfo")
    24.     public Object updateUserInfo(@RequestBody UserInfo userInfo) {
    25.         UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
    26.         if (newUserInfo == null){
    27.             return "不存在该用户";
    28.         }
    29.         return newUserInfo;
    30.     }
    31.     @DeleteMapping("/userInfo/{id}")
    32.     public Object deleteUserInfo(@PathVariable Integer id) {
    33.         userInfoService.deleteById(id);
    34.         return "SUCCESS";
    35.     }
    36. }
  • 相关阅读:
    spring高级50讲
    图像特征(一)
    js创建对象的几种设计模式
    基于React俄罗斯方块h5小游戏源码响应式支持PC+手机
    POJ 3279 Fliptile 反转 + 点灯游戏
    python基于django的宠物知识分享网站
    Flutter的专属Skia引擎解析+用法原理
    k8s-kubernetes常用命令,服务部署,可视化控制台安装及token的生成
    《CSS学习笔记》弹性盒子
    超过50%的测试员不懂“测试”,凭什么月薪20k?
  • 原文地址:https://blog.csdn.net/m0_55849656/article/details/125412577