A Guide To Caching in Spring – Spring中的缓存指南

最后修改: 2015年 5月 22日

中文/混合/英文(键盘快捷键:t)

1. The Cache Abstraction?

1.缓存抽象?

In this tutorial, we’re going to learn how to use the Caching Abstraction in Spring, and generally improve the performance of our system.

在本教程中,我们将学习如何使用Spring中的缓存抽象,并普遍提高我们系统的性能。

We’ll enable simple caching for some real-world method examples, and we’ll discuss how we can practically improve the performance of these calls through smart cache management.

我们将为一些真实世界的方法实例启用简单的缓存,我们将讨论如何通过智能缓存管理切实提高这些调用的性能。

2. Getting Started

2.开始

The core caching abstraction provided by Spring resides in the spring-context module. So when using Maven, our pom.xml should contain the following dependency:

Spring提供的核心缓存抽象位于spring-context模块中。因此,在使用Maven时,我们的pom.xml应包含以下依赖关系。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.3</version>
</dependency>

Interestingly, there is another module named spring-context-support, which sits on top of the spring-context module and provides a few more CacheManagers backed by the likes of EhCache or Caffeine. If we want to use those as our cache storage, then we need to use the spring-context-support module instead:

有趣的是,还有一个名为spring-context-support的模块,它位于spring-context模块之上,提供了一些更多的CacheManagers,由EhCacheCaffeine等支持。如果我们想使用这些作为我们的缓存存储,那么我们需要使用spring-context-support模块来代替。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.3</version>
</dependency>

Since the spring-context-support module transitively depends on the spring-context module, there is no need for a separate dependency declaration for the spring-context.

由于spring-context-support 模块过渡性地依赖于spring-context 模块,所以不需要为spring-context单独声明依赖关系。

2.1. Spring Boot

2.1.Spring之靴

If we use Spring Boot, then we can utilize the spring-boot-starter-cache starter package to easily add the caching dependencies:

如果我们使用Spring Boot,那么我们可以利用spring-boot-starter-cachestarter包来轻松添加缓存依赖项。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.4.0</version>
</dependency>

Under the hood, the starter brings the spring-context-support module.

在引擎盖下,启动器带来了spring-context-support模块。

3. Enable Caching

3.启用缓存

To enable caching, Spring makes good use of annotations, much like enabling any other configuration level feature in the framework.

为了实现缓存,Spring很好地利用了注解,就像在框架中启用任何其他配置级别的功能。

We can enable the caching feature simply by adding the @EnableCaching annotation to any of the configuration classes:

我们可以通过在任何一个配置类中添加@EnableCaching注解来启用缓存功能。

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("addresses");
    }
}

We can, of course, enable cache management with XML configuration as well:

当然,我们也可以用XML配置启用缓存管理。

<beans>
    <cache:annotation-driven />

    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean 
                  class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
                  name="addresses"/>
            </set>
        </property>
    </bean>
</beans>

Note: After we enable caching, for the minimal setup, we must register a cacheManager.

注意:在我们启用缓存后,对于最小的设置,我们必须注册一个cacheManager

3.1. Using Spring Boot

3.1.使用Spring Boot</b

When using Spring Boot, the mere presence of the starter package on the classpath alongside the EnableCaching annotation would register the same ConcurrentMapCacheManager. So there is no need for a separate bean declaration.

当使用Spring Boot时,仅仅是classpath上的启动包和EnableCachingannotation的存在就可以注册相同的ConcurrentMapCacheManager.,所以不需要单独的bean声明。

Also, we can customize the auto-configured CacheManager using one or more CacheManagerCustomizer<T> beans:

另外,我们可以使用一个或多个CacheManagerCustomizer<T>Bean来定制自动配置的CacheManager

@Component
public class SimpleCacheCustomizer 
  implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

    @Override
    public void customize(ConcurrentMapCacheManager cacheManager) {
        cacheManager.setCacheNames(asList("users", "transactions"));
    }
}

The CacheAutoConfiguration auto-configuration picks up these customizers and applies them to the current CacheManager before its complete initialization.

