Spring源码之缓存抽象(36. Cache Abstraction)

36.1 Introduction

从3.1版本开始,Spring Framework就支持在现有Spring应用程序中透明地添加缓存。与事务支持类似,缓存抽象允许在对代码影响最小的情况下一致使用各种缓存解决方案。

从Spring 4.1开始,在JSR-107注释和更多定制选项的支持下,缓存抽象得到了显著改进。

36.2 Understanding the cache abstraction

Cache vs Buffer

术语“buffer”(缓冲区)和“cache”(缓存)往往可以互换使用;但是要注意它们代表不同的东西。缓冲区通常用作快实体和慢实体之间数据的中间临时存储。由于一方必须等待另一方影响性能,因此缓冲区允许整个数据块同时移动,而不是以小块的形式移动,从而减轻了这种情况。数据只从缓冲区写入和读取一次。此外,缓冲区对至少知道缓冲区的一方是可见的。

另一方面,缓存的定义是隐藏的,双方都不知道缓存的发生。它还可以提高性能,但这是通过允许以快速方式多次读取相同的数据来实现的。

在这里可以找到对两者差异的进一步解释。

抽象的核心是将缓存应用于Java方法,从而减少了基于缓存中可用信息的执行次数。也就是说,每次调用目标方法时,抽象都会应用一个缓存行为,检查方法是否已经为给定的参数执行过。如果有,则返回缓存的结果,而不需要执行实际的方法;如果没有,则执行方法,缓存结果并返回给用户,以便在下一次调用该方法时返回缓存的结果。这样,对于给定的一组参数和重用的结果,昂贵的方法(无论是CPU还是IO绑定)只能执行一次,而不必实际再次执行该方法。缓存逻辑透明地应用,不会对调用程序造成任何干扰。

显然,这种方法只适用于那些保证对于给定的输入(或参数)无论执行多少次都返回相同输出(结果)的方法。

抽象提供了其他与缓存相关的操作,例如更新缓存内容或删除所有条目之一的能力。如果缓存处理的数据在应用程序过程中可能会发生变化,那么这些参数非常有用。

就像其他服务在Spring框架中,缓存服务是一个抽象(不是缓存实现),需要使用一个实际的存储来存储缓存数据——也就是说,抽象使开发人员不必写缓存逻辑但不提供实际的存储。

这个抽象由org.springframework.cache.Cache和org.springframework.cache.CacheManager接口实现。

这种抽象有几种现成的实现:JDK java.util.concurrent.ConcurrentMap,Ehcache 2.x, Gemfire cache, Caffeine, Guava caches和JSR-107兼容缓存(例如Ehcache 3.x)。有关插入其他缓存存储/提供程序的更多信息,请参见36.7节“插入不同后端缓存”。

缓存抽象没有对多线程和多进程环境的特殊处理,因为这些特性是由缓存实现处理的。

如果您有一个多进程环境(即部署在多个节点上的应用程序),则需要相应地配置缓存提供程序。根据您的用例,在多个节点上复制相同的数据可能就足够了,但是如果您在应用程序过程中更改了数据,您可能需要启用其他传播机制。

缓存特定的项与典型的“如果没有找到,然后进行处理,最后放置”代码块是直接等价的,这些代码块是通过编程缓存交互找到的:没有应用锁,多个线程可能尝试同时加载相同的项。

这同样适用于回收:如果多个线程试图同时更新或回收数据,您可能会使用过期的数据。某些缓存提供程序在该领域提供了高级功能,请参阅正在使用的缓存提供程序文档以获得更多详细信息。

要使用缓存抽象,开发人员需要注意两个方面:

  • 缓存声明——标识需要缓存的方法及其策略
  • 缓存配置——存储和读取数据的备份缓存

36.3 Declarative annotation-based caching 基于注释的声明式缓存

为了缓存声明,抽象提供了一组Java注释:

  • @Cacheable 触发缓存人口
  • @CacheEvict 触发缓存驱逐
  • @CachePut 在不影响方法执行的情况下更新缓存
  • @Caching 对要应用于某个方法的多个缓存操作进行重新分组
  • @CacheConfig 在类级别上共享一些常见的缓存相关设置

