Spring Boot 2.x 把 Guava 干掉了選擇本地緩存之王 Caffeine(推薦)
環(huán)境配置:
- JDK 版本:1.8
- Caffeine 版本:2.8.0
- SpringBoot 版本:2.2.2.RELEASE
一、本地緩存介紹
緩存在日常開(kāi)發(fā)中啟動(dòng)至關(guān)重要的作用,由于是存儲(chǔ)在內(nèi)存中,數(shù)據(jù)的讀取速度是非??斓?,能大量減少對(duì)數(shù)據(jù)庫(kù)的訪(fǎng)問(wèn),減少數(shù)據(jù)庫(kù)的壓力。
之前介紹過(guò) Redis 這種 NoSql 作為緩存組件,它能夠很好的作為分布式緩存組件提供多個(gè)服務(wù)間的緩存,但是 Redis 這種還是需要網(wǎng)絡(luò)開(kāi)銷(xiāo),增加時(shí)耗。本地緩存是直接從本地內(nèi)存中讀取,沒(méi)有網(wǎng)絡(luò)開(kāi)銷(xiāo),例如秒殺系統(tǒng)或者數(shù)據(jù)量小的緩存等,比遠(yuǎn)程緩存更合適。
二、緩存組件 Caffeine 介紹
按 Caffeine Github 文檔描述,Caffeine 是基于 JAVA 8 的高性能緩存庫(kù)。并且在 spring5 (springboot 2.x) 后,spring 官方放棄了 Guava,而使用了性能更優(yōu)秀的 Caffeine 作為默認(rèn)緩存組件。
1、Caffeine 性能
可以通過(guò)下圖觀測(cè)到,在下面緩存組件中 Caffeine 性能是其中最好的。
2、Caffeine 配置說(shuō)明
參數(shù) | 類(lèi)型 | 描述 |
---|---|---|
initialCapacity | integer | 初始的緩存空間大小 |
maximumSize | long | 緩存的最大條數(shù) |
maximumWeight | long | 緩存的最大權(quán)重 |
expireAfterAccess | duration | 最后一次寫(xiě)入或訪(fǎng)問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期 |
refreshAfterWrite | duration | 最后一次寫(xiě)入后經(jīng)過(guò)固定時(shí)間過(guò)期 |
refreshAfterWrite | duration | 創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過(guò)固定的時(shí)間間隔,刷新緩存 |
weakKeys | boolean | 打開(kāi) key 的弱引用 |
weakValues | boolean | 打開(kāi) value 的弱引用 |
softValues | boolean | 打開(kāi) value 的軟引用 |
recordStats | - | 開(kāi)發(fā)統(tǒng)計(jì)功能 |
注意:
weakValues
和softValues
不可以同時(shí)使用。maximumSize
和maximumWeight
不可以同時(shí)使用。expireAfterWrite
和expireAfterAccess
同事存在時(shí),以expireAfterWrite
為準(zhǔn)。
3、軟引用與弱引用
- 軟引用: 如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。
- 弱引用: 弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線(xiàn)程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存
// 軟引用 Caffeine.newBuilder().softValues().build(); // 弱引用 Caffeine.newBuilder().weakKeys().weakValues().build();
三、SpringBoot 集成 Caffeine 兩種方式
SpringBoot 有倆種使用 Caffeine 作為緩存的方式:
方式一: 直接引入 Caffeine 依賴(lài),然后使用 Caffeine 方法實(shí)現(xiàn)緩存。
方式二: 引入 Caffeine 和 Spring Cache 依賴(lài),使用 SpringCache 注解方法實(shí)現(xiàn)緩存。
下面將介紹下,這倆中集成方式都是如何實(shí)現(xiàn)的。
Spring Boot 基礎(chǔ)就不介紹了,推薦看下這個(gè)教程:
https://github.com/javastacks/spring-boot-best-practice
四、SpringBoot 集成Caffeine 方式一
1、Maven 引入相關(guān)依賴(lài)
<?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、配置緩存配置類(lèi)
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() // 設(shè)置最后一次寫(xiě)入或訪(fǎng)問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期 .expireAfterWrite(60, TimeUnit.SECONDS) // 初始的緩存空間大小 .initialCapacity(100) // 緩存的最大條數(shù) .maximumSize(1000) .build(); } }
3、定義測(cè)試的實(shí)體對(duì)象
import lombok.Data; import lombok.ToString; @Data @ToString public class UserInfo { private Integer id; private String name; private String sex; private Integer age; }
4、定義服務(wù)接口類(lèi)和實(shí)現(xiàn)類(lèi)
UserInfoService
import mydlq.club.example.entity.UserInfo; public interface UserInfoService { /** * 增加用戶(hù)信息 * * @param userInfo 用戶(hù)信息 */ void addUserInfo(UserInfo userInfo); /** * 獲取用戶(hù)信息 * * @param id 用戶(hù)ID * @return 用戶(hù)信息 */ UserInfo getByName(Integer id); /** * 修改用戶(hù)信息 * * @param userInfo 用戶(hù)信息 * @return 用戶(hù)信息 */ UserInfo updateUserInfo(UserInfo userInfo); /** * 刪除用戶(hù)信息 * * @param id 用戶(hù)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 { /** * 模擬數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù) */ 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; } // 如果緩存中不存在,則從庫(kù)中查找 log.info("get"); userInfo = userInfoMap.get(id); // 如果用戶(hù)信息不為空,則加入緩存 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()); // 替換內(nèi)容 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()); } // 將新的對(duì)象存儲(chǔ),更新舊對(duì)象信息 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、測(cè)試的 Controller 類(lèi)
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 "沒(méi)有該用戶(hù)"; } 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 "不存在該用戶(hù)"; } return newUserInfo; } @DeleteMapping("/userInfo/{id}") public Object deleteUserInfo(@PathVariable Integer id) { userInfoService.deleteById(id); return "SUCCESS"; } }
五、SpringBoot 集成 Caffeine 方式二
1、Maven 引入相關(guān)依賴(lài)
<?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、配置緩存配置類(lèi)
@Configuration public class CacheConfig { /** * 配置緩存管理器 * * @return 緩存管理器 */ @Bean("caffeineCacheManager") public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() // 設(shè)置最后一次寫(xiě)入或訪(fǎng)問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期 .expireAfterAccess(60, TimeUnit.SECONDS) // 初始的緩存空間大小 .initialCapacity(100) // 緩存的最大條數(shù) .maximumSize(1000)); return cacheManager; } }
3、定義測(cè)試的實(shí)體對(duì)象
@Data @ToString public class UserInfo { private Integer id; private String name; private String sex; private Integer age; }
4、定義服務(wù)接口類(lèi)和實(shí)現(xiàn)類(lèi)
服務(wù)接口
import mydlq.club.example.entity.UserInfo; public interface UserInfoService { /** * 增加用戶(hù)信息 * * @param userInfo 用戶(hù)信息 */ void addUserInfo(UserInfo userInfo); /** * 獲取用戶(hù)信息 * * @param id 用戶(hù)ID * @return 用戶(hù)信息 */ UserInfo getByName(Integer id); /** * 修改用戶(hù)信息 * * @param userInfo 用戶(hù)信息 * @return 用戶(hù)信息 */ UserInfo updateUserInfo(UserInfo userInfo); /** * 刪除用戶(hù)信息 * * @param id 用戶(hù)ID */ void deleteById(Integer id); }
服務(wù)實(shí)現(xiàn)類(lèi)
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 { /** * 模擬數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù) */ 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()); // 替換內(nèi)容 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()); } // 將新的對(duì)象存儲(chǔ),更新舊對(duì)象信息 userInfoMap.put(oldUserInfo.getId(), oldUserInfo); // 返回新對(duì)象信息 return oldUserInfo; } @Override @CacheEvict(key = "#id") public void deleteById(Integer id) { log.info("delete"); userInfoMap.remove(id); } }
5、測(cè)試的 Controller 類(lèi)
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 "沒(méi)有該用戶(hù)"; } 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 "不存在該用戶(hù)"; } 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
到此這篇關(guān)于Spring Boot 2.x 把 Guava 干掉了選擇本地緩存之王 Caffeine的文章就介紹到這了,更多相關(guān)Spring Boot 2.x Caffeine內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jpa異常No entity found for query問(wèn)題解決
這篇文章主要為大家介紹了jpa異常之No entity found for query的異常問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03普通類(lèi)注入不進(jìn)spring bean的解決方法
這篇文章主要介紹了普通類(lèi)注入不進(jìn)spring bean的解決方法,幫助大家更好的理解和使用spring bean,感興趣的朋友可以了解下2021-01-01Java使用原型模式展現(xiàn)每日生活應(yīng)用案例詳解
這篇文章主要介紹了Java使用原型模式展現(xiàn)每日生活應(yīng)用案例,較為詳細(xì)的分析了原型模式的概念、原理及Java使用原型模式展現(xiàn)每日生活案例的相關(guān)操作步驟與注意事項(xiàng),需要的朋友可以參考下2018-05-05spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題
這篇文章主要介紹了spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02詳解spring boot容器加載完后執(zhí)行特定操作
這篇文章主要介紹了詳解spring boot容器加載完后執(zhí)行特定操作,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01IDEA?Error:java:無(wú)效的源發(fā)行版:13的解決過(guò)程
之前用idea運(yùn)行時(shí),也會(huì)出現(xiàn)這種情況,后面通過(guò)網(wǎng)上的資料解決了這個(gè)問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于IDEA?Error:java:無(wú)效的源發(fā)行版:13的解決過(guò)程,需要的朋友可以參考下2023-01-01MyBatis使用級(jí)聯(lián)操作解決lombok構(gòu)造方法識(shí)別失敗問(wèn)題
這篇文章主要介紹了MyBatis使用級(jí)聯(lián)操作解決lombok構(gòu)造方法識(shí)別失敗問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Java 實(shí)戰(zhàn)項(xiàng)目錘煉之仿天貓網(wǎng)上商城的實(shí)現(xiàn)流程
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+servlet+mysql+ajax實(shí)現(xiàn)一個(gè)仿天貓網(wǎng)上商城項(xiàng)目,大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11深入剖析java中String、StringBuffer、StringBuilder的區(qū)別
下面小編就為大家?guī)?lái)一篇深入剖析java中String、StringBuffer、StringBuilder的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05