Spring Boot 整合 Caffeine 本地缓存及 Spring Cache 注解的使用

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:是否开启统计信息记录,用于监控缓存的命中率、加载时间等统计信息。

加上@EnableCachingSpring 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 注解参数设置:

  1. beforeInvocation=false,缓存的清除是否是在方法之前执行,默认false,即在方法之后清除,当方法执行出现异常时,缓存不会清除。
  2. 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的缓存
相关推荐
后端小张2 小时前
基于飞算AI的图书管理系统设计与实现
spring boot
金銀銅鐵6 小时前
Spring 中的 initializeBean 方法的内部逻辑小总结
spring
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
阿杆1 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C2 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*2 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*2 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu2 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
-Xie-2 天前
Mysql杂志(十六)——缓存池
数据库·mysql·缓存
七夜zippoe2 天前
缓存与数据库一致性实战手册:从故障修复到架构演进
数据库·缓存·架构