让我们仔细看看每个注释:

36.3.1 @Cacheable annotation

顾名思义,@Cacheable用于划分可缓存的方法 -- 也就是说,结果存储在缓存中的方法,因此在后续调用时(使用相同的参数),缓存中的值将被返回,而不需要实际执行该方法。在最简单的形式中,注释声明需要与注释方法相关联的缓存的名称:

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

在上面的代码片段中,findBook方法与名为books的缓存相关联。每次调用该方法时,都会检查缓存,以确定调用是否已经执行,并且不需要重复。虽然在大多数情况下,只声明一个缓存,但注释允许指定多个名称,以便使用多个缓存。

在这种情况下,每一个缓存都将在执行方法之前被检查-如果至少一个缓存被命中,那么相关的值将被返回:

所有其他不包含该值的缓存也将被更新,即使缓存的方法没有实际执行。

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

Default Key Generation

由于缓存本质上是键值存储,缓存方法的每次调用都需要转换为适合缓存访问的键。开箱即用,缓存抽象使用一个简单的密钥生成器基于以下算法:

  • 如果没有提供参数,则返回SimpleKey.EMPTY。
  • 如果只给出一个参数,则返回该实例。
  • 如果给定多个参数,则返回一个包含所有参数的SimpleKey。

这种方法适用于大多数用例;只要参数具有自然键并实现有效的hashCode()和equals()方法。如果不是这样,那么战略就需要改变。

要提供不同的默认密钥生成器,需要实现org.springframe .cache.interceptor.KeyGenerator 接口。

随着Spring 4.0的发布,默认的密钥生成策略发生了变化。早期版本的Spring使用键生成策略,对于多个键参数,只考虑参数的hashCode(),而不考虑equals();这可能会导致意外的键冲突(背景信息请参见spring -10237)。新的“SimpleKeyGenerator”在这种情况下使用复合键。如果希望继续使用前面的密钥策略,可以配置已弃用的org.springframework.cache.interceptor.DefaultKeyGenerator类,或者创建一个基于散列的自定义“KeyGenerator”实现。

Custom Key Generation Declaration  自定义KEY生成声明

由于缓存是通用的,所以目标方法很可能具有各种各样的签名,这些签名不能简单地映射到缓存结构的顶部。当目标方法有多个参数,其中只有一些适合缓存(而其余仅由方法逻辑使用)时,这一点就变得很明显。例如:

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

乍一看,虽然这两个布尔参数影响查找图书的方式,但它们对缓存没有用处。更重要的是,如果两者中只有一个重要,而另一个不重要呢?

对于这种情况,@Cacheable注释允许用户指定如何通过其key属性生成key。开发人员可以使用SpEL来选择感兴趣的参数(或其嵌套属性)、执行操作甚至调用任意方法,而无需编写任何代码或实现任何接口。这是针对默认生成器的推荐方法,因为随着代码库的增长,方法的签名往往会有很大的不同;虽然默认策略可能适用于某些方法,但很少适用于所有方法。

下面是各种SpEL声明的一些例子——如果您不熟悉它,请阅读第10章Spring Expression Language (SpEL):

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

上面的代码片段展示了选择某个参数、它的某个属性甚至任意(静态)方法是多么容易。

如果生成密钥的算法过于特定,或者需要共享密钥,则可以在操作上定义自定义密钥生成器。为此,请指定要使用的KeyGenerator bean实现的名称:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

key 和keyGenerator 参数是互斥的,指定这两个参数的操作将导致异常。

Default Cache Resolution

开箱即用的缓存抽象使用一个简单的CacheResolver,它检索使用配置的CacheManager在操作级别定义的缓存。

要提供不同的默认缓存解析器,需要实现org.springframework.cache.interceptor.CacheResolver接口。

Custom cache resolution 自定义缓存解决方案

默认的缓存解析非常适合使用单个CacheManager且没有复杂缓存解析需求的应用程序。

