Spring Cache 入门教程

1、缓存抽象

本文将带你了解如何使用 Spring Cache 来提高系统性能。

2、开始使用

Spring 提供的核心缓存抽象位于 spring-context 模块中。

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

另外,还有一个名为 spring-context-support 的模块,它位于 spring-context 模块之上,并提供了更多由 EhCacheCaffeine 支持的 CacheManager。如果想使用这些模块作为缓存存储,则需要使用 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 Starter 来轻松添加缓存依赖项:

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

该 Starter 包含了 spring-context-support 模块。

3、启用缓存

只需在任何配置类中添加 @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 上存在 Starter 依赖,并且与 @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 自动配置会获取到这些 Customizer,并在完成初始化之前将其应用到当前的 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

缓存的数据如果不进行清理,会保留大量陈旧或未使用甚至是过期的数据。

可以使用 @CacheEvict 注解来表示删除一个、多个或所有的值,以刷新缓存:

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

如上,使用参数 allEntries 与要清空的缓存结合使用;这将清除缓存 addresses 中的所有条目。

4.3、@CachePut

使用 @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、condition 参数

如果想对注解激活的条件进行更多控制,可以用 condition 参数定义一个 SpEL 表达式,框架会根据该表达式的计算结果进行缓存:

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

SpEL 表达式中可以通过 # 访问方法参数。

5.2、unless 参数

还可以通过 unless 参数,根据方法的返回值而不是参数值来控制缓存:

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

除非返回的地址短于 64 个字符,否则上述注解将缓存地址。

conditionunless 参数可与所有缓存注解结合使用。

事实证明,这种条件缓存对管理大型结果非常有效。它还有助于根据方法参数自定义行为,而不是对所有操作强制执行通用行为。

6、基于 XML 的声明式缓存

如果无法访问应用的源码,或者想从外部注入缓存行为,也可以使用基于 XML 的声明式缓存。

XML 配置如下:

<!-- 希望缓存的服务 -->
<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>
<!-- 定义缓存行为 -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
    <cache:caching cache="addresses">
        <cache:cacheable method="getAddress" key="#customer.name"/>
    </cache:caching>
</cache:advice>

<!-- 将该行为应用于 CustomerDataService 接口的所有实现 -->
<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 Cache 抽象来实现声明式缓存以及如何通过缓存注解的 conditionunless 属性来灵活地控制缓存条件。


Ref:https://www.baeldung.com/spring-cache-tutorial