CacheAutoConfiguration自动配置会拾取这些自定义器,并在当前CacheManager完全初始化之前将其应用于当前CacheManager。

4. Use Caching With Annotations

4.使用注解的缓存</b

Once we’ve enabled caching, the next step is to bind the caching behavior to the methods with declarative annotations.

一旦我们启用了缓存,下一步就是用声明性注解将缓存行为绑定到方法中。

4.1. @Cacheable

4.1.@Cacheable

The simplest way to enable caching behavior for a method is to demarcate it with @Cacheable, and parameterize it with the name of the cache where the results would be stored:

启用方法的缓存行为的最简单方法是用@Cacheable来划定它,并用存储结果的缓存名称对它进行参数化。

@Cacheable("addresses")
public String getAddress(Customer customer) {...}

The getAddress() call will first check the cache addresses before actually invoking the method and then caching the result.

getAddress()调用将首先检查缓存的addresses,然后再实际调用该方法并缓存结果。

While in most cases one cache is enough, the Spring framework also supports multiple caches to be passed as parameters:

虽然在大多数情况下,一个缓存就足够了,但Spring框架也支持将多个缓存作为参数来传递。

@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}

In this case, if any of the caches contain the required result, the result is returned and the method is not invoked.

在这种情况下,如果任何一个缓存包含了所需的结果,那么该结果将被返回,该方法不会被调用。

4.2. @CacheEvict

4.2. @CacheEvict

Now, what would be the problem with making all methods @Cacheable?

现在,让所有的方法@Cacheable会有什么问题?

The problem is size. We don’t want to populate the cache with values that we don’t need often. Caches can grow quite large, quite fast, and we could be holding on to a lot of stale or unused data.

问题在于大小。W我们不想用我们不经常需要的值来填充缓冲区。缓存可以增长得相当快,而且我们可能会保留很多陈旧或未使用的数据。

We can use the @CacheEvict annotation to indicate the removal of one or more/all values so that fresh values can be loaded into the cache again:

我们可以使用@CacheEvict注解来表示删除一个或多个/所有的值,这样新鲜的值就可以再次加载到缓存中。

@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

Here we’re using the additional parameter allEntries in conjunction with the cache to be emptied; this will clear all the entries in the cache addresses and prepare it for new data.

这里我们使用额外的参数allEntries与要清空的缓存一起使用;这将清除缓存中的所有条目addresses,为新的数据做准备。

4.3. @CachePut

4.3 @CachePut

While @CacheEvict reduces the overhead of looking up entries in a large cache by removing stale and unused entries, we want to avoid evicting too much data out of the cache.

虽然@CacheEvict通过删除陈旧和未使用的条目来减少在大型缓存中查找条目的开销,但我们希望避免从缓存中驱逐过多的数据

Instead, we selectively update the entries whenever we alter them.

相反,每当我们改变这些条目时,我们都会有选择地更新这些条目。

With the @CachePut annotation, we can update the content of the cache without interfering with the method execution. That is, the method will always be executed and the result cached:

通过@CachePut注解,我们可以在不干扰方法执行的情况下更新缓存的内容。也就是说,该方法将一直被执行,并将结果缓存起来。

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

The difference between @Cacheable and @CachePut is that @Cacheable will skip running the method, whereas @CachePut will actually run the method and then put its results in the cache.

@Cacheable@CachePut的区别在于,@Cacheable跳过运行方法,而@CachePut实际运行方法,然后将其结果放入缓存。

4.4. @Caching

4.4.@缓存

What if we want to use multiple annotations of the same type for caching a method? Let’s look at an incorrect example:

如果我们想使用同一类型的多个注解来缓存一个方法怎么办?让我们看看一个不正确的例子。

@CacheEvict("addresses")
@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}

The above code would fail to compile since Java does not allow multiple annotations of the same type to be declared for a given method.

上述代码将无法编译,因为Java不允许为一个特定的方法声明多个相同类型的注解。

The workaround to the above issue would be:

上述问题的解决方法是。

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}

As shown in the code snippet above, we can group multiple caching annotations with @Caching, and use it to implement our own customized caching logic.

