> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/untitled-7/untitled-1.md).

# 13.3　使用 XML 声明缓存

你可能想要知道为什么想要以 XML 的方式声明缓存。毕竟，本章中我们所看到的缓存注解要优雅得多。

我认为有两个原因：

* 你可能会觉得在自己的源码中添加 Spring 的注解有点不太舒服；
* 你需要在没有源码的 bean 上应用缓存功能。

在上面的任意一种情况下，最好（或者说需要）将缓存配置与缓存数据的代码分隔开来。Spring 的 cache 命名空间提供了使用 XML 声明缓存规则的方法，可以作为面向注解缓存的替代方案。因为缓存是一种面向切面的行为，所以 cache 命名空间会与 Spring 的 aop 命名空间结 合起来使用，用来声明缓存所应用的切点在哪里。

要开始配置 XML 声明的缓存，首先需要创建 Spring 配置文件，这个文件中要包含 cache 和 aop 命名空间：

```markup
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cache="http://www.springframework.org/schema/cache"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache.xsd" >
  
  <!-- Caching configuration go here />

</beans>
```

cache 命名空间定义了在 Spring XML 配置文件中声明缓存的配置元素。表 13.5 列出了 cache 命名空间所提供的所有元素。

| 元素                        | 描述                                            |
| ------------------------- | --------------------------------------------- |
| \<cache:annotationdriven> | 启用注解驱动的缓存。等同于 Java 配置中的 @EnableCaching        |
| \<cache:advice>           | 定义缓存通知（advice）。结合，将通知应用到切点上                   |
| \<cache:caching>          | 在缓存通知中，定义一组特定的缓存规则                            |
| \<cache:cacheable>        | 指明某个方法要进行缓存。等同于 @Cacheable 注解                 |
| \<cache:cache-put>        | 指明某个方法要填充缓存，但不会考虑缓存中是否已有匹配的值。等同于 @CachePut 注解 |
| \<cache:cache-evict>      | 指明某个方法要从缓存中移除一个或多个条目，等同于 @CacheEvict 注解       |

&#x20;\<cache:annotation-driven> 元素与 Java 配置中所对应的 @EnableCaching 非常类似，会启用注解驱动的缓存。我们已经讨论过这种风格的缓存，因此没有必要再对其进行介绍。

表 13.5 中其他的元素都用于基于 XML 的缓存配置。接下来的代码清单展现了如何使用这些元素为 SpittleRepository bean 配置缓存，其作用等同于本章前面章使用缓存注解的方式。

{% code title="程序清单 13.7　使用 XML 元素为 SpittleRepository 声明缓存规则" %}

```markup
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cache="http://www.springframework.org/schema/cache"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache.xsd" >
  
  <aop:config>
    <aop:advice advice-ref="cacheAdvice" pointcut="execution(* com.habuma.spittr.db.SpittleRepository.*(..))" />
  </aop:config>
  
  <cache:advice id="cacheAdvice">
    <cache:caching>
      <cache:cacheable cache="spittleCache" method="findRecent" />
      
      <cache:cacheable cache="spittleCache" method="findBySpitterId" />
      
      <cache:cacheable cache="spittleCache" method="findOne" />
      
      <cache:cache-put cache="spittleCache" method="save" key="#result.id" />
      
      <cache:cache-evict cache="spittleCache" method="remove" />
    </cache:caching>
  </cache:advice>
  
  <bean id="cacheManage" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
</beans>
```

{% endcode %}

&#x20;在程序清单 13.7 中，我们首先看到的是 \<aop:advice>，它引用 ID 为 cacheAdvice 的通知，该元素将这个通知与一个切点进行匹配，因此建立了一个完整的切面。在本例中，这个切面的切点会在执行 SpittleRepository 的任意方法时触发。如果这样的方法被 Spring 应用上下文中的任意某个 bean 所调用，那么就会调用切面的通 知。

