快取
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"