对于使用多个缓存管理器的应用程序,可以将cacheManager设置为每个操作使用:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}

也可以完全以与密钥生成类似的方式替换CacheResolver。每个缓存操作都需要解析,这样实现就有机会根据运行时参数实际解析要使用的缓存:

@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}

自Spring 4.1以来,缓存注释的value属性不再是强制性的,因为无论注释的内容如何,CacheResolver都可以提供这个特定的信息。

与key和keyGenerator类似,cacheManager和cacheResolver参数是互斥的,指定这两个参数的操作将导致异常,因为定制的cacheManager将被cacheResolver实现忽略。这可能不是你所期望的。

Synchronized caching

在多线程环境中,某些操作可能会为相同的参数并发调用(通常在启动时)。默认情况下,缓存抽象不会锁定任何东西,相同的值可能会被计算多次,这就违背了缓存的目的。

对于这些特殊情况,可以使用sync属性指示底层缓存提供程序在计算值时锁定缓存项。因此,在缓存中更新条目之前,只有一个线程忙于计算该值,而其他线程则被阻塞。

@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}

这是一个可选的特性,您喜欢的缓存库可能不支持它。核心框架提供的所有CacheManager实现都支持它。有关更多细节,请参阅缓存提供程序的文档。

Conditional caching

有时,方法可能不适合一直进行缓存(例如,它可能依赖于给定的参数)。缓存注释通过条件参数支持这种功能,条件参数接受一个被计算为true或false的SpEL表达式。

如果为真,则缓存该方法——如果为假,则表现为该方法未缓存,无论缓存中有什么值或使用什么参数,每次都将执行该方法。一个快速的例子-下面的方法将被缓存,只有当参数名的长度小于32:

@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)

此外,条件参数unless可用于否决向缓存中添加值。与条件不同,除非表达式是在调用方法之后计算的。扩展前面的例子-也许我们只想缓存平装书:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)

缓存抽象支持java.util.Optional,仅在内容出现时才将其内容作为缓存值使用。#result总是引用业务实体,从不在受支持的包装器上,所以前面的示例可以重写如下:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

注意结果仍然引用Book而不是Optional。因为它可能是null,所以我们应该使用安全导航操作符。

Available caching SpEL evaluation context

NameLocationDescriptionExample

methodName

root object

The name of the method being invoked

#root.methodName

method

root object

The method being invoked

#root.method.name

target

root object

The target object being invoked

#root.target

targetClass

root object

The class of the target being invoked

#root.targetClass

args

root object

The arguments (as array) used for invoking the target

#root.args[0]

caches

root object

Collection of caches against which the current method is executed

#root.caches[0].name

argument name

evaluation context

Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0).

#iban or #a0 (one can also use #p0 or #p<#arg> notation as an alias).

result

evaluation context

The result of the method call (the value to be cached). Only available in unless expressions, cache put expressions (to compute the key), or cache evict expressions (when beforeInvocation is false). For supported wrappers such as Optional#result refers to the actual object, not the wrapper.

#result

36.3.2 @CachePut annotation

对于需要在不影响方法执行的情况下更新缓存的情况,可以使用@CachePut注释。也就是说,将始终执行该方法并将其结果放入缓存中(根据@CachePut选项)。它支持与@Cacheable相同的选项,应该用于缓存填充而不是方法流优化:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

注意,通常强烈反对在同一方法上使用@CachePut和@Cacheable注释,因为它们具有不同的行为。后者通过使用缓存跳过方法执行,而前者强制执行,以便执行缓存更新。这将导致意外的行为,除了特定的角情况(如注释具有相互排斥的条件)之外,应该避免这样的声明。还要注意的是,这种条件不应该依赖于结果对象(即#result变量),因为它们是预先验证的,以确认排除。

36.3.3 @CacheEvict annotation

缓存抽象不仅允许填充缓存存储,还允许回收。此过程对于从缓存中删除过时或未使用的数据非常有用。与@Cacheable不同,@CacheEvict标注了执行缓存驱逐的方法,即作为触发器从缓存中删除数据的方法。

就像兄弟姐妹,@CacheEvict需要指定一个(或多个)缓存所影响的行动,允许自定义缓存和关键的决议或指定一个条件,但除此之外,额外特性参数allEntries这表明cache-wide驱逐是否需要执行而不是只是一个条目(基于key):

@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)