在这里，通知利用 \<cache:advice> 元素进行了声明。在 \<cache:advice> 元素中，可以包含任意数量的 \<cache:caching> 元素，这些元素用来完整地定义应用的缓存规 则。在本例中，只包含了一个 \<cache:cacheable> 元素。这个元素又包含了三个 \<cache:cacheable> 元素和一个 \<cache:cache-put> 元素。

每个 \<cache:cacheable> 元素都声明了切点中的某一个方法是支持缓存的。这是与 @Cacheable 注解同等作用的 XML 元素。具体来讲，findRecent()、findOne() 和 findBySpitterId() 都声明为支持缓存，它们的返回值将会保存在名为 spittleCache 的缓存之中。

\<cache:cache-put> 是 Spring XML 中与 @CachePut 注解同等作用的元素。它表明一个方法的返回值要填充到缓存之中，但是这个方法本身并不会从缓存中获取返回值。在本例中，save() 方法用来填充缓存。同面向注解的缓存一样，我们需要将默认的 key 改为返回 Spittle 对象的 id 属性。

最后，\<cache:cache-evict> 元素是 Spring XML 中用来替代 @CacheEvict 注解的。它会从缓存中移除元素，这样的话，下次有人进行查找的时候就找不到了。在这里，调用 remove() 时，会将缓存中的 Spittle 删除掉，其中 key 与 remove() 方法所传递进来的 ID 参数相等的条目会从缓存中移除。

需要注意的是，\<cache:advice> 元素有一个 cache-manager 元素，用来指定作为缓存管理器的 bean。它的默认值是 cacheManager，这与程序清单 13.7 底部的 \<bean> 声明恰好是一致的，所以没有必要再显式地进行设置。但是，如果缓存管理器的 ID 与之不同的话（使用多个缓存管理器的时候，可能会遇到这样的场景），那么可以通过设置 cache-manager 属性指定要使用哪个缓存管理器。

另外，还要留意的是，\<cache:cacheable>、\<cache:cache-put> 和 \<cache:cache-evict> 元素都引用了同一个名为 spittleCache 的缓存。为了消除这种重复，我们可以在 \<cache:caching> 元素上指明缓存的名字：

{% code title="" %}

```markup
<cache:advice id="cacheAdvice">
  <cache:caching cache="spittleCache">
      <cache:cacheable method="findRecent" />
      <cache:cacheable method="findBySpitterId" />      
      <cache:cacheable method="findOne" />
      <cache:cache-put method="save" key="#result.id" />
      <cache:cache-evict method="remove" />
  </cache:caching>
</cache:advice>
```

{% endcode %}

\<cache:caching> 有几个可以供 \<cache:cacheable>、\<cahe:cache-put> 和 \<cache:cache-evict> 共享的属性，包括：

cache：指明要存储和获取值的缓存；

condition：SpEL 表达式，如果计算得到的值为false，将会为这 个方法禁用缓存；

key：SpEL 表达式，用来得到缓存的 key（默认为方法的参数）；

method：要缓存的方法名。

除此之外，\<cache:cacheable> 和 \<cache:cache-put> 还有一个 unless 属性，可以为这个可选的属性指定一个 SpEL 表达式，如果这个表达式的计算结果为 true，那么将会阻止将返回值放到缓存之中。

\<cache:cahe-evict> 元素还有几个特有的属性：

* all-entries：如果是 true 的话，缓存中所有的条目都会被移除掉。如果是 false 的话，只有匹配 key 的条目才会被移除掉。
* before-invocation：如果是 true 的话，缓存条目将会在方法调用之前被移除掉。如果是 false 的话，方法调用之后才会移除缓存。

all-entries 和 before-invocation 的默认值都是 false。这意味着在使用 \<cache:cache-evict> 元素且不配置这两个属性时，会在方法调用完成后只删除一个缓存条目。要删除的条目会通过默认的 key（基于方法的参数）进行识别，当然也可以通过为名为 key 的属性设置一个 SpEL 表达式指定要删除的 key。
