Spring Boot 整合 Caffeine 本地缓存及 Spring Cache 注解的使用
介绍
在现代的Web应用程序中,缓存是提高性能和响应速度的重要手段之一。Spring Boot提供了对缓存的良好支持,并且可以轻松地整合Caffeine本地缓存作为缓存提供者。结合Spring Cache注解,可以实现对方法级别的缓存,从而提高系统的性能和响应速度。
添加依赖
首先,在pom.xml文件中添加Spring Boot和Caffeine、Spring Cache缓存相关的依赖:
java
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置Caffeine缓存管理器
在配置类中创建Caffeine缓存管理器的Bean,并指定缓存的配置信息:
java
@EnableCaching
@Configuration
public class CacheManagerConfig {
@Bean("caffeineCacheManager")
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
// 设置过期时间,写入后五分钟过期
.expireAfterWrite(5, TimeUnit.MINUTES)
// 初始化缓存空间大小
.initialCapacity(100)
// 最大的缓存条数
.maximumSize(200)
);
return cacheManager;
}
}
配置说明:
-
initialCapacity:初始容量,表示缓存的初始容量大小。
-
maximumSize:最大缓存大小,表示缓存的最大容量限制。当缓存中的条目数量达到该值时,Caffeine会根据指定的策略进行清理。
-
expireAfterWrite:写入后过期时间,表示缓存条目在写入后经过指定时间后过期。
-
expireAfterAccess:访问后过期时间,表示缓存条目在最后一次访问后经过指定时间后过期。
-
refreshAfterWrite:写入后刷新时间,表示缓存条目在写入后经过指定时间后自动刷新。
-
recordStats:是否开启统计信息记录,用于监控缓存的命中率、加载时间等统计信息。
加上@EnableCaching
后Spring Cache
的缓存注解才能生效。
除了上面这种配置之外,还可以直接在application.yml文件中进行配置,如下:
java
spring:
cache:
type: caffeine
caffeine:
spec: initialCapacity=100,maximumSize=200,expireAfterWrite=5m
如果使用这种方式,就可以忽略前面的caffeineCacheManager
的自定义bean
配置信息,建议使用自定义的bean配置信息。
SpringCache的注解介绍和使用
1. @EnableCaching
在配置类上使用该注解,启用Spring的缓存支持。
前面 CacheManagerConfig
类就使用了该注解开启Spring缓存的支持。
2. @Cacheable
在方法上使用该注解,表示该方法的返回结果将被缓存。当再次调用该方法时,如果缓存中已存在相同的参数,则直接返回缓存中的结果,而不执行方法体。
注解核心设置:
java
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
-
value / cacheNames:缓存的名称,用于指定要使用哪个缓存。可以指定一个或多个缓存名称。
-
key:缓存的键,用于指定缓存的键值。默认情况下,会使用方法的所有参数作为缓存的键。
-
keyGenerator:用于指定自定义的缓存键生成器。
-
cacheManager:用于指定使用的缓存管理器的名称。
-
cacheResolver:用于指定自定义的缓存解析器。
-
condition:用于指定条件表达式,当条件为true时,才会进行缓存。
-
unless:用于指定条件表达式,当条件为true时,不会进行缓存。
-
sync:用于指定是否使用同步模式进行缓存。
简单使用案例:
案例一:
java
@Cacheable(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "say")
public String sayHello(String name) {
return "hello " + name + "-->" + UUID.randomUUID();
}
key为SpEL表达式,因此如果要写字符串时要用单引号括起来。如果name参数为高启强,缓存key的值为p_高启强
。
第一次请求:http://localhost:8080/api/cache/say?name=高启强
代码debug可以看到第一次查询会执行该方法。
页面打印返回信息:hello 高启强-->79c86d44-abbc-4892-b66f-f7786d2df0c0
第二次请求:http://localhost:8080/api/cache/say?name=高启强
第二次查询代码debug没有执行该方法。
页面打印返回信息:hello 高启强-->79c86d44-abbc-4892-b66f-f7786d2df0c0
说明缓存生效了。
案例二:
java
@Cacheable(value = "condition", cacheManager = "caffeineCacheManager", key = "#age", condition = "#age % 2 == 0")
@GetMapping(path = "condition")
public String setByCondition(Integer age) {
return "condition: " + age + "-->" + UUID.randomUUID();
}
当age为偶数时才写缓存,否则不写。
请求奇数5:http://localhost:8080/api/cache/condition?age=5
页面打印返回信息:condition: 5-->1b10c7aa-e7da-4d0a-976c-9a28056ae268
再次请求奇数5:http://localhost:8080/api/cache/condition?age=5
页面打印返回信息:condition: 5-->f8d2c8f7-33e8-42fa-aef3-08d3f97f7592
说明请求奇数时,不写缓存。
请求奇数6:http://localhost:8080/api/cache/condition?age=6
页面打印返回信息:condition: 6-->500aece9-9a6f-4f77-b3ff-78b317c9fbdf
再次请求奇数6:http://localhost:8080/api/cache/condition?age=6
页面打印返回信息:condition: 6-->500aece9-9a6f-4f77-b3ff-78b317c9fbdf
说明请求偶数时,写缓存。
案例三:
java
@Cacheable(value = "unless", cacheManager = "caffeineCacheManager", key = "#age", unless = "#age % 2 == 0")
@GetMapping(path = "unless")
public String setByUnless(Integer age) {
return "unless: " + age + "-->" + UUID.randomUUID();
}
与案例二相反,不满足条件时,才写入缓存。
3. @CachePut
在方法上使用该注解,表示该方法的返回结果将被更新到缓存中。即使缓存中已存在相同的参数,也会执行方法体,并将返回结果更新到缓存中。
java
@CachePut(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "cachePut")
public String cachePut(String name) {
return "hello " + name + "-->" + UUID.randomUUID();
}
测试一下:
第一次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->3fcc2d80-ce32-46bb-8c51-de79cfd7e8fe
第二次请求:http://localhost:8080/api/cache/cachePut?name=高启虎
页面打印返回信息:hello 高启虎-->9e459753-f1e2-4372-a707-bc48b173c28c
第三次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->9e459753-f1e2-4372-a707-bc48b173c28c
第一次请求@Cacheable注解,第二次请求@CachePut注解更新了缓存内容,第三次再次请求@Cacheable注解返回的是第二次请求更新的内容。
4. @CacheEvict
在方法上使用该注解,表示清除缓存中的数据。可以根据指定的条件清除缓存中的数据,例如根据参数、条件等。
beforeInvocation 注解参数设置:
- beforeInvocation=false,缓存的清除是否是在方法之前执行,默认false,即在方法之后清除,当方法执行出现异常时,缓存不会清除。
- beforeInvocation=true,方法之前清除,无论方法执行是否出现异常,缓存都会清除。
java
@CacheEvict(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
@GetMapping(path = "evict")
public String evict(String name) {
return "hello " + name + "-->" + UUID.randomUUID();
}
测试一下:
第一次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->52a64043-d3c4-45c6-9118-679a2e47dcef
第二次请求:http://localhost:8080/api/cache/evict?name=高启虎
页面打印返回信息:hello 高启虎-->3baadce2-1283-4362-8765-c7ce81f3fc25
第三次请求:http://localhost:8080/api/cache/say?name=高启虎
页面打印返回信息:hello 高启虎-->3baadce2-1283-4362-8765-c7ce81f3fc25
第一次请求@Cacheable注解,第二次请求@CacheEvict注解删除了缓存内容,第三次再次请求@Cacheable注解返回的内容也更新了。
5. @Caching
在方法上使用该注解,可以同时应用多个缓存注解,实现复杂的缓存逻辑。
java
@Caching(cacheable = @Cacheable(value = "say", cacheManager = "caffeineCacheManager", key = "'p_' + #name")
, evict = @CacheEvict(value = "condition", cacheManager = "caffeineCacheManager", key = "#age"))
@GetMapping(path = "caching")
public String caching(String name, Integer age) {
return "caching " + name + "-->" + UUID.randomUUID();
}
上面代码就是组合操作:
- @Cacheable:先读取缓存,缓存不存在再执行方法并写入缓存
- @CacheEvict:删除缓存key为
#age
的缓存