Spring中的缓存指南

评论 0 浏览 0 2015-05-22

1.缓存抽象

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

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

2.入门

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

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

有趣的是,还有一个名为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>

由于 spring-context-support 模块可传递地依赖于 spring-context 模块,因此不需要为 spring-context 单独声明依赖关系。

2.1. Spring Boot

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

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

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

3.启用缓存

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

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

@Configuration
@EnableCaching
public class CachingConfig {

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

当然,我们也可以用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>

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

3.1.使用Spring Boot

当使用Spring Boot时,仅仅在classpath上出现启动包和EnableCaching 注解就可以注册相同的ConcurrentMapCacheManager.,所以不需要单独的Bean声明。

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

@Component
public class SimpleCacheCustomizer 
  implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

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

CacheAutoConfiguration 自动配置选择这些自定义器,并在其完全初始化之前将其应用于当前的CacheManager

4.使用注解的缓存

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

4.1. @Cacheable

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

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

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

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

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

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

4.2. @CacheEvict

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

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

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

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

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

4.3. @CachePut

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

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

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

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

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

4.4. @Caching

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

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

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

上述问题的解决方法是:

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

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

4.5. @CacheConfig

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

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

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

5.条件性缓存

有时,缓存可能并不是在所有情况下对一个方法都能很好地工作。

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

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

5.1.条件参数

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

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

5.2. Unless参数

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

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

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

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

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

6.基于XML的声明式缓存

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

这里是我们的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的缓存

下面是等效的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;
    }
}

这里是我们的CustomerDataService

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

8.摘要

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

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

最后更新2023-04-01
0 个评论
标签