缓存抽象

缓存抽象

 自版本3.1以来,Spring框架为透明地向现有的Spring应用程序添加缓存提供了支持。与事务支持类似,缓存抽象允许一致地使用各种缓存解决方案,对代码的影响最小。

从Spring4.1开始,缓存抽象在JSR-107注释和更多定制选项的支持下得到了显著的扩展。

 

理解缓存抽象

  缓存与缓冲区

术语“缓冲区”和“缓存”倾向于互换使用。不过,请注意,它们代表不同的东西。传统上,缓冲区用作快速实体和慢速实体之间数据的中间临时存储。由于一方必须等待另一方(这会影响性能),缓冲区通过允许整个数据块同时移动而不是以小块移动来缓解这一问题。数据只从缓冲区写入和读取一次。此外,缓冲区对至少一个知道它的方可见。

另一方面,根据定义,缓存是隐藏的,而且任何一方都不知道缓存的发生。它还可以提高性能,但可以让同一数据以快速的方式多次读取。

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

此方法仅适用于保证为给定输入(或参数)返回相同输出(结果)的方法,无论执行多少次。

缓存抽象提供了其他与缓存相关的操作,例如更新缓存内容或删除一个或所有条目的能力。如果缓存处理在应用程序过程中可能更改的数据,则这些功能非常有用。

   与spring框架中的其他服务一样,缓存服务是一个抽象(不是缓存实现),需要使用实际存储来存储缓存数据,也就是说,抽象使您不必编写缓存逻辑,但不提供实际的数据存储。

  这个抽象由org.springframework.cache.Cache和org.springframework.cache.CacheManager接口具体化。

spring提供了这种抽象的一些实现:基于jdk java.util.concurrent.ConcurrentMap的缓存、ehcache 2.x、gemfire缓存、 Caffeine和jsr-107兼容的缓存(例如ehcache 3.x)。有关插入其他缓存存储和提供程序的详细信息,请参阅插入不同的后端缓存。

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

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

缓存一个特定的项与典型的get if not found then-proceed then put finally代码块是直接等价的,后者是通过编程缓存交互找到的没有应用锁,多个线程可能会尝试同时加载同一项这同样适用于驱逐。如果多个线程同时尝试更新或收回数据,则可能会使用过时的数据。某些缓存提供程序在该领域提供高级功能。有关详细信息,请参阅缓存提供程序的文档。

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

Caching declaration: Identify the methods that need to be cached and their policy.

Cache configuration: The backing cache where the data is stored and from which it is read.

缓存声明:标识需要缓存的方法及其策略。

缓存配置:背后的缓存存储数据的地方从中读取数据。

 

基于声明性注释的缓存

对于缓存声明,spring的缓存抽象提供了一组java注释:

@Cacheable

触发缓存填充

@CacheEvict

触发缓存收回。

@CachePut

在不干扰方法执行的情况下更新缓存。

@Caching

重新组合要应用于方法的多个缓存操作。

@CacheConfig

在类级别共享一些与缓存相关的公共设置。

 

@Cacheable 注释

顾名思义,可以使用@Cacheable来划分可缓存的方法,即结果存储在缓存中的方法,以便在随后的调用(使用相同的参数)中返回缓存中的值,而不必实际执行该方法。缓存中的值返回时不必实际执行该方法在最简单的形式中,注释声明需要与注释方法关联的缓存的名称,如下例所示:

@Cacheable("books")

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

 

在前面的代码片段中,findbook方法与名为books的缓存相关联。每次调用该方法时,都会检查缓存以查看调用是否已执行且不必重复在大多数情况下,只声明一个缓存,而注释允许指定多个名称,以便使用多个缓存。在这种情况下,在执行方法之前检查每个缓存。如果至少命中一个缓存,则返回关联的值。

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

@Cacheable({"books", "isbns"})

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

 

默认Key生成

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

  如果没有给出参数,则返回SimpleKey.EMPTY。

  如果只给定一个参数,则返回该实例。

  如果给定了多个参数,则返回包含所有参数的SimpleKey。

只要参数具有自然键并实现有效的 hashCode()  equals()方法,这种方法对大多数用例都很有效。如果不是这样,你需要改变策略。要提供不同的默认密钥生成器,需要实现org.springframework.cache.interceptor.KeyGenerator接口。

随着Spring4.0的发布,默认的key生成策略发生了变化早期版本的Spring使用了一种key生成策略,对于多个key参数,只考虑参数的hashCode()而不考虑equals()这可能会导致意外的key冲突(有关背景信息,请参阅SPR-10237)新的simplekeygenerator在这种情况下使用复合键。

