SpringBoot 使用 Caffeine 本地缓存

一、本地缓存介绍

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

之前介绍过 Redis 这种 NoSql 作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。

二、缓存组件 Caffeine 介绍

按 Caffeine Github 文档描述,Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboot 2.x) 后,spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。

2.1. Caffeine 性能

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

在这里插入图片描述

2.2. Caffeine 配置说明

在这里插入图片描述
注意:

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

2.3. 软引用与弱引用

软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存.

// 软引用
Caffeine.newBuilder().softValues().build();

// 弱引用
Caffeine.newBuilder().weakKeys().weakValues().build();
三、SpringBoot 集成 Caffeine 方式一
3.1. Maven 引入相关依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--字符串工具类-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
3.2. 配置缓存配置类
package com.gblfy.config;

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;

/**
 * 本地caffeine缓存配置
 *
 * @author gblfy
 * @date 2022-03-15
 */
@Configuration
public class CaffeineCacheConfig {
    
    

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

3.3. 定义实体对象
package com.gblfy;

import lombok.Data;
import lombok.ToString;

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

3.4. 定义服务接口类
package com.gblfy.service;

import com.gblfy.entity.UserInfo;

/**
 * 用户模块接口
 *
 * @author gblfy
 * @date 2022-03-15
 */
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);

}


3.5. 定义服务接口实现类
package com.gblfy.service.impl;

import com.gblfy.entity.UserInfo;
import com.gblfy.service.UserInfoService;
import com.gblfy.uitls.CaffeineUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户模块接口实现类
 *
 * @author gblfy
 * @date 2022-03-15
 */
@Slf4j
@Service
public class UserInfoServiceImpl implements UserInfoService {
    
    

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

    @Autowired
    private CaffeineUtils caffeineUtils;

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

    @Override
    public UserInfo getByName(Integer userId) {
    
    
        // 先从缓存读取
        UserInfo userInfo = caffeineUtils.getObjCacheByKey(String.valueOf(userId), UserInfo.class);
        if (userInfo != null) {
    
    
            return userInfo;
        }
        // 如果缓存中不存在,则从库中查找
        log.info("get");
        userInfo = userInfoMap.get(userId);
        // 如果用户信息不为空,则加入缓存
        if (userInfo != null) {
    
    
            caffeineUtils.putAndUpdateCache(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.isNotBlank(oldUserInfo.getName())) {
    
    
            oldUserInfo.setName(userInfo.getName());
        }
        if (StringUtils.isNotBlank(oldUserInfo.getSex())) {
    
    
            oldUserInfo.setSex(userInfo.getSex());
        }

        oldUserInfo.setAge(userInfo.getAge());
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 替换缓存中的值
        caffeineUtils.putAndUpdateCache(String.valueOf(oldUserInfo.getId()), oldUserInfo);
        return oldUserInfo;
    }

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

}

3.6. Caffeine工具类
package com.gblfy.uitls;

import com.github.benmanes.caffeine.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Caffeine缓存工具类
 *
 * @Author gblfy
 * @Date 2022-03-15 14:58
 **/
@Component
public class CaffeineUtils {
    
    

    @Autowired
    Cache<String, Object> caffeineCache;

    /**
     * 添加或更新缓存
     *
     * @param key
     * @param value
     */
    public void putAndUpdateCache(String key, Object value) {
    
    
        caffeineCache.put(key, value);
    }


    /**
     * 获取对象缓存
     *
     * @param key
     * @return
     */
    public <T> T getObjCacheByKey(String key, Class<T> t) {
    
    
        caffeineCache.getIfPresent(key);
        return (T) caffeineCache.asMap().get(key);
    }

    /**
     * 根据key删除缓存
     *
     * @param key
     */
    public void removeCacheByKey(String key) {
    
    
        // 从缓存中删除
        caffeineCache.asMap().remove(key);
    }
}

3.7. 测试案例
package com.gblfy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户模块入口
 *
 * @author gblfy
 * @date 2022-03-15
 */
@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";
    }

}
四、测试案例
4.1. 添加用户
请求方式:POST
content-type:application/json
测试地址:localhost:8080/userInfo

报文内容

{
    
    
    'id':1,
    'name':"yx",
     'sex':"女",
      'age':2
}
4.2. 查询用户
请求方式:GET
测试地址:localhost:8080/userInfo/1
4.3. 更新用户
请求方式:PUT
content-type:application/json
测试地址:localhost:8080/userInfo

报文内容

{
    
    
    'id':1,
    'name':"gblfy",
     'sex':"男",
      'age':22
}
4.4. 删除用户
请求方式:DELETE
测试地址:localhost:8080/userInfo/1
4.5. 效果图

在这里插入图片描述

五、第二种整合方式
5.1. 依赖

上面基础上添加

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
5.2. 接口实现类(替换)
package com.gblfy;

import lombok.extern.slf4j.Slf4j;
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;

/**
 * 用户模块接口实现类
 *
 * @author gblfy
 * @date 2022-03-15
 */
@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);
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_40816738/article/details/123500570