当需要清除整个缓存区域时,这个选项非常有用——而不是清除每个条目(这会花费很长时间,因为效率很低),所有条目都在一个操作中删除,如上所示。请注意,框架将忽略此场景中指定的任何键,因为它不适用(整个缓存将被回收,而不仅仅是一个条目)。

您还可以指示是否应该在(默认情况下)之后或通过beforeInvocation属性在方法执行之前执行回收。前者提供了与其他注释相同的语义——一旦方法成功完成,将执行缓存上的一个操作(在本例中是删除)。如果方法不执行(因为它可能被缓存)或抛出异常,则不会发生回收。后者(beforeInvocation=true)会导致在调用方法之前总是发生驱逐——这在不需要将驱逐绑定到方法结果的情况下非常有用。

重要的是要注意,无返回值的方法可以用于@CacheEvict——作为触发器,忽略返回值(如他们不与缓存),情况不是这样@Cacheable添加/更新数据到缓存,因此需要一个结果。

36.3.4 @Caching annotation

在某些情况下,需要指定相同类型的多个注释,例如@CacheEvict或者@CachePut,因为不同缓存之间的条件或键表达式是不同的。@Caching允许在同一个方法上使用多个嵌套的@Cacheable, @CachePut和@CacheEvict:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

36.3.5 @CacheConfig annotation

到目前为止,我们已经看到缓存操作提供了许多定制选项,这些选项可以在操作的基础上进行设置。但是,如果一些定制选项适用于类的所有操作,那么配置它们可能会很繁琐。例如,为类的每个缓存操作指定要使用的缓存的名称可以由单个类级别定义替换。这就是@CacheConfig发挥作用的地方。

@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {

    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

@CacheConfig是一个类级注释,它允许共享缓存名称、自定义密钥生成器、自定义CacheManager,最后是自定义CacheResolver。将此注释放在类上不会打开任何缓存操作。

操作级定制将始终覆盖@CacheConfig上设置的定制。因此,这为每个缓存操作提供了三个级别的自定义:

  • 全局配置,可用于CacheManager、KeyGenerator
  • At class level, using @CacheConfig
  • At the operation level

36.3.6 Enable caching annotations

重要的是要注意,虽然宣布缓存注释并不会自动触发他们的行为——就像很多东西在Spring,该特性必须声明启用(这意味着如果你怀疑缓存是罪魁祸首,您可以禁用它只通过移除一个配置而不是所有的注释代码行)。

要启用缓存注释,请将注释@EnableCaching添加到您的@Configuration类之一:

@Configuration
@EnableCaching
public class AppConfig {
}

对于XML配置,也可以使用缓存:注解驱动的元素:

<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"
    xsi:schemaLocation="
        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">

        <cache:annotation-driven/>
</beans>

缓存:注解驱动的元素和@EnableCaching注解都允许指定影响通过AOP向应用程序添加缓存行为的方式的各种选项。该配置与@Transactional的配置类似:

处理缓存注释的默认通知模式是“代理”,它只允许通过代理拦截调用;同一类中的本地调用不能以这种方式被拦截。对于更高级的侦听模式,可以考虑结合编译时或加载时编织切换到“aspectj”模式。

使用Java config的高级定制需要实现CachingConfigurer:有关详细信息,请参阅javadoc。

Table 36.2. Cache annotation settings

XML AttributeAnnotation AttributeDefaultDescription

cache-manager

N/A (See CachingConfigurerjavadocs)

cacheManager

Name of cache manager to use. A default CacheResolver will be initialized behind the scenes with this cache manager (or `cacheManager`if not set). For more fine-grained management of the cache resolution, consider setting the 'cache-resolver' attribute.

cache-resolver

N/A (See CachingConfigurerjavadocs)

SimpleCacheResolver using the configured cacheManager.

The bean name of the CacheResolver that is to be used to resolve the backing caches. This attribute is not required, and only needs to be specified as an alternative to the 'cache-manager' attribute.

key-generator

N/A (See CachingConfigurerjavadocs)

SimpleKeyGenerator

Name of the custom key generator to use.

error-handler

N/A (See CachingConfigurerjavadocs)

SimpleCacheErrorHandler

Name of the custom cache error handler to use. By default, any exception throw during a cache related operations are thrown back at the client.

mode

mode

proxy

The default mode "proxy" processes annotated beans to be proxied using Spring’s AOP framework (following proxy semantics, as discussed above, applying to method calls coming in through the proxy only). The alternative mode "aspectj" instead weaves the affected classes with Spring’s AspectJ caching aspect, modifying the target class byte code to apply to any kind of method call. AspectJ weaving requires spring-aspects.jar in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See the section called “Spring configuration” for details on how to set up load-time weaving.)