如果希望继续使用以前的key策略,可以配置不推荐使用的org.springframework.cache.interceptor.defaultkeygenerator类,或创建自定义的基于hashcode的keygenerator实现。

 

自定义key生成声明

  由于缓存是通用的,因此目标方法很可能具有各种签名,这些签名无法轻松映射到缓存结构的顶部。

当目标方法有多个参数,其中只有一些参数适合缓存时(而其他参数仅由方法逻辑使用),这一点就变得很明显。请考虑以下示例:

@Cacheable("books")

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

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

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

下面的示例介绍了各种spel声明(如果您不熟悉spel,请自己阅读spring表达式语言):

@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)

 

前面的片段显示了选择某个参数、其属性之一,甚至任意(静态)方法是多么容易。

如果负责生成key的算法过于特定或需要共享key,则可以在操作上定义自定义密钥生成器为此,请指定要使用的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接口。

自定义缓存解析

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

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

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")

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

指定anotherCacheManager 缓存管理器

您还可以完全以类似于替换Key生成的方式替换CacheResolver为每个缓存操作请求解析,让实现根据运行时参数实际解析要使用的缓存下面的示例演示如何指定缓存解析程序:

@Cacheable(cacheResolver="runtimeCacheResolver")

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

指定一个runtimeCacheResolver缓存解析器。

由于Spring 4.1,缓存注释的value属性不再是必需的,因为不管注释的内容如何,缓存解析程序都可以提供这个特定的信息。

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

 

同步缓存

在多线程环境中,可以为同一个参数并发调用某些操作(通常在启动时)。默认情况下,缓存抽象不会锁定任何内容,并且可以多次计算相同的值,从而破坏了缓存的目的

对于这些特殊情况,可以使用sync属性指示基础缓存提供程序在计算值时锁定缓存项结果,只有一个线程正忙于计算该值,而其他线程则被阻塞,直到缓存中的条目更新下面的示例演示如何使用sync属性:

@Cacheable(cacheNames="foos", sync=true)

public Foo executeExpensiveOperation(String id) {...}

这是一个可选功能,您喜爱的缓存库可能不支持它。核心框架提供的所有CacheManager实现都支持它有关详细信息,请参阅缓存提供程序的文档。

条件缓存

有时,方法可能不适合一直进行缓存(例如,它可能依赖于给定的参数)。缓存注释通过condition参数支持这样的功能,condition参数接受一个SpEL表达式,该表达式的计算结果为true或false如果为true,则缓存该方法。如果不是,它的行为就好像方法没有被缓存一样(也就是说,无论缓存中有什么值或使用了什么参数,每次都会执行该方法)。

仅当参数名的长度小于32时,才缓存以下方法:

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

public Book findBook(String name)

设置条件 @Cacheable

除了condition参数之外,您还可以使用除非参数否决向缓存中添加值。与条件不同,除非在调用方法后计算表达式。为了在前面的示例上进行扩展,我们可能只希望缓存平装书(paperback books),如下例所示:

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

public Book findBook(String name)

使用“除非”属性阻止精装本(hardbacks)进行缓存。

 

所述缓存抽象介质java.util.Optional,使用其内容作为所述缓存值只有在存在时才使用。result总是引用业务实体,而不是受支持的包装器,因此前面的示例可以重写如下:

@Cacheable(cacheNames="book",condition="#name.length()<32", unless="#result?.hardback")

public Optional<Book> findBook(String name)

注意,结果仍然是指书本Book而不是可选Optional的因为它可能是空的,我们应该使用安全导航操作符。

 

Available Caching SpEL Evaluation Context

可用缓存spel求值上下文

每个SpEL表达式都根据一个专用上下文进行求值。除了内置参数外,框架还提供专用的缓存相关元数据,如参数名。

下表描述了上下文可用的项,以便您可以将它们用于键和条件计算:

缓存spel可用元数据名称

Name

Location

Description

Example

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

被调用的目标的类

#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 the names are not available (perhaps due to having no debug information),the argument names are also available under the #a<#arg>where #arg 

stands for the argument index (starting from 0).

任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可以在#a<#arg>下使用,其中#arg表示参数索引(从0开始)。

#iban or #a0 (you 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.方法调用的结果(要缓存的值)仅在除非表达式、缓存放置表达式(用于计算键)或缓存收回表达式(当beforeInvocation为false时)中可用对于支持的包装器(例如可选的),#result指的是实际的对象,而不是包装器。

#result

@CachePut注释

