# 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。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/untitled-7/untitled-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