proxy-target-class

proxyTargetClass

false

Applies to proxy mode only. Controls what type of caching proxies are created for classes annotated with the @Cacheable or @CacheEvictannotations. If the proxy-target-class attribute is set to true, then class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, then standard JDK interface-based proxies are created. (See Section 11.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)

order

order

Ordered.LOWEST_PRECEDENCE

Defines the order of the cache advice that is applied to beans annotated with @Cacheable or @CacheEvict. (For more information about the rules related to ordering of AOP advice, see the section called “Advice ordering”.) No specified ordering means that the AOP subsystem determines the order of the advice.

 

Method visibility and cache annotations 方法可见性和缓存注释

在使用代理时,应该只将缓存注释应用于具有公共可见性的方法。如果使用这些注释注释受保护的、私有的或包可见的方法,则不会引发错误,但是注释的方法不会显示配置的缓存设置。如果需要在非公共方法更改字节码本身时对其进行注释,请考虑使用AspectJ(参见下面)。

Spring建议您只使用@Cache*注释来注释具体类(以及具体类的方法),而不是注释接口。您当然可以将@Cache*注释放置在接口(或接口方法)上,但是只有在使用基于接口的代理时,这种方法才能正常工作。Java注释的事实并不意味着继承接口如果您使用的是基于类的代理(proxy-target-class = " true ")或weaving-based方面(模式=“aspectj”),然后由代理缓存设置不认可和编织的基础设施,和对象将不会被包裹在一个缓存代理,这将是绝对不好。

在代理模式(默认模式)中,只有通过代理传入的外部方法调用被拦截。这意味着,实际上,自调用目标对象中的方法调用目标对象的另一个方法不会在运行时导致实际的缓存,即使调用的方法被标记为@Cacheable——考虑在本例中使用aspectj模式。此外,代理必须完全初始化,以提供预期的行为,因此您不应该在初始化代码中依赖这个特性,即@PostConstruct。

36.3.7 Using custom annotations

Custom annotation and AspectJ

这个特性只在基于代理的方法中能够开箱即用,但是使用AspectJ需要额外的工作。

spring-aspect模块仅为标准注释定义了一个方面。如果您已经定义了自己的注释,那么还需要为这些注释定义一个方面。查看AnnotationCacheAspect作为示例。

缓存抽象允许您使用自己的注释来标识触发缓存填充或清除的方法。作为模板机制,这非常方便,因为它消除了重复缓存注释声明的需要(如果指定了键或条件,尤其有用),或者在代码库中不允许导入外部(org.springframework)。

与构造型注释的其余部分类似,@Cacheable、@CachePut、@CacheEvict和@CacheConfig可以用作元注释,也就是可以注释其他注释的注释。也就是说,让我们用我们自己的自定义注释替换一个普通的@Cacheable声明:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}

上面,我们定义了自己的SlowService注释,它本身是用@Cacheable注释的——现在我们可以替换下面的代码:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

with : 

@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