当需要在不干扰方法执行的情况下更新缓存时,可以使用@CachePut注释。也就是说,方法总是被执行,其结果被放入缓存(根据@CachePut选项)。

它支持与@Cacheable相同的选项,应该用于缓存填充而不是方法流优化以下示例使用@CachePut注释:

@CachePut(cacheNames="book", key="#isbn")

public Book updateBook(ISBN isbn, BookDescriptor descriptor)

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

 

@CacheEvict注释

缓存抽象不仅允许填充缓存存储,还允许收回。此过程对于从缓存中删除过时或未使用的数据非常有用。与@cacheable相反,@cacheevict定义执行缓存逐出的方法(即充当从缓存中删除数据的触发器的方法)。

与其同级类似,@CacheEvict要求指定受操作影响的一个或多个缓存,允许指定自定义缓存和key解析或条件,并具有一个额外的参数(allentries),该参数指示是否需要执行缓存范围的逐出,而不仅仅是逐出项(基于key)。

 以下示例将从图书缓存中逐出所有条目:

@CacheEvict(cacheNames="books", allEntries=true)

public void loadBooks(InputStream batch)

使用all entries属性从缓存中逐出所有条目。当需要清除整个缓存区域时,此选项就会派上用场。而不是逐出每个条目(这将花费很长的时间,因为它是低效的),所有的条目是在一个操作中删除,如前面的例子所示。注意,框架忽略了在此场景中指定的任何键,因为它不适用(整个缓存被逐出,而不仅仅是一个条目)。

还可以指示在(默认值)之后还是在使用“beforeInvocation ”属性执行该方法之前是否应该进行驱逐。前者提供与其余注释相同的语义:一旦该方法成功完成,则执行高速缓存上的动作(在这种情况下,驱逐)。如果该方法不执行(因为它可能被缓存)或者抛出异常,则不会发生驱逐。后者(beforeInvocation =true)导致驱逐总是在调用方法之前发生。在不需要将驱逐与方法结果挂钩的情况下,这是有用的。

注意,void方法可以与@CacheEvict一起使用-由于这些方法充当触发器,返回值将被忽略(因为它们不与缓存交互)。@cacheable的情况并非如此,它向缓存中添加或更新数据,因此需要结果。

 

@Caching注释

有时,需要指定相同类型的多个注释(例如@CacheEvict或@CachePut),因为不同缓存之间的条件或键表达式不同。@缓存允许在同一方法上使用多个嵌套的@Cacheable、@CachePut和@CacheEvict注释。下面的示例使用两个@CacheEvict注释:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })

public Book importBooks(String deposit, Date date)

 @CacheConfig注释

迄今为止,我们已经看到缓存操作提供了许多定制选项,并且可以为每个操作设置这些选项。但是,某些自定义选项在应用到类的所有操作时可能是繁琐的。例如,指定要用于类的每个缓存操作的缓存的名称可以由单个级别定义替代。这是@CacheConfig开始播放的位置。

@CacheConfig("books")

public class BookRepositoryImpl implements BookRepository

 {

@Cacheable

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

}

 

使用@CacheConfig设置缓存的名称。

cacheConfig是允许共享缓存名称、自定义密钥生成器、自定义CacheManager和自定义CacheResolver的类级别注释。将此注释放置在类上不会启用任何缓存操作。

操作级定制总是覆盖@CacheConfig上的自定义集。因此,这为每个缓存操作提供了三个级别的自定义:

全局配置,可用于CacheManager、KeyGenerator。

在类级别,使用@CacheConfig。

在操作级别。

 

启用缓存注释

需要注意的是,尽管声明缓存注释不会自动触发它们的操作--就像Spring中的许多东西一样,这个特性必须以声明方式启用(这意味着如果您怀疑缓存是罪魁祸首,您可以只删除一个配置行而不是代码中的所有注释来禁用它)。

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

@Configuration

@EnableCaching 

public class AppConfig { }

Alternatively, for XML configuration you can use the cache:annotation-driven element:

<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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven/> 

</beans>

<cache:annotation-driven/> 和@EnableCaching 注释都允许您指定各种选项,这些选项影响通过AOP将缓存行为添加到应用程序的方式。该配置有意与@Transaction的配置相似。

 

用于处理缓存注释的默认通知模式是代理,它允许仅通过代理截获调用。同一类中的本地呼叫不能以这种方式被截获。对于更高级的拦截模式,请考虑切换到AspectJ模式,并结合编译时或加载时编织。

有关实现CachingConfigrer所需的高级自定义(使用Java配置)的更多细节,请参见javadoc。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章