缓存
Spring Framework 支持透明地向应用添加缓存。其核心是,该抽象将缓存应用于方法,从而根据缓存中可用的信息减少执行次数。缓存逻辑被透明地应用,对调用者没有任何干扰。只要使用 @EnableCaching
注解启用缓存支持,Spring Boot 就会自动配置缓存基础设施。
有关更多详细信息,请查阅 Spring Framework 参考文档的相关章节。 |
简而言之,要为服务的某个操作添加缓存,只需在其方法上添加相关的注解,如下例所示:
-
Java
-
Kotlin
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class MyMathService {
@Cacheable("piDecimals")
public int computePiDecimal(int precision) {
...
}
}
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Component
@Component
class MyMathService {
@Cacheable("piDecimals")
fun computePiDecimal(precision: Int): Int {
...
}
}
此示例演示了在潜在开销较大的操作上使用缓存。在调用 computePiDecimal
之前,该抽象会在 piDecimals
缓存中查找与 precision
参数匹配的条目。如果找到条目,缓存中的内容会立即返回给调用者,并且不会调用该方法。否则,将调用该方法,并在返回结果之前更新缓存。
你也可以透明地使用标准的 JSR-107 (JCache) 注解(例如 @CacheResult )。但是,我们强烈建议你不要混用 Spring Cache 和 JCache 注解。 |
如果你不添加任何特定的缓存库,Spring Boot 会自动配置一个简单的提供者,它使用内存中的并发 Map。当需要缓存时(例如上例中的 piDecimals
),此提供者会为你创建它。简单提供者不建议用于生产环境,但非常适合入门并确保你了解这些功能。当你决定使用哪个缓存提供者时,请务必阅读其文档,了解如何配置你的应用使用的缓存。几乎所有提供者都要求你明确配置应用中使用的每个缓存。有些提供了自定义由 spring.cache.cache-names
属性定义的默认缓存的方式。
支持的缓存提供者
缓存抽象本身不提供实际存储,它依赖于由 Cache
和 CacheManager
接口体现的抽象。
如果你没有定义类型为 CacheManager
或名为 cacheResolver
的 CacheResolver
Bean(参见 CachingConfigurer
),Spring Boot 会尝试检测以下提供者(按所示顺序)
-
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan 等)
如果 CacheManager 由 Spring Boot 自动配置,可以通过设置 spring.cache.type 属性来强制使用特定的缓存提供者。如果你需要在某些环境(例如测试)中使用 no-op 缓存,请使用此属性。 |
使用 spring-boot-starter-cache starter 可以快速添加基本的缓存依赖。该 starter 会引入 spring-context-support 。如果你手动添加依赖,则必须包含 spring-context-support 才能使用 JCache 或 Caffeine 支持。 |
如果 CacheManager
由 Spring Boot 自动配置,你可以通过暴露一个实现了 CacheManagerCustomizer
接口的 Bean,在其完全初始化之前进一步调整其配置。以下示例设置了一个标志,表示 null
值不应该传递到底层 Map:
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
@Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
return (cacheManager) -> cacheManager.setAllowNullValues(false);
}
}
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyCacheManagerConfiguration {
@Bean
fun cacheManagerCustomizer(): CacheManagerCustomizer<ConcurrentMapCacheManager> {
return CacheManagerCustomizer { cacheManager ->
cacheManager.isAllowNullValues = false
}
}
}
在上例中,期望自动配置一个 ConcurrentMapCacheManager 。如果情况并非如此(要么你自己提供了配置,要么自动配置了不同的缓存提供者),则完全不会调用 customizer。你可以拥有任意数量的 customizer,并且可以使用 @Order 或 Ordered 来排序它们。 |
通用
如果上下文定义了至少一个 Cache
Bean,则使用通用缓存。将创建包装该类型所有 Bean 的 CacheManager
。
JCache (JSR-107)
通过 classpath 中存在 CachingProvider
(即 classpath 中存在兼容 JSR-107 的缓存库)来引导 JCache,并且 JCacheCacheManager
由 spring-boot-starter-cache
starter 提供。有各种兼容的库可用,Spring Boot 为 Ehcache 3、Hazelcast 和 Infinispan 提供了依赖管理。也可以添加任何其他兼容的库。
可能存在多个提供者,在这种情况下必须明确指定提供者。即使 JSR-107 标准没有强制规定配置文件的标准化位置,Spring Boot 也会尽力容纳包含实现细节的缓存设置,如下例所示:
-
Properties 文件
-
YAML 文件
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
# Only necessary if more than one provider is present
spring:
cache:
jcache:
provider: "com.example.MyCachingProvider"
config: "classpath:example.xml"
当缓存库同时提供原生实现和 JSR-107 支持时,Spring Boot 会优先选择 JSR-107 支持,以便在你切换到不同的 JSR-107 实现时仍可使用相同的功能。 |
Spring Boot 对 Hazelcast 提供通用支持。如果存在单个 HazelcastInstance ,除非指定了 spring.cache.jcache.config 属性,否则它也会自动复用于 CacheManager 。 |
有两种方式可以自定义底层的 CacheManager
-
可以通过设置
spring.cache.cache-names
属性在启动时创建缓存。如果定义了自定义的Configuration
Bean,则用于自定义它们。 -
JCacheManagerCustomizer
Bean 会在完全自定义时被调用,并传入CacheManager
的引用。
如果定义了标准的 CacheManager Bean,它会自动被包装在抽象期望的 CacheManager 实现中。不会对其进行进一步的自定义。 |
Hazelcast
Spring Boot 对 Hazelcast 提供通用支持。如果 HazelcastInstance
已自动配置且 classpath 中存在 com.hazelcast:hazelcast-spring
,则它会自动包装在 CacheManager
中。
Hazelcast 可以用作兼容 JCache 的缓存或兼容 Spring CacheManager 的缓存。当设置 spring.cache.type 为 hazelcast 时,Spring Boot 将使用基于 CacheManager 的实现。如果你想将 Hazelcast 用作兼容 JCache 的缓存,请将 spring.cache.type 设置为 jcache 。如果你有多个兼容 JCache 的缓存提供者并希望强制使用 Hazelcast,你必须明确设置 JCache 提供者。 |
Infinispan
Infinispan 没有默认的配置文件位置,因此必须明确指定。否则,将使用默认引导程序。
-
Properties 文件
-
YAML 文件
spring.cache.infinispan.config=infinispan.xml
spring:
cache:
infinispan:
config: "infinispan.xml"
可以通过设置 spring.cache.cache-names
属性在启动时创建缓存。如果定义了自定义的 ConfigurationBuilder
Bean,则用于自定义缓存。
为了兼容 Spring Boot 的 Jakarta EE 9 基线,必须使用 Infinispan 的 -jakarta
模块。对于每个带有 -jakarta
变体的模块,都必须使用该变体替换标准模块。例如,必须使用 infinispan-core-jakarta
和 infinispan-commons-jakarta
分别替换 infinispan-core
和 infinispan-commons
。
Couchbase
如果 Spring Data Couchbase 可用并且 Couchbase 已配置,则会自动配置一个 CouchbaseCacheManager
。可以通过设置 spring.cache.cache-names
属性在启动时创建额外的缓存,并且可以使用 spring.cache.couchbase.*
属性配置缓存默认值。例如,以下配置创建了 cache1
和 cache2
缓存,其条目过期时间为 10 分钟:
-
Properties 文件
-
YAML 文件
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
spring:
cache:
cache-names: "cache1,cache2"
couchbase:
expiration: "10m"
如果你需要对配置有更多控制,请考虑注册一个 CouchbaseCacheManagerBuilderCustomizer
Bean。以下示例展示了一个 customizer,它为 cache1
和 cache2
配置了特定的条目过期时间:
-
Java
-
Kotlin
import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;
@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {
@Bean
public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("cache1", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))
.withCacheConfiguration("cache2", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));
}
}
import org.springframework.boot.autoconfigure.cache.CouchbaseCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration
import java.time.Duration
@Configuration(proxyBeanMethods = false)
class MyCouchbaseCacheManagerConfiguration {
@Bean
fun myCouchbaseCacheManagerBuilderCustomizer(): CouchbaseCacheManagerBuilderCustomizer {
return CouchbaseCacheManagerBuilderCustomizer { builder ->
builder
.withCacheConfiguration(
"cache1", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofSeconds(10))
)
.withCacheConfiguration(
"cache2", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofMinutes(1))
)
}
}
}
Redis
如果Redis 可用且已配置,则会自动配置一个 RedisCacheManager
。可以通过设置 spring.cache.cache-names
属性在启动时创建额外的缓存,并且可以使用 spring.cache.redis.*
属性配置缓存默认值。例如,以下配置创建了 cache1
和 cache2
缓存,其存活时间为 10 分钟:
-
Properties 文件
-
YAML 文件
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
spring:
cache:
cache-names: "cache1,cache2"
redis:
time-to-live: "10m"
默认情况下,会添加一个键前缀,这样如果两个独立的缓存使用相同的键,Redis 就不会有重叠的键,也不会返回无效值。如果你创建自己的 RedisCacheManager ,强烈建议保持此设置启用。 |
通过添加自己的 RedisCacheConfiguration @Bean ,你可以完全控制默认配置。如果你需要自定义默认的序列化策略,这会很有用。 |
如果你需要对配置有更多控制,请考虑注册一个 RedisCacheManagerBuilderCustomizer
Bean。以下示例展示了一个 customizer,它为 cache1
和 cache2
配置了特定的存活时间:
-
Java
-
Kotlin
import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {
@Bean
public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("cache1", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))
.withCacheConfiguration("cache2", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));
}
}
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import java.time.Duration
@Configuration(proxyBeanMethods = false)
class MyRedisCacheManagerConfiguration {
@Bean
fun myRedisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer {
return RedisCacheManagerBuilderCustomizer { builder ->
builder
.withCacheConfiguration(
"cache1", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofSeconds(10))
)
.withCacheConfiguration(
"cache2", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofMinutes(1))
)
}
}
}
Caffeine
Caffeine 是 Guava 缓存的 Java 8 重写,它取代了对 Guava 的支持。如果存在 Caffeine,则会自动配置一个 CaffeineCacheManager
(由 spring-boot-starter-cache
starter 提供)。可以通过设置 spring.cache.cache-names
属性在启动时创建缓存,并且可以通过以下方式之一(按所示顺序)进行自定义:
-
由
spring.cache.caffeine.spec
定义的缓存规范 -
定义了
CaffeineSpec
Bean -
定义了
Caffeine
Bean
例如,以下配置创建了 cache1
和 cache2
缓存,最大大小为 500,存活时间为 10 分钟:
-
Properties 文件
-
YAML 文件
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
spring:
cache:
cache-names: "cache1,cache2"
caffeine:
spec: "maximumSize=500,expireAfterAccess=600s"
如果定义了 CacheLoader
Bean,它会自动关联到 CaffeineCacheManager
。由于 CacheLoader
将与缓存管理器管理的所有缓存关联,因此必须将其定义为 CacheLoader<Object, Object>
。自动配置会忽略任何其他泛型类型。
Cache2k
Cache2k 是一个内存缓存。如果存在 Cache2k spring 集成,则会自动配置一个 SpringCache2kCacheManager
。
可以通过设置 spring.cache.cache-names
属性在启动时创建缓存。可以使用 Cache2kBuilderCustomizer
Bean 自定义缓存默认值。以下示例展示了一个 customizer,它配置了缓存的容量为 200 个条目,过期时间为 5 分钟:
-
Java
-
Kotlin
import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {
@Bean
public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
return (builder) -> builder.entryCapacity(200)
.expireAfterWrite(5, TimeUnit.MINUTES);
}
}
import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit
@Configuration(proxyBeanMethods = false)
class MyCache2kDefaultsConfiguration {
@Bean
fun myCache2kDefaultsCustomizer(): Cache2kBuilderCustomizer {
return Cache2kBuilderCustomizer { builder ->
builder.entryCapacity(200)
.expireAfterWrite(5, TimeUnit.MINUTES)
}
}
}
简单
如果找不到其他提供者,则会配置一个使用 ConcurrentHashMap
作为缓存存储的简单实现。如果你的应用中没有缓存库,这是默认设置。默认情况下,缓存会按需创建,但你可以通过设置 cache-names
属性来限制可用缓存列表。例如,如果你只想使用 cache1
和 cache2
缓存,请按如下方式设置 cache-names
属性:
-
Properties 文件
-
YAML 文件
spring.cache.cache-names=cache1,cache2
spring:
cache:
cache-names: "cache1,cache2"
如果这样做并且你的应用使用了未列出的缓存,那么它会在需要缓存时(而不是在启动时)运行时失败。这类似于“真正的”缓存提供者在你使用未声明的缓存时的行为。
无 (None)
当 @EnableCaching
存在在你的配置中时,也期望存在合适的缓存配置。如果你有自定义的 org.springframework.cache.CacheManager
,考虑将其定义在一个单独的 @Configuration
类中,以便在需要时可以覆盖它。None
使用的是一个 no-op 实现,在测试中很有用,并且 slice tests 通过 @AutoConfigureCache
默认使用它。
如果你需要在某个环境中而不是自动配置的缓存管理器中使用 no-op 缓存,请将缓存类型设置为 none
,如下例所示:
-
Properties 文件
-
YAML 文件
spring.cache.type=none
spring:
cache:
type: "none"