如上面的代码片段所示,我们可以用@Caching组合多个缓存注解,并使用它来实现我们自己的定制缓存逻辑。

4.5. @CacheConfig

4.5.@CacheConfig

With the @CacheConfig annotation, we can streamline some of the cache configuration into a single place at the class level, so that we don’t have to declare things multiple times:

通过@CacheConfig注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明。

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

    @Cacheable
    public String getAddress(Customer customer) {...}

5. Conditional Caching

5.条件性缓存

Sometimes, caching might not work well for a method in all situations.

有时,缓存可能不会在所有情况下对一个方法都有很好的效果。

Reusing our example from the @CachePut annotation, this will both execute the method as well as cache the results each and every time:

重复我们在@CachePut注解中的例子,这将同时执行该方法,并且每次都会缓存结果。

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

5.1. Condition Parameter

5.1.条件参数

If we want more control over when the annotation is active, we can parameterize @CachePut with a condition parameter that takes a SpEL expression and ensures that the results are cached based on evaluating that expression:

如果我们想更多地控制注解何时被激活,我们可以用一个条件参数对@CachePut进行参数化,该参数接收一个SpEL表达式,并确保根据对该表达式的评估来缓存结果。

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}

5.2. Unless Parameter

5.2.除非参数

We can also control the caching based on the output of the method rather than the input via the unless parameter:

我们还可以通过unless参数控制缓存基于方法的输出而非输入

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

The above annotation would cache addresses unless they were shorter than 64 characters.

上述注释将缓存地址,除非它们短于64个字符。

It’s important to know that the condition and unless parameters can be used in conjunction with all the caching annotations.

重要的是要知道conditionunless参数可以和所有的缓存注释一起使用。

This kind of conditional caching can prove quite effective for managing large results. It’s also useful for customizing behavior based on input parameters instead of enforcing a generic behavior to all operations.

这种条件性缓存对于管理大型结果来说是相当有效的。它对于根据输入参数定制行为也很有用,而不是对所有操作强制执行一个通用行为。

6. Declarative XML-Based Caching

6.基于XML的声明式缓存

If we don’t have access to our application’s source code, or want to inject the caching behavior externally, we can also use declarative XML- based caching.

如果我们不能访问我们应用程序的源代码,或者想从外部注入缓存行为,我们也可以使用基于XML的声明式缓存。

Here is our XML configuration:

这里是我们的XML配置。

<!-- the service that you wish to make cacheable -->
<bean id="customerDataService" 
  class="com.your.app.namespace.service.CustomerDataService"/>

<bean id="cacheManager" 
  class="org.springframework.cache.support.SimpleCacheManager"> 
    <property name="caches"> 
        <set> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="directory"/> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="addresses"/> 
        </set> 
    </property> 
</bean>
<!-- define caching behavior -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
    <cache:caching cache="addresses">
        <cache:cacheable method="getAddress" key="#customer.name"/>
    </cache:caching>
</cache:advice>

<!-- apply the behavior to all the implementations of CustomerDataService interface->
<aop:config>
    <aop:advisor advice-ref="cachingBehavior"
      pointcut="execution(* com.your.app.namespace.service.CustomerDataService.*(..))"/>
</aop:config>

7. Java-Based Caching

7.基于Java的缓存</b

Here is the equivalent Java Configuration:

下面是等效的Java配置。

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
          new ConcurrentMapCache("directory"), 
          new ConcurrentMapCache("addresses")));
        return cacheManager;
    }
}

And here is our CustomerDataService:

这里是我们的CustomerDataService

@Component
public class CustomerDataService {
 
    @Cacheable(value = "addresses", key = "#customer.name")
    public String getAddress(Customer customer) {
        return customer.getAddress();
    }
}

8. Summary

8.总结

In this article, we discussed the basics of Caching in Spring, and how to make good use of that abstraction with annotations.

在这篇文章中,我们讨论了Spring中缓存的基础知识,以及如何通过注解很好地利用这一抽象概念。

The full implementation of this article can be found in the GitHub project.

这篇文章的完整实现可以在GitHub项目中找到。