Java緩存框架之Caffeine源碼解析
前言
在系統(tǒng)中,有些數(shù)據(jù),訪問(wèn)十分頻繁,往往把這些數(shù)據(jù)放入分布式緩存中,但為了減少網(wǎng)絡(luò)傳輸,加快響應(yīng)速度,緩存分布式緩存讀壓力,會(huì)把這些數(shù)據(jù)緩存到本地JVM中,大多是先取本地緩存中,再取分布式緩存中的數(shù)據(jù),Caffeine是一個(gè)高性能Java 緩存庫(kù),使用Java8對(duì)Guava緩存重寫(xiě)版本,在Spring Boot 2.0中將取代Guava
一. SpringBoot緩存注解相關(guān)知識(shí)點(diǎn)
1. @Cacheable:
@Cacheable可以標(biāo)記在一個(gè)方法上,也可以標(biāo)記在一個(gè)類上。當(dāng)標(biāo)記在一個(gè)方法上時(shí)表示該方法是支持緩存的,當(dāng)標(biāo)記在一個(gè)類上時(shí)則表示該類所有的方法都是支持緩存的。對(duì)于一個(gè)支持緩存的方法,Spring會(huì)在其被調(diào)用后將其返回值緩存起來(lái),以保證下次利用同樣的參數(shù)來(lái)執(zhí)行該方法時(shí)可以直接從緩存中獲取結(jié)果,而不需要再次執(zhí)行該方法。Spring在緩存方法的返回值時(shí)是以鍵值對(duì)進(jìn)行緩存的,值就是方法的返回結(jié)果,至于鍵的話,Spring又支持兩種策略,默認(rèn)策略和自定義策略,這個(gè)稍后會(huì)進(jìn)行說(shuō)明。需要注意的是當(dāng)一個(gè)支持緩存的方法在對(duì)象內(nèi)部被調(diào)用時(shí)是不會(huì)觸發(fā)緩存功能的。@Cacheable可以指定三個(gè)屬性,value、key和condition。
參數(shù) | 解釋 | 例子 |
value | 緩存的名稱,在 spring 配置文件中定義,必須指定至少一個(gè) | 例如:@Cacheable(value=”mycache”) |
key | 緩存的key,可以為空,如果指定要按照SpEL表達(dá)式編寫(xiě),如不指定,則按照方法所有參數(shù)組合 | @Cacheable(value=”testcache”,key=”#userName”) |
condition | 緩存的條件,可以為空,使用 SpEL 編寫(xiě),返回 true 或者 false,只有為 true 才進(jìn)行緩存 | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
使用案例:
// key 是指?jìng)魅霑r(shí)的參數(shù) @Cacheable(value="users", key="#id") public Integer find(Integer id) { return id; } // 表示第一個(gè)參數(shù) @Cacheable(value="users", key="#p0") public Long find(Long id) { return id; } // 表示User中的id值 @Cacheable(value="users", key="#user.id") public User find(User user) { return user; } // 表示第一個(gè)參數(shù)里的id屬性值 @Cacheable(value="users", key="#p0.id") public User find(User user) { return user; }
除了上面的案例使用方法,還有以下幾種:
屬性名稱 | 描述 | 示例 |
methodName | 當(dāng)前方法名 | #root.methodName |
method | 當(dāng)前方法 | #root.method.name |
target | 當(dāng)前被調(diào)用的對(duì)象 | #root.target |
targetClass | 當(dāng)前被調(diào)用的對(duì)象的class | #root.targetClass |
args | 當(dāng)前方法參數(shù)組成的數(shù)組 | #root.args[0] |
caches | 當(dāng)前被調(diào)用的方法使用的Cache | #root.caches[0].name |
condition屬性指定發(fā)生的條件
有的時(shí)候我們可能并不希望緩存一個(gè)方法所有的返回結(jié)果。通過(guò)condition屬性可以實(shí)現(xiàn)這一功能。condition屬性默認(rèn)為空,表示將緩存所有的調(diào)用情形。其值是通過(guò)SpringEL表達(dá)式來(lái)指定的,當(dāng)為true時(shí)表示進(jìn)行緩存處理;當(dāng)為false時(shí)表示不進(jìn)行緩存處理,即每次調(diào)用該方法時(shí)該方法都會(huì)執(zhí)行一次。如下示例表示只有當(dāng)user的id為偶數(shù)時(shí)才會(huì)進(jìn)行緩存。
// 根據(jù)條件判斷是否緩存 @Cacheable(value="users", key="#user.id", condition="#user.id%2==0") public User find(User user) { return user; }
2. CacheEvict
@CacheEvict是用來(lái)標(biāo)注在需要清除緩存元素的方法或類上的。當(dāng)標(biāo)記在一個(gè)類上時(shí)表示其中所有的方法的執(zhí)行都會(huì)觸發(fā)緩存的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語(yǔ)義與@Cacheable對(duì)應(yīng)的屬性類似。即value表示清除操作是發(fā)生在哪些Cache上的(對(duì)應(yīng)Cache的名稱);key表示需要清除的是哪個(gè)key,如未指定則會(huì)使用默認(rèn)策略生成的key;condition表示清除操作發(fā)生的條件。下面我們來(lái)介紹一下新出現(xiàn)的兩個(gè)屬性allEntries和beforeInvocation。
allEntries屬性
allEntries是boolean類型,表示是否需要清除緩存中的所有元素。默認(rèn)為false,表示不需要。當(dāng)指定了allEntries為true時(shí),Spring Cache將忽略指定的key。有的時(shí)候我們需要Cache一下清除所有的元素,這比一個(gè)一個(gè)清除元素更有效率。
@CacheEvict(value="user", allEntries=true) public void delete(Integer id) { System.out.println(id); }
beforeInvocation屬性
清除操作默認(rèn)是在對(duì)應(yīng)方法成功執(zhí)行之后觸發(fā)的,即方法如果因?yàn)閽伋霎惓6茨艹晒Ψ祷貢r(shí)也不會(huì)觸發(fā)清除操作。使用beforeInvocation可以改變觸發(fā)清除操作的時(shí)間,當(dāng)我們指定該屬性值為true時(shí),Spring會(huì)在調(diào)用該方法之前清除緩存中的指定元素。
@CacheEvict(value="user", beforeInvocation=true) public void delete(Integer id) { System.out.println(id); }
3. @Caching
@Caching注解可以讓我們?cè)谝粋€(gè)方法或者類上同時(shí)指定多個(gè)Spring Cache相關(guān)的注解。其擁有三個(gè)屬性:cacheable、put和evict,分別用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching( cacheable = @Cacheable("user"), evict = { @CacheEvict(value = "user1", key = "#id"), @CacheEvict(value = "user", allEntries = true)}) public Integer find(Integer id) { return id; }
4. 自定義注解
Spring允許我們?cè)谂渲每删彺娴姆椒〞r(shí)使用自定義的注解,前提是自定義的注解上必須使用對(duì)應(yīng)的注解進(jìn)行標(biāo)注。如我們有如下這么一個(gè)使用@Cacheable進(jìn)行標(biāo)注的自定義注解
二. Caffeine相關(guān)知識(shí)點(diǎn)
Caffeine常用配置說(shuō)明:
- initialCapacity=[integer]: 初始的緩存空間大小
- maximumSize=[long]: 緩存的最大條數(shù)
- maximumWeight=[long]: 緩存的最大權(quán)重
- expireAfterAccess=[duration]: 最后一次寫(xiě)入或訪問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期
- expireAfterWrite=[duration]: 最后一次寫(xiě)入后經(jīng)過(guò)固定時(shí)間過(guò)期
- refreshAfterWrite=[duration]: 創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過(guò)固定的時(shí)間間隔,刷新緩存
注意點(diǎn):
- expireAfterWrite和expireAfterAccess同事存在時(shí),以expireAfterWrite為準(zhǔn)
- maximumSize和maximumWeight不可以同時(shí)使用
配置案例:
spring: # 配置緩存,初始緩存容量為10,最大容量為200,過(guò)期時(shí)間(這里配置寫(xiě)入后過(guò)期時(shí)間為3秒) cache: type: caffeine caffeine: spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s
三. pringBoot集成Caffeine簡(jiǎn)單demo
1. pom文件
<?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 http://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.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.gj</groupId> <artifactId>boot-cache-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>boot-cache-demo</name> <description>Demo project for Spring Boot</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.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>cn.gjing</groupId> <artifactId>tools-common</artifactId> <version>1.0.4</version> </dependency> <dependency> <groupId>cn.gjing</groupId> <artifactId>tools-starter-swagger</artifactId> <version>1.0.9</version> </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> <version>2.7.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2. 配置文件
server: port: 8080 spring: application: name: springboot-cache-demo # 配置數(shù)據(jù)庫(kù)信息和連接池 datasource: url: jdbc:mysql://127.0.0.1:3306/cache?characterEncoding=utf8&useSSL=false&serverTimezone=UTC password: root username: root driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 1 maximum-pool-size: 15 idle-timeout: 30000 connection-timeout: 20000 # 開(kāi)啟jpa自動(dòng)建表 jpa: database: mysql hibernate: ddl-auto: update database-platform: org.hibernate.dialect.MySQL5InnoDBDialect # 配置緩存,初始緩存容量,最大容量,過(guò)期時(shí)間(這里配置寫(xiě)入后過(guò)期時(shí)間) cache: type: caffeine caffeine: spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s # 配置controller路徑 swagger: base-package: com.gj.web title: springboot使用caffeine緩存
3. 啟動(dòng)類
@SpringBootApplication @EnableSwagger @EnableJpaAuditing @EnableCaching public class BootCacheDemoApplication { public static void main(String[] args) { SpringApplication.run(BootCacheDemoApplication.class, args); } }
4. 定義一個(gè)實(shí)體
/** * @author Gjing **/ @Entity @Table(name = "custom") @Data @Builder @NoArgsConstructor @AllArgsConstructor @EntityListeners(AuditingEntityListener.class) public class Custom { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "custom_name", columnDefinition = "varchar(20) not null comment '用戶名'") private String customName; @Column(name = "custom_number", columnDefinition = "int not null comment '用戶編號(hào)'") private Integer customNumber; @Column(name = "create_time", columnDefinition = "datetime") @CreatedDate private Date createTime; @Column(name = "update_time", columnDefinition = "datetime") @LastModifiedDate private Date updateTime; }
5. 定義持久層接口
/** * @author Gjing **/ @Repository public interface CustomRepository extends JpaRepository<Custom,Integer> { /** * 通過(guò)用戶名查詢 * @param customName 用戶名 * @return 用戶 */ Custom findByCustomName(String customName); }
6. 定義service
/** * @author Gjing **/ @Service @Slf4j public class CustomService { @Resource private CustomRepository customRepository; /** * 獲取一個(gè)用戶 * * @param customId 用戶id * @return custom */ @Cacheable(value = "user", key = "#customId") public Custom getCustom(Integer customId) { log.warn("通過(guò)數(shù)據(jù)庫(kù)去查詢,用戶id為:{}", customId); return customRepository.findById(customId) .orElseThrow(() -> new UserNotFoundException("Users don't exist")); } @CacheEvict(value = "user", key = "#customId") public void deleteCustom(Integer customId) { Custom custom = customRepository.findById(customId) .orElseThrow(() -> new UserNotFoundException("Users don't exist")); customRepository.delete(custom); } public Boolean insertCustom(String customName) { Custom custom = customRepository.findByCustomName(customName); if (custom == null) { customRepository.save(Custom.builder() .customName(customName) .customNumber(Integer.valueOf(RandomUtil.generateNumber(6))) .build()); return true; } return false; } }
7. 定義異常
/** * @author Gjing **/ public class UserNotFoundException extends RuntimeException{ public UserNotFoundException(String message) { super(message); } } /** * @author Gjing **/ @RestControllerAdvice class DemoExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity userNot(UserNotFoundException e) { return ResponseEntity.badRequest().body(ErrorResult.error(e.getMessage())); } }
8. 定義接口
/** * @author Gjing **/ @RestController public class CustomController { @Resource private CustomService customService; @PostMapping("/custom") @ApiOperation(value = "添加用戶", httpMethod = "POST") @ApiImplicitParam(name = "customName", value = "用戶名", required = true, dataType = "String", paramType = "Query") public ResponseEntity insertCustom(String customName) { Boolean insertCustom = customService.insertCustom(customName); if (insertCustom) { return ResponseEntity.ok("New successful"); } return ResponseEntity.ok("Add failed, user already exists"); } @GetMapping("/custom/{custom-id}") @ApiOperation(value = "查詢指定用戶", httpMethod = "GET") public ResponseEntity getCustom(@PathVariable("custom-id") Integer customId) { return ResponseEntity.ok(customService.getCustom(customId)); } @DeleteMapping("/custom") @ApiOperation(value = "刪除指定用戶", httpMethod = "DELETE") @ApiImplicitParam(name = "customId", value = "用戶id", required = true, dataType = "int", paramType = "Query") public ResponseEntity deleteCustom(Integer customId) { customService.deleteCustom(customId); return ResponseEntity.ok("Delete successful"); } }
啟動(dòng)后訪問(wèn)//localhost:8080/swagger-ui.html即可測(cè)試,第一次獲取數(shù)據(jù)會(huì)從數(shù)據(jù)庫(kù)中查詢,接下來(lái)會(huì)直接讀取緩存直到緩存失效
到此這篇關(guān)于Java緩存框架之Caffeine源碼解析的文章就介紹到這了,更多相關(guān)Caffeine源碼解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用springboot防止反編譯proguard+xjar
介紹了三種代碼混淆和加密工具的使用方法:ProGuard、Xjar和ClassFinal,ProGuard用于混淆Java字節(jié)碼,Xjar提供對(duì)JAR包內(nèi)資源的加密和動(dòng)態(tài)解密,而ClassFinal則支持直接加密JAR包或WAR包,通過(guò)預(yù)研和實(shí)際操作2024-11-11Spring?Cloud?OpenFeign實(shí)例介紹使用方法
Spring?Cloud?OpenFeign?對(duì)?Feign?進(jìn)行了二次封裝,使得在?Spring?Cloud?中使用?Feign?的時(shí)候,可以做到使用?HTTP?請(qǐng)求訪問(wèn)遠(yuǎn)程服務(wù),就像調(diào)用本地方法一樣的,開(kāi)發(fā)者完全感知不到這是在調(diào)用遠(yuǎn)程訪問(wèn),更感知不到在訪問(wèn)?HTTP?請(qǐng)求2022-09-09SpringBoot響應(yīng)Json數(shù)據(jù)亂碼通過(guò)配置的解決
這篇文章主要介紹了SpringBoot響應(yīng)Json數(shù)據(jù)亂碼通過(guò)配置的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11淺析Java中Map與HashMap,Hashtable,HashSet的區(qū)別
HashMap和Hashtable兩個(gè)類都實(shí)現(xiàn)了Map接口,二者保存K-V對(duì)(key-value對(duì));HashSet則實(shí)現(xiàn)了Set接口,性質(zhì)類似于集合2013-09-09java8 統(tǒng)計(jì)字符串字母?jìng)€(gè)數(shù)的幾種方法總結(jié)(推薦)
下面小編就為大家分享一篇java8 統(tǒng)計(jì)字符串字母?jìng)€(gè)數(shù)的幾種方法總結(jié)(推薦),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)吧2017-11-11網(wǎng)關(guān)Gateway過(guò)濾器的使用詳解
Gateway網(wǎng)關(guān)的過(guò)濾器分為兩種,一種是局部過(guò)濾器,一種是全局過(guò)濾器,過(guò)濾器就是過(guò)濾一些請(qǐng)求,在這里,全局過(guò)濾器的作用是處理一切進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)響應(yīng),與GatewayFilter的作用一樣,本文給大家介紹網(wǎng)關(guān)Gateway過(guò)濾器的使用,感興趣的朋友一起看看吧2022-07-07Java實(shí)現(xiàn)讀取Excel文件功能(EasyExcel初使用)
EasyExcel是一款基于Java語(yǔ)言的開(kāi)源Excel解析工具,可以幫助我們快速、高效地讀取和寫(xiě)入Excel文件,這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)讀取Excel文件功能的相關(guān)資料,使用的是EasyExcel,需要的朋友可以參考下2024-07-07