快取

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"