文章目录
- 一、本地缓存介绍
- 二、缓存组件 Caffeine 介绍
- 2.1. Caffeine 性能
- 2.2. Caffeine 配置说明
- 2.3. 软引用与弱引用
- 三、SpringBoot 集成 Caffeine 方式一
- 3.1. Maven 引入相关依赖
- 3.2. 配置缓存配置类
- 3.3. 定义实体对象
- 3.4. 定义服务接口类
- 3.5. 定义服务接口实现类
- 3.6. Caffeine工具类
- 3.7. 测试案例
- 四、测试案例
- 4.1. 添加用户
- 4.2. 查询用户
- 4.3. 更新用户
- 4.4. 删除用户
- 4.5. 效果图
- 五、第二种整合方式
- 5.1. 依赖
- 5.2. 接口实现类(替换)
一、本地缓存介绍
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。
之前介绍过 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 {@Beanpublic 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<>();@Autowiredprivate CaffeineUtils caffeineUtils;@Overridepublic void addUserInfo(UserInfo userInfo) {log.info("create");userInfoMap.put(userInfo.getId(), userInfo);// 加入缓存caffeineUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@AutowiredCache<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 {@Autowiredprivate 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);}}