缓存

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 属性定义的默认缓存的方式。

也可以透明地更新逐出缓存中的数据。

支持的缓存提供者

缓存抽象本身不提供实际存储,它依赖于由 CacheCacheManager 接口体现的抽象。

如果你没有定义类型为 CacheManager 或名为 cacheResolverCacheResolver Bean(参见 CachingConfigurer),Spring Boot 会尝试检测以下提供者(按所示顺序)

如果 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,并且可以使用 @OrderOrdered 来排序它们。

通用

如果上下文定义了至少一个 Cache Bean,则使用通用缓存。将创建包装该类型所有 Bean 的 CacheManager

JCache (JSR-107)

通过 classpath 中存在 CachingProvider(即 classpath 中存在兼容 JSR-107 的缓存库)来引导 JCache,并且 JCacheCacheManagerspring-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.typehazelcast 时,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-jakartainfinispan-commons-jakarta 分别替换 infinispan-coreinfinispan-commons

Couchbase

如果 Spring Data Couchbase 可用并且 Couchbase 已配置,则会自动配置一个 CouchbaseCacheManager。可以通过设置 spring.cache.cache-names 属性在启动时创建额外的缓存,并且可以使用 spring.cache.couchbase.* 属性配置缓存默认值。例如,以下配置创建了 cache1cache2 缓存,其条目过期时间为 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,它为 cache1cache2 配置了特定的条目过期时间:

  • 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.* 属性配置缓存默认值。例如,以下配置创建了 cache1cache2 缓存,其存活时间为 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,它为 cache1cache2 配置了特定的存活时间:

  • 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 属性在启动时创建缓存,并且可以通过以下方式之一(按所示顺序)进行自定义:

  1. spring.cache.caffeine.spec 定义的缓存规范

  2. 定义了 CaffeineSpec Bean

  3. 定义了 Caffeine Bean

例如,以下配置创建了 cache1cache2 缓存,最大大小为 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 属性来限制可用缓存列表。例如,如果你只想使用 cache1cache2 缓存,请按如下方式设置 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"