32、缓存
Spring Framework 支持以透明的方式向应用程序添加缓存。从本质上讲,将缓存应用于方法上,根据缓存数据减少方法的执行次数。缓存逻辑是透明的,不会对调用者造成任何干扰。通过 @EnableCaching
注解启用缓存支持,Spring Boot 就会自动配置缓存设置。
注意
有关更多详细信息,请查看 Spring Framework 参考文档的相关部分。
简而言之,为服务添加缓存的操作就像在其方法中添加注解一样简单,如下所示:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class MathService {
@Cacheable("piDecimals")
public int computePiDecimal(int i) {
// ...
}
}
此示例展示了如何在代价可能高昂的操作上使用缓存。在调用 computePiDecimal
之前,缓存支持会在 piDecimals
缓存中查找与 i
参数匹配的项。如果找到,则缓存中的内容会立即返回给调用者,并不会调用该方法。否则,将调用该方法,并在返回值之前更新缓存。
注意
您还可以使用标准 JSR-107(JCache)注解(例如
@CacheResult
)。但是,我们强烈建议您不要将 Spring Cache 和 JCache 注解混合使用。
如果您不添加任何指定的缓存库,Spring Boot 会自动配置一个使用并发 map 的 simple provider。当需要缓存时(例如前面示例中的 piDecimals
),该 simple provider 会为您创建缓存。不推荐将 simple provider 用于生产环境,但它非常适合入门并帮助您了解这些功能。当您决定使用缓存提供者时,请务必阅读其文档以了解如何配置应用程序。几乎所有提供者都要求您显式配置应用程序中使用的每个缓存。有些提供了自定义 spring.cache.cache-names
属性以定义默认缓存。
提示
32.1、支持的缓存提供者
缓存抽象不提供存储实现,其依赖于 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口实现的抽象。
如果您未定义 CacheManager
类型的 bean 或名为 cacheResolver
的 CacheResolver
(请参阅 CachingConfigurer
),则 Spring Boot 会尝试检测以下提供者(按序号顺序):
- Generic
- JCache (JSR-107)(EhCache 3、Hazelcast、Infinispan 或其他)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis8 Caffeine
- Simple
提示
也可以通过设置
spring.cache.type
属性来强制指定缓存提供者。如果您需要在某些环境(比如测试)中完全禁用缓存,请使用此属性。
提示
使用
spring-boot-starter-cache
starter 快速添加基本的缓存依赖。starter 引入了spring-context-support
。如果手动添加依赖,则必须包含spring-context-support
才能使用 JCache、EhCache 2.x 或 Guava 支持。
如果通过 Spring Boot 自动配置 CacheManager
,则可以通过暴露一个实现了 CacheManagerCustomizer
接口的 bean,在完全初始化之前进一步调整其配置。以下示例设置了一个 flag,表示应将 null 值传递给底层 map:
@Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
return new CacheManagerCustomizer<ConcurrentMapCacheManager>() {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setAllowNullValues(false);
}
};
}
注意
在前面示例中,需要一个自动配置的
ConcurrentMapCacheManager
。如果不是这种情况(您提供了自己的配置或自动配置了不同的缓存提供者),则根本不会调用 customizer。您可以拥有多个 customizer,也可以使用@Order
或Ordered
来排序它们。
32.1.1、Generic
如果上下文定义了至少一个 org.springframework.cache.Cache
bean,则使用 Generic 缓存。将创建一个包装所有该类型 bean 的 CacheManager
。
32.1.2、JCache (JSR-107)
JCache 通过 classpath 上的 javax.cache.spi.CachingProvider
(即 classpath 上存在符合 JSR-107 的缓存库)来引导,jCacheCacheManager
由 spring-boot-starter-cache
starter 提供。您可以使用各种兼容库,Spring Boot 为 Ehcache 3、Hazelcast 和 Infinispan 提供依赖管理。您还可以添加任何其他兼容库。
可能存在多个提供者,在这种情况下必须明确指定提供者。即使 JSR-107 标准没有强制规定一个定义配置文件位置的标准化方法,Spring Boot 也会尽其所能设置一个包含实现细节的缓存,如下所示:
# 存在多个 provider 才需要这样做
spring.cache.jcache.provider=com.acme.MyCachingProvider
spring.cache.jcache.config=classpath:acme.xml
注意
当缓存库同时提供原生实现和 JSR-107 支持时,Spring Boot 更倾向 JSR-107 支持,因此当您切换到不同的 JSR-107 实现时,还可以使用相同的功能。
提示
Spring Boot 对 Hazelcast 的支持一般。如果有一个
HazelcastInstance
可用,它也会自动为CacheManager
复用,除非指定了spring.cache.jcache.config
属性。
有两种方法可以自定义底层的 javax.cache.cacheManager
:
- 可以通过设置
spring.cache.cache-names
属性在启动时创建缓存。如果定义了自定义javax.cache.configuration.Configuration
bean,则会使用它来自定义。 - 使用
CacheManager
的引用调用org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer
bean 以进行完全自定义。
提示
如果定义了一个标准的
javax.cache.CacheManager
bean,它将自动包装进一个抽象所需的org.springframework.cache.CacheManager
实现中,而不会应用自定义配置。
32.1.3、EhCache 2.x
如果可以在 classpath 的根目录中找到名为 ehcache.xml
的文件,则使用 EhCache 2.x。如果找到 EhCache 2.x,则使用 spring-boot-starter-cache
starter 提供的 EhCacheCacheManager
来启动缓存管理器。还可以提供其他配置文件,如下所示:
spring.cache.ehcache.config=classpath:config/another-config.xml
32.1.4、Hazelcast
Spring Boot 对 Hazelcast 的支持一般。如果自动配置了一个 HazelcastInstance
,它将自动包装进 CacheManager
中。
32.1.5、Infinispan
Infinispan 没有默认的配置文件位置,因此必须明确指定。否则将使用默认配置引导。
spring.cache.infinispan.config=infinispan.xml
可以通过设置 spring.cache.cache-names
属性在启动时创建缓存。如果定义了自定义 ConfigurationBuilder
bean,则它将用于自定义缓存。
注意
Infinispan 在 Spring Boot 中的支持仅限于内嵌模式,非常简单。如果你想要更多选项,你应该使用官方的 Infinispan Spring Boot starter。有关更多详细信息,请参阅 Infinispan 文档。
32.1.6、Couchbase
如果 Couchbase Java 客户端和 couchbase-spring-cache
实现可用且已经配置了 Couchbase,则应用程序会自动配置一个 CouchbaseCacheManager
。也可以通过设置 spring.cache.cache-names
属性在启动时创建其他缓存。这些缓存在自动配置的 Bucket 上操作。您还可以使用 customizer 在其他 Bucket
上创建缓存。假设您需要在 main Bucket
上有两个缓存(cache1
和 cache2
),并且 another Bucket
有一个过期时间为 2 秒的缓存(cache3
)。您可以通过配置创建这两个缓存,如下所示:
spring.cache.cache-names=cache1,cache2
然后,您可以定义一个 @Configuration
类来配置其他 Bucket
和 cache3
缓存,如下所示:
@Configuration
public class CouchbaseCacheConfiguration {
private final Cluster cluster;
public CouchbaseCacheConfiguration(Cluster cluster) {
this.cluster = cluster;
}
@Bean
public Bucket anotherBucket() {
return this.cluster.openBucket("another", "secret");
}
@Bean
public CacheManagerCustomizer<CouchbaseCacheManager> cacheManagerCustomizer() {
return c -> {
c.prepareCache("cache3", CacheBuilder.newInstance(anotherBucket())
.withExpiration(2));
};
}
}
此示例配置复用了通过自动配置创建的 Cluster
。
32.1.7、Redis
如果 Redis 可用并已经配置,则应用程序会自动配置一个 RedisCacheManager
。通过设置 spring.cache.cache-names
属性可以在启动时创建其他缓存,并且可以使用 spring.cache.redis.*
属性配置缓存默认值。例如,以下配置创建 cache1
和 cache2
缓存,他们的有效时间为 10 分钟:
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=600000
注意
默认情况下,会添加一个 key 前缀,这样做是因为如果两个单独的缓存使用了相同的键,Redis 不支持重叠 key,而缓存也不能返回无效值。如果您创建自己的
RedisCacheManager
,我们强烈建议您启用此设置。
提示
您可以通过添加自己的
RedisCacheConfiguration
@Bean
来完全控制配置。如果您想自定义序列化策略,这种方式可能很有用。
32.1.8、Caffeine
Caffeine 是一个使用了 Java 8 重写 Guava 缓存,用于取代 Guava 支持的缓存库。如果 Caffeine 存在,则应用程序会自动配置一个 CaffeineCacheManager
(由 spring-boot-starter-cache
starter 提供)。可以通过设置 spring.cache.cache-names
属性在启动时创建缓存,并且可以通过以下方式之一(按序号顺序)自定义缓存:
- 一个由
spring.cache.caffeine.spec
定义的缓存规范 - 一个已定义的
com.github.benmanes.caffeine.cache.CaffeineSpec
bean - 一个已定义的
com.github.benmanes.caffeine.cache.Caffeine
bean
例如,以下配置创建 cache1
和 cache2
缓存,最大大小为 500,有效时间 为 10 分钟:
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
如果定义了 com.github.benmanes.caffeine.cache.CacheLoader
bean,它将自动与 CaffeineCacheManager
关联。由于 CacheLoader
将与缓存管理器管理的所有缓存相关联,因此必须将其定义为 CacheLoader<Object, Object>
。自动配置会忽略所有其他泛型类型。
32.1.9、Simple
如果找不到其他提供者,则配置使用一个 ConcurrentHashMap
作为缓存存储的简单实现。如果您的应用程序中没有缓存库,则该项为默认值。默认情况下,会根据需要创建缓存,但您可以通过设置 cache-names
属性来限制可用缓存的列表。例如,如果只需要 cache1
和 cache2
缓存,请按如下设置 cache-names
属性:
spring.cache.cache-names=cache1,cache2
如果这样做了,并且您的应用程序使用了未列出的缓存,则运行时在它需要缓存时会触发失败,但在启动时则不会。这类似于真实缓存提供者在使用未声明的缓存时触发的行为方式。
32.1.10、None
当配置中存在 @EnableCaching
时,也需要合适的缓存配置。如果需要在某些环境中完全禁用缓存,请将缓存类型强制设置为 none
以使用 no-op 实现,如下所示:
spring.cache.type=none