Spring Cache 使用教程之一

大纲

前言

官方文档

简单介绍

Spring Cache 是 Spring 提供的一个缓存框架,从 Spring 3.1 版本开始支持将缓存添加到现有的 Spring 应用程序中,从 Spring 在 4.1 版本开始,缓存已支持 JSR-107 注释和更多自定义的选项。Spring Cache 利用了 AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解就能实现缓存功能,做到了较小的代码侵入性。由于市面上的缓存工具实在太多,Spring Cache 框架还提供了 CacheManager 接口,可以实现降低对各种缓存框架的耦合;它不是具体的缓存实现,只是提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如 Ehcache、Caffeine、Hazelcast、Couchbase 等。

JSR-107 规范

JSR 是 Java Specification Requests(Java 规范请求)的缩写。JSR-107 是关于如何使用缓存的规范,是 Java 提供的一个接口规范,类似 JDBC 规范,没有具体的实现。Java Caching(JSR-107)定义了 5 个核心接口,分别是 CachingProvider、CacheManager、Cache、Entry 、Expiry。

  • CachingProvider(缓存提供者):用于创建、配置、获取、管理和控制多个 CacheManager。
  • CacheManager(缓存管理器):用于创建、配置、获取、管理和控制多个唯一命名的 Cache,一个 CacheManager 仅对应一个 CachingProvider。
  • Cache(缓存):存在于 CacheManager 的上下文中,是一个类似 Map 的数据结构,并临时存储以 Key 为索引的值。一个 Cache 仅被一个 CacheManager 所拥有,由 CacheManager 管理其生命周期。
  • Entry(缓存键值对):是一个存储在 Cache 中的键值对。
  • Expiry(缓存时效):每一个存储在 Cache 中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后条目将不可以执行访问、更新和删除操作。缓存有效期可以通过 ExpiryPolicy 设置。

Spring Cache 概念

大致原理

在 Spring Cache 官网中,有一个缓存抽象的概念,其核心就是将缓存应用于 Java 方法中,从而减少基于缓存中可用信息的执行次数。换句话来说,就是每次调用目标方法前,Spring Cache 都会先检查该方法是否正对给定参数执行,如果已经执行过,就直接返回缓存的结果。通俗的讲,就是查看缓存里面是否有对应的数据,如果有就返回缓存数据,而无需执行实际方法;如果该方法尚未执行,则执行该方法(缓存中没有对应的数据就执行方法来获取对应的数据),并缓存结果后返回给用户。这样就不用多次去执行数据库操作,减少 CPU 和 IO 的消耗。

使用 Spring 缓存抽象时,应该需要关注以下两点

  • 1、确定方法需要被缓存以及它们的缓存策略
  • 2、从缓存中读取之前缓存存储的数据

核心接口

  • org.springframework.cache.Cache:为缓存组件定义规范,包含缓存的各种操作集合。在 Cache 接口下,Spring 提供了各种 xxxCache 的实现,如 RedisCache、EhCacheCache、ConcurrentMapCache 等。
  • org.springframework.cache.CacheManager:缓存管理器,管理各种缓存(Cache)组件,如 RedisCacheManager,使用 Redis 作为缓存。

核心注解

注解说明
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其执行结果进行缓存,相当于缓存使用的是读模式
@CacheEvict 将一条或多条数据从缓存中删除,相当于缓存使用的是写模式中的失效模式
@CachePut 保证方法被调用,又希望结果被缓存,相当于缓存使用的是写模式中的双写模式
@EnableCaching 开启基于注解的缓存功能
@CacheConfig 标注在类上,用于抽取 Spring Cache 相关注解的公共配置,可抽取的公共配置包括缓存名称、主键生成器、缓存管理器
@Caching 用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解
keyGenerator 缓存数据时 Key 的生成策略
serialize 缓存数据时 Value 的序列化策略

在上面常用的三个注解 @Cacheable@CachePutCacheEvict 中,主要有以下的参数,可用于对要缓存的数据进行过滤和配置。

提示

  • 1、@Cacheable 标注在方法上,表示方法的结果需要被缓存起来,缓存的键由 keyGenerator 的策略决定,缓存的值的形式则由 serialize 序列化策略决定(JDK 还是 Json 序列化机制);标注上该注解之后,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果。
  • 2、@CachePut 也标注在方法上,它和 @Cacheable 相似也会将方法的返回值缓存起来,不同的是标注 @CachePut 的方法每次都会被调用,而且每次都会将结果缓存起来,适用于对象的更新。

Spring Cache 整合

下面将介绍 Spring Boot 项目如何整合 Spring Cache,并使用 Redis 存储缓存数据。

引入依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

配置信息

application.yml 配置文件里,指定 Spring Cache 使用 Redis 存储缓存数据,并配置 Redis 的连接信息。

1
2
3
4
5
6
7
8
9
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 0
timeout: 5000
cache:
type: redis

启用缓存

在应用的主启动类上添加 @EnableCaching 注解,启用 Spring Cache 的缓存功能。

1
2
3
4
5
@EnableCaching
@SpringBootApplication
public class ProductApplication {

}

配置序列化

默认情况下,Spring Cache 会使用 JDK 的序列化机制将缓存数据写入 Redis,这样存储在 Redis 里面的就是二进制数据。若希望 Spring Cache 将数据序列化成 JSON 数据再写入 Redis,可以使用以下的配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class SpringCacheConfig extends CachingConfigurerSupport {

/**
* Spring Cache 的 Redis 配置
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
// 默认配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

// 设置随机的有效时间,若不设置,默认是永久有效
// Random random = new Random();
// config = config.entryTtl(Duration.ofHours(random.nextInt(24)));

// Key的序列化机制
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));

// Value的序列化机制
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

// 加载配置文件的内容
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}

}

添加缓存注解

在需要使用缓存的方法上添加 @Cacheable 注解,其中的 value 参数代表缓存的名称,必须指定至少一个。标注上该注解之后,会将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。

1
2
3
4
5
6
7
8
9
10
11
12
@Service("categoryService")
public class CategoryServiceImpl implements CategoryService {

@Override
@Cacheable(value = "categoryTree")
public List<CategoryEntity> listWithTree() {
// TODO 查询数据库
System.out.println("查询数据库");
return Collections.emptyList();
}

}

至此,Spring Boot 项目整合 Spring Cache 的步骤就完成了。当重复调用标记了 @Cacheable 注解的方法时,可以发现该方法只会被调用一次,此时说明 Redis 的缓存生效了。

参考博客