缓存

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 的 bean,或没有名为 cacheResolverCacheResolver(见 CachingConfigurer),Spring Boot 会按如下顺序尝试检测以下提供者:

  1. 通用

  2. JCache (JSR-107)(EhCache 3、Hazelcast、Infinispan 等)

  3. Hazelcast

  4. Infinispan

  5. Couchbase

  6. Redis

  7. Caffeine

  8. Cache2k

  9. 简单

此外,https://github.com/spring-projects/spring-boot-data-geode[Spring Boot for Apache Geode] 提供了 使用 Apache Geode 作为缓存提供者的自动配置

如果 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。如果不是(如你自定义了配置或自动配置了其他缓存提供者),则不会调用该定制器。你可以定义任意多个定制器,也可通过 @OrderOrdered 排序。

通用

如果上下文中定义了 至少 一个 Cache bean,则会创建一个包装所有该类型 bean 的 CacheManager

JCache (JSR-107)

JCache 通过 classpath 上存在 CachingProvider(即存在 JSR-107 兼容缓存库)引导,spring-boot-starter-cache starter 提供 JCacheCacheManager。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
# 仅当存在多个提供者时才需要
spring:
  cache:
    jcache:
      provider: "com.example.MyCachingProvider"
      config: "classpath:example.xml"
当缓存库同时提供原生实现和 JSR-107 支持时,Spring Boot 优先使用 JSR-107 支持,这样切换到其他 JSR-107 实现时也能获得相同特性。
Spring Boot 对 Hazelcast 有 通用支持。如果有单个 HazelcastInstance,也会自动用于 CacheManager,除非指定了 spring.cache.jcache.config 属性。

自定义底层 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-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。如下示例展示了为 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"
默认会添加 key 前缀,确保两个不同缓存使用相同 key 时,Redis 不会出现键冲突导致返回无效值。强烈建议自定义 RedisCacheManager 时保持此设置开启。
通过自定义 RedisCacheConfiguration @Bean,可完全控制默认配置。这对于自定义序列化策略很有用。

如需更细致的配置,可注册 RedisCacheManagerBuilderCustomizer bean。如下示例展示了为 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 自定义缓存默认值。如下示例展示了为缓存配置容量为 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

  • Properties

  • YAML

spring.cache.cache-names=cache1,cache2
spring:
  cache:
    cache-names: "cache1,cache2"

如果这样设置,应用使用未声明的缓存时会在运行时失败(而非启动时),这与“真实”缓存提供者的行为一致。

当配置中存在 @EnableCaching 时,也应有合适的缓存配置。如果有自定义 org.springframework.cache.CacheManager,建议在单独的 @Configuration 类中定义,以便必要时覆盖。None 使用 no-op 实现,适用于测试,切片测试默认通过 @AutoConfigureCache 使用该实现。

如需在某些环境下使用 no-op 缓存而非自动配置的缓存管理器,可将缓存类型设为 none,如下所示:

  • Properties

  • YAML

spring.cache.type=none
spring:
  cache:
    type: "none"