# 11.1. 缓存

Spring 框架支持透明地向应用程序添加缓存。其核心是，抽象将缓存应用于方法，从而减少基于缓存中可用信息的执行次数。缓存逻辑是透明应用的，不会对调用者造成任何干扰。只要使用`@EnableCaching`注释启用缓存支持，Spring Boot 就会自动配置缓存基础设施。

> 检查Spring 框架参考的 [相关部分以获取更多详细信息。](https://docs.spring.io/spring-framework/reference/6.1/integration/cache.html)

简而言之，要向服务的操作添加缓存，请向其方法添加相关注释，如以下示例所示：

```
@Component
public class MyMathService {
​
    @Cacheable("piDecimals")
    public int computePiDecimal(int precision) {
        ...
    }
​
}
```

此示例演示了如何在可能成本高昂的操作中使用缓存。在调用之前`computePiDecimal`，抽象会在`piDecimals`缓存中查找与参数`i`匹配的条目。如果找到条目，则立即将缓存中的内容返回给调用者，并且不调用该方法。否则，将调用该方法，并在返回值之前更新缓存。

> 您还可以透明地使用标准 JSR-107 (JCache) 注释（例如`@CacheResult`）。但是，我们强烈建议您不要混合搭配 Spring Cache 和 JCache 注解。

如果您不添加任何特定的缓存库，Spring Boot 会自动配置一个使用内存中并发映射的[简单提供程序。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.simple)当需要缓存时（例如`piDecimals`前面的示例），该提供程序会为您创建它。并不真正建议将简单的提供程序用于生产用途，但它非常适合入门并确保您了解其功能。当您决定要使用的缓存提供程序时，请务必阅读其文档以了解如何配置应用程序使用的缓存。几乎所有提供程序都要求您显式配置应用程序中使用的每个缓存。有些提供了一种自定义属性`spring.cache.cache-names`定义的默认缓存的方法。

> 还可以透明地从缓存中 [更新](https://docs.spring.io/spring-framework/reference/6.1/integration/cache/annotations.html#cache-annotations-put)或[逐出数据。](https://docs.spring.io/spring-framework/reference/6.1/integration/cache/annotations.html#cache-annotations-evict)

**11.1.1. 支持的缓存提供程序**

缓存抽象不提供实际的存储，而是依赖于`org.springframework.cache.Cache`和`org.springframework.cache.CacheManager`接口具体化的抽象。

如果您尚未定义类型`CacheManager`或`CacheResolver`命名的 `cacheResolver` bean（请参阅 参考资料[`CachingConfigurer`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/cache/annotation/CachingConfigurer.html)），Spring Boot 会尝试检测以下提供程序（按指定的顺序）：

1. [Generic](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.generic)
2. [JCache (JSR-107)](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.jcache) (EhCache 3, Hazelcast, Infinispan, and others)
3. [Hazelcast](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.hazelcast)
4. [Infinispan](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.infinispan)
5. [Couchbase](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.couchbase)
6. [Redis](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.redis)
7. [Caffeine](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.caffeine)
8. [Cache2k](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.cache2k)
9. [Simple](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.simple)

此外，[Spring Boot for Apache Geode](https://github.com/spring-projects/spring-boot-data-geode)提供了[使用 Apache Geode 作为缓存提供程序的自动配置](https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5/#geode-caching-provider)。

> 如果Spring Boot 自动配置`CacheManager`，则可以通过设置`spring.cache.type`属性来*强制使用*特定的缓存提供程序。如果您需要在某些环境（例如测试）中 [使用无操作缓存，请使用此属性。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.none)
>
> 使用`spring-boot-starter-cache`“Starter”快速添加基本的缓存依赖项。启动器引入了`spring-context-support`。如果手动添加依赖项，则必须添加依赖项`spring-context-support`才能使用 JCache 或 Caffeine 支持。

如果Spring Boot 自动配置`CacheManager`，您可以在完全初始化之前通过公开实现`CacheManagerCustomizer`接口的 bean 来进一步调整其配置。以下示例设置一个标志来表示`null`值不应向下传递到底层映射：

```
@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
​
    @Bean
    public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
        return (cacheManager) -> cacheManager.setAllowNullValues(false);
    }
​
}
```

> 在前面的示例中，需要 自动配置 `ConcurrentMapCacheManager`。如果情况并非如此（您提供了自己的配置或自动配置了不同的缓存提供程序），则根本不会调用定制器。您可以拥有任意数量的定制器，也可以使用`@Order`或`Ordered`来进行排序。

**通用的**

如果上下文定义了*至少*一个`org.springframework.cache.Cache`bean，则使用通用缓存。`CacheManager`创建一个包装该类型的所有 bean。

**JCache (JSR-107)**

[JCache](https://jcp.org/en/jsr/detail?id=107)通过类路径上存在的 `javax.cache.spi.CachingProvider`进行引导（即，类路径上存在符合 JSR-107 的缓存库），并且由`spring-boot-starter-cache` “Starter”提供`JCacheCacheManager`。提供各种兼容的库，而且Spring Boot 为 Ehcache 3、Hazelcast 和 Infinispan 提供依赖管理。也可以添加任何其他兼容的库。

可能会出现多个提供者，在这种情况下必须显式指定该提供者。即使 JSR-107 标准没有强制采用标准化方法来定义配置文件的位置，Spring Boot 也会尽力通过实现细节来设置缓存，如以下示例所示：

```
# Only necessary if more than one provider is present
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
```

> 当缓存库同时提供本机实现和 JSR-107 支持时，Spring Boot 更喜欢 JSR-107 支持，以便在切换到不同的 JSR-107 实现时可以使用相同的功能。
>
> Spring Boot[对 Hazelcast 具有普遍支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.hazelcast)。如果单个`HazelcastInstance`可用，则它也会自动重用`CacheManager`，除非指定了`spring.cache.jcache.config`属性。

底层定制`javax.cache.cacheManager`有两种方式：

* 可以通过设置`spring.cache.cache-names`属性在启动时创建缓存。如果`javax.cache.configuration.Configuration`定义了自定义 bean，则它用于自定义它们。
* `org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer`beans 通过`CacheManager` 的引用来调用以实现完全定制。

> 如果定义了 标准`javax.cache.CacheManager`bean，它会自动包装在`org.springframework.cache.CacheManager`抽象期望的实现中。没有对其应用进一步的定制。

**Hazelcast**

Spring Boot[对 Hazelcast 具有普遍支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.hazelcast)。如果 `HazelcastInstance`已自动配置并且`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 提供程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.jcache)。

**Infinispan**

[Infinispan](https://infinispan.org/)没有默认的配置文件位置，因此必须显式指定。否则，将使用默认引导程序。

```
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 已[配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.nosql.couchbase)，则 `CouchbaseCacheManager`会自动配置。可以通过设置`spring.cache.cache-names`属性在启动时创建额外的缓存，并且可以使用`spring.cache.couchbase.*`属性配置缓存默认值。例如，以下配置创建`cache1`并`cache2`缓存条目*过期时间*为 10 分钟：

```
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
```

如果您需要对配置进行更多控制，请考虑注册`CouchbaseCacheManagerBuilderCustomizer`bean。以下示例显示了为`cache1`和`cache2`配置特定条目到期时间的定制程序：

```
@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)));
​
    }
​
}
```

**Redis**

如果[Redis](https://redis.io/)可用并已配置，则会`RedisCacheManager`自动配置。可以通过设置`spring.cache.cache-names`属性在启动时创建额外的缓存，并且可以使用`spring.cache.redis.*`属性配置缓存默认值。例如，以下配置创建`cache1`并`cache2`缓存*生存时间*为 10 分钟的内容：

```
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
```

> 默认情况下，会添加一个键前缀，这样，如果两个单独的缓存使用相同的键，Redis 不会有重叠的键，也不会返回无效值。如果您创建自己的`RedisCacheManager`.
>
> 您可以通过添加自己的`RedisCacheConfiguration` `@Bean` 配置来完全控制默认配置。如果您需要自定义默认序列化策略，这可能很有用。

如果您需要对配置进行更多控制，请考虑注册`RedisCacheManagerBuilderCustomizer`bean。以下示例显示了配置特定生存时间`cache1`和 `cache2`的定制程序：

```
@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)));
​
    }
​
}
```

**Caffeine**

[Caffeine](https://github.com/ben-manes/caffeine)是 Guava 缓存的 Java 8 重写，取代了对 Guava 的支持。如果存在Caffeine，则会自动配置`CaffeineCacheManager`（由“Starter”提供）。`spring-boot-starter-cache`可以通过设置`spring.cache.cache-names`属性在启动时创建缓存，并且可以通过以下选项之一进行自定义（按指定的顺序）：

1. 缓存规范定义为`spring.cache.caffeine.spec`
2. 定义了一个`com.github.benmanes.caffeine.cache.CaffeineSpec`bean
3. 定义了一个`com.github.benmanes.caffeine.cache.Caffeine`bean

例如，以下配置创建`cache1`和`cache2`缓存的最大大小为 500，*生存时间*为 10 分钟

```
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
```

如果定义了一个`com.github.benmanes.caffeine.cache.CacheLoader` bean，它会自动关联到`CaffeineCacheManager`. 由于`CacheLoader`将会与缓存管理器管理的*所有*缓存相关联，因此它必须定义为`CacheLoader<Object, Object>`。自动配置会忽略任何其他通用类型。

**Cache2k**

[Cache2k](https://cache2k.org/)是内存缓存。如果存在 Cache2k spring 集成，则会自动配置 `SpringCache2kCacheManager`。

可以通过设置`spring.cache.cache-names`属性在启动时创建缓存。可以使用`Cache2kBuilderCustomizer`bean 自定义缓存默认值。以下示例显示了一个自定义程序，它将缓存容量配置为 200 个条目，过期时间为 5 分钟：

```
@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {
​
    @Bean
    public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
        return (builder) -> builder.entryCapacity(200)
                .expireAfterWrite(5, TimeUnit.MINUTES);
    }
​
}
```

**Simple**

如果找不到其他提供程序，则配置使用 `ConcurrentHashMap`作为缓存存储的简单实现。如果您的应用程序中不存在缓存库，则这是默认设置。默认情况下，会根据需要创建缓存，但您可以通过设置属性`cache-names`来限制可用缓存的列表。例如，如果您只需要`cache1`和`cache2`缓存，请`cache-names`按如下方式设置该属性：

```
spring.cache.cache-names=cache1,cache2
```

如果您这样做并且您的应用程序使用未列出的缓存，那么它会在需要缓存时在运行时失败，但在启动时不会失败。如果您使用未声明的缓存，这类似于“真实”缓存提供程序的行为方式。

**None**

当您的配置中存在`@EnableCaching` 时，也需要合适的缓存配置。如果您有自定义`CacheManager`，请考虑将其定义在单独的`@Configuration`类中，以便您可以在必要时覆盖它。None 使用在测试中有用的无操作实现，切片测试默认通过`@AutoConfigureCache`.

如果您需要在特定环境下使用无操作缓存而不是自动配置的缓存管理器，请将缓存类型设置为`none`，如下例所示：

```
spring.cache.type=none
```