即使@SlowService不是Spring注释,容器也会在运行时自动获取其声明并理解其含义。注意,如上所述,需要启用注解驱动的行为。

36.4 JCache (JSR-107) annotations

由于Spring Framework 4.1,缓存抽象完全支持JCache标准注释:它们是@CacheResult、@CachePut、@CacheRemove和@CacheRemoveAll以及@CacheDefaults、@CacheKey和@CacheValue的伙伴。这些注释可以正确使用,而无需将缓存存储迁移到JSR-107:内部实现使用Spring的缓存抽象,并提供符合规范的默认CacheResolver和KeyGenerator实现。换句话说,

如果您已经在使用Spring的缓存抽象,那么您可以切换到这些标准注释,而不需要更改缓存存储(或者配置)。

Table 36.3. Spring vs. JSR-107 caching annotations

SpringJSR-107Remark

@Cacheable

@CacheResult

相当类似。@CacheResult可以缓存特定的异常,并强制执行方法,而不管缓存的内容是什么。

@CachePut

@CachePut

当Spring使用方法调用的结果更新缓存时,JCache需要将其作为参数传递,并用@CacheValue注释。由于这种差异,JCache允许在实际方法调用之前或之后更新缓存。

@CacheEvict

@CacheRemove

相当类似。@CacheRemove支持在方法调用导致异常时进行条件回收。

@CacheEvict(allEntries=true)

@CacheRemoveAll

See @CacheRemove.

@CacheConfig

@CacheDefaults

允许以类似的方式配置相同的概念。

JCache有javax.cache.annotation.CacheResolver的概念。它与Spring的CacheResolver接口相同,只是JCache只支持一个缓存。默认情况下,一个简单的实现基于注释上声明的名称检索要使用的缓存。应该注意的是,如果注释上没有指定缓存名,那么将自动生成默认值,请检查@CacheResult#cacheName()的javadoc以获得更多信息。

CacheResolver实例由CacheResolverFactory检索。可以根据缓存操作自定义工厂:

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class)
public Book findBook(ISBN isbn)

对于所有引用的类,Spring都尝试定位具有给定类型的bean。如果存在多个匹配项,则创建一个新实例,并可以使用常规bean生命周期回调(如依赖项注入)。

键是由avax.cache.annotation.CacheKeyGenerator生成的。与Spring的KeyGenerator的用途相同的CacheKeyGenerator。默认情况下,除非使用@CacheKey注释了至少一个参数,否则会考虑所有方法参数。这类似于Spring的自定义密钥生成声明。例如,这些操作是相同的,一个使用Spring的抽象,另一个使用JCache:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

还可以在操作上指定要使用的CacheKeyResolver,其方式与CacheResolverFactory类似。

JCache可以管理由带注释的方法引发的异常:这可以防止缓存的更新,但它也可以将异常缓存为失败的指示器,而不是再次调用该方法。假设如果ISBN的结构无效,则抛出InvalidIsbnNotFoundException。这是一个永久性的失败,没有一本书可以用这样的参数来检索。下面将缓存异常,以便使用相同的无效ISBN进行进一步调用时,直接抛出缓存的异常,而不是再次调用该方法。

@CacheResult(cacheName="books", exceptionCacheName="failures"
            cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)

36.4.2 Enabling JSR-107 support

在启用JSR-107支持和Spring的声明性注释支持时,不需要做任何特定的工作。如果类路径中同时存在JSR-107 API和spring上下文支持模块,那么@EnableCaching和cache:annotation驱动的元素将自动启用JCache支持。

根据您的用例,选择基本上是您自己的。您甚至可以使用JSR-107 API和Spring自己的注释混合和匹配服务。但是请注意,如果这些服务影响相同的缓存,则应该使用一致的和相同的密钥生成实现。

36.5 Declarative XML-based caching

略。。

36.6 Configuring the cache storage

开箱即用的缓存抽象提供了几个存储集成。要使用它们,只需要声明一个适当的CacheManager—一个控制和管理缓存的实体,可以用来检索这些缓存以进行存储。

