Spring Boot 整合 Caffeine 本地缓存及 Spring Cache 注解的使用
介绍
在现代的Web应用程序中,缓存是提高性能和响应速度的重要手段之一。Spring Boot提供了对缓存的良好支持,并且可以轻松地整合Caffeine本地缓存作为缓存提供者。结合Spring Cache注解,可以实现对方法级别的缓存,从而提高系统的性能和响应速度。
添加依赖
首先,在pom.xml文件中添加Spring Boot和Caffeine、Spring Cache缓存相关的依赖:
<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,并指定缓存的配置信息:
@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文件中进行配置,如下:
spring:
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=100,maximumSize=200,expireAfterWrite=5m
 
如果使用这种方式,就可以忽略前面的caffeineCacheManager的自定义bean配置信息,建议使用自定义的bean配置信息。
SpringCache的注解介绍和使用
1. @EnableCaching
在配置类上使用该注解,启用Spring的缓存支持。
前面 CacheManagerConfig 类就使用了该注解开启Spring缓存的支持。
2. @Cacheable
在方法上使用该注解,表示该方法的返回结果将被缓存。当再次调用该方法时,如果缓存中已存在相同的参数,则直接返回缓存中的结果,而不执行方法体。
注解核心设置:
 @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:用于指定是否使用同步模式进行缓存。
 
简单使用案例:
案例一:
@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
说明缓存生效了。
案例二:
@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
说明请求偶数时,写缓存。
案例三:
@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
在方法上使用该注解,表示该方法的返回结果将被更新到缓存中。即使缓存中已存在相同的参数,也会执行方法体,并将返回结果更新到缓存中。
@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,方法之前清除,无论方法执行是否出现异常,缓存都会清除。
 
@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
在方法上使用该注解,可以同时应用多个缓存注解,实现复杂的缓存逻辑。
@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的缓存 










