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的缓存
相关推荐
意疏1 小时前
JDK动态代理、Cglib动态代理及Spring AOP
java·开发语言·spring
hjxxlsx1 小时前
什么是Spring Boot 应用开发?
spring boot
计算机学无涯1 小时前
Spring事务回滚
数据库·sql·spring
m0_748247802 小时前
SpringBoot集成Flowable
java·spring boot·后端
散一世繁华,颠半世琉璃2 小时前
SpringBoot揭秘:URL与HTTP方法如何定位到Controller
spring boot·后端·http
B1nna10 小时前
Redis学习(三)缓存
redis·学习·缓存
Q_192849990610 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
陈无左耳、11 小时前
Spring Boot应用开发实战:从入门到精通
spring boot
烟波人长安吖~11 小时前
【目标跟踪+人流计数+人流热图(Web界面)】基于YOLOV11+Vue+SpringBoot+Flask+MySQL
vue.js·pytorch·spring boot·深度学习·yolo·目标跟踪
小天努力学java12 小时前
【面试系列】深入浅出 Spring
java·spring·面试