36.6.1 JDK ConcurrentMap-based Cache

基于jdk的缓存实现位于org.springframework.cache.concurrente之下。它允许使用ConcurrentHashMap作为备份缓存存储。

<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

上面的代码片段使用SimpleCacheManager为名为default和books的两个嵌套的ConcurrentMapCache实例创建CacheManager。注意,名称是直接为每个缓存配置的。

由于缓存是由应用程序创建的,因此它被绑定到其生命周期中,使其适合于基本用例、测试或简单应用程序。缓存可以很好地扩展,并且非常快,但是它不提供任何管理或持久性功能,也不提供回收契约。

36.6.2 Ehcache-based Cache

Ehcache 3.x完全符合JSR-107,不需要专门的支持。

Ehcache 2.x实现位于org.springframework.cache.ehcache包。同样,要使用它,只需声明适当的CacheManager:

<bean id="cacheManager"
        class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

这个设置引导Spring IoC中的ehcache库(通过ehcache bean),然后将其连接到专用的CacheManager实现中。注意,整个特定于ehcache的配置都是从ehcache.xml读取的。

36.6.3 Caffeine Cache

Caffeine 是对Guava’s缓存的Java 8重写,它的实现位于org.springframework.cache.caffeine包并且提供了几个特点的Caffeine。

配置按需创建缓存的CacheManager很简单:

<bean id="cacheManager"
        class="org.springframework.cache.caffeine.CaffeineCacheManager"/>

还可以提供显式使用的缓存。在这种情况下,manager只提供下列文件:

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Caffeine 缓存管理器还支持自定义Caffeine 和CacheLoader。有关这些的更多信息,请参阅Caffeine 文档

36.6.4 Guava Cache

Guava 的实现位于org.springframework.cache.guava包并且提供访问Guava 的几个特点。

配置按需创建缓存的CacheManager很简单:

<bean id="cacheManager"
      class="org.springframework.cache.guava.GuavaCacheManager"/>

还可以提供显式使用的缓存。在这种情况下,经理只提供下列文件:

<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Guava  CacheManager还支持客户端CacheBuilder和CacheLoader。有关Guava 的更多信息,请参阅Guava 文档。

36.6.5 GemFire-based Cache

GemFire是一个面向内存/磁盘支持的、弹性可伸缩的、持续可用的、活动的(内置基于模式的订阅通知)、全局复制的数据库,并提供全功能的边缘缓存。有关如何将GemFire用作CacheManager(以及更多)的更多信息,请参阅Spring Data GemFire参考文档。

36.6.6 JSR-107 Cache

与JSR-107兼容的缓存也可以被Spring的缓存抽象使用。JCache实现位于org.springframework.cache.jcache包。

同样,要使用它,只需声明适当的CacheManager:

<bean id="cacheManager"
        class="org.springframework.cache.jcache.JCacheCacheManager"
        p:cache-manager-ref="jCacheManager"/>

<!-- JSR-107 cache manager setup  -->
<bean id="jCacheManager" .../>

36.6.7 Dealing with caches without a backing store

有时在切换环境或进行测试时,可能会在没有配置实际的备份缓存的情况下使用缓存声明。由于这是一个无效的配置,在运行时将抛出一个异常,因为缓存基础结构无法找到合适的存储。在这种情况下,与其删除缓存声明(这可能会很繁琐),还不如连接一个简单的、不执行缓存的虚拟缓存——也就是说,每次都强制执行缓存的方法:

<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
    <property name="cacheManagers">
        <list>
            <ref bean="jdkCache"/>
            <ref bean="gemfireCache"/>
        </list>
    </property>
    <property name="fallbackToNoOpCache" value="true"/>
</bean>

上面的CompositeCacheManager链接多个缓存管理器,并且,通过fallbackToNoOpCache标志,为所有未由配置的缓存管理器处理的定义添加no op缓存。也就是说,在jdkCache或gem热(上面配置的)中没有找到的每个缓存定义都将由no op缓存处理,它不会存储导致每次执行目标方法的任何信息。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值