Spring缓存架构详解-Spring官方原版-全面解析

Spring缓存架构详解-Spring官方原版-全面解析

计算机毕设项目资讯获取:

大家点赞、收藏、关注、评论啦 、查看👇🏻👇🏻👇🏻获取项目下载链接,博主联系方式👇🏻👇🏻👇🏻

链接点击直达:下载链接

一、理解缓存抽象

从 3.1 版开始,Spring 框架支持透明地将缓存添加到 现有的 Spring 应用程序。与事务支持类似,缓存抽象允许一致地使用各种缓存解决方案,这些解决方案与 对代码的影响最小。在 Spring Framework 4.1 中,缓存抽象得到了显著扩展,支持 用于 JSR-107 注释和更多自定义选项。

缓存与缓冲区

术语“缓冲区”和“缓存”往往可以互换使用。但请注意, 它们代表不同的东西。传统上,缓冲液用作中间体 快速和慢速实体之间的数据的临时存储。因为一方将不得不等待 对于另一个(影响性能),缓冲区通过允许整个 数据块一次移动,而不是小块移动。数据被写入和读取 缓冲区中仅一次。此外,缓冲区至少对一方可见 这是意识到的。

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

与Spring框架中的其他服务一样,缓存服务是一种抽象(而不是缓存实现),需要使用实际存储来存储缓存数据 — 也就是说,抽象使您不必编写缓存逻辑,但不提供实际的数据存储。此抽象由org.springframework.cache.cache和org.springfframework.cache.CacheManager接口具体化。

Spring提供了一些抽象的实现:基于JDKjava.util.concurrent.ConcurrentMap的缓存、Gemfire缓存、Caffeine和JSR-107兼容缓存(如Ehcache3.x)。有关插入其他缓存存储和提供程序的更多信息,请参阅插入不同后端缓存。

二、基于声明式注释的缓存

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

  • @Cacheable:触发缓存填充。

  • @CacheEvict:触发缓存逐出。

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

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

  • @CacheConfig:在类级别共享一些与缓存相关的常见设置。

2.1 注释@Cacheable

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

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

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

以下示例在具有多个缓存的findBook方法上使用@Cacheable:

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成

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

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

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

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

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

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

自定义密钥生成声明

由于缓存是通用的,因此目标方法很可能具有各种签名 这不能轻易地映射到缓存结构的顶部。这往往变得显而易见 当目标方法有多个参数时,其中只有一些参数适合 缓存(而其余的仅由方法逻辑使用)。请考虑以下示例:

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

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

以下示例使用了各种SpEL声明(如果您不熟悉SpEL,请自行阅读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)

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

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

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
默认的缓存解决方案

缓存抽象使用一个简单的CacheResolver,它通过使用配置的CacheManager检索在操作级别定义的缓存。要提供不同的默认缓存解析器,需要实现org.springframework.cache.interceptor.CacheResolver接口。

自定义缓存解决方案

默认缓存分辨率非常适合使用单个CacheManager且没有复杂缓存分辨率要求的应用程序。

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

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
同步缓存

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

对于这些特定情况,可以使用sync属性指示底层缓存提供程序在计算值时锁定缓存条目。因此,只有一个线程忙于计算值,而其他线程则被阻止,直到缓存中的条目被更新。以下示例显示了如何使用sync属性:

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
条件缓存

有时,方法可能不适合一直缓存(例如,它可能取决于给定的参数)。缓存注释通过条件参数支持这样的用例,条件参数采用计算为true或false的SpEL表达式。如果为true,则缓存该方法。如果没有,它的行为就像该方法没有被缓存一样(即,无论缓存中有什么值或使用了什么参数,每次都会调用该方法)。例如,只有当参数名称的长度小于32时,才会缓存以下方法:

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

在@Cacheable上设置条件。

除了条件参数之外,还可以使用除非参数否决向缓存添加值。与条件不同,除非在调用方法之后计算表达式。为了扩展前面的示例,也许我们只想缓存平装书,就像下面的示例所做的那样:

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

使用“unless”属性阻止hardback

缓存抽象支持java.util.Optional返回类型。如果存在可选值,它将存储在关联的缓存中。如果不存在可选值,则空值将存储在关联的缓存中#result始终引用业务实体,而不是受支持的包装器,因此前面的示例可以重写如下:

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

2.2 注释@CachePut

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

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

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

2.3 注释@CacheEvict

缓存抽象不仅允许填充缓存存储,还允许清除。此过程对于从缓存中删除过时或未使用的数据非常有用。与@Cacheable不同,@CacheEvict定义了执行缓存清除的方法(即充当从缓存中删除数据的触发器的方法)。与它的同级类似,@CacheEvict需要指定一个或多个受操作影响的缓存,允许指定自定义缓存和密钥解析或条件,并具有一个额外的参数(allEntries),指示是否需要执行缓存范围的清除,而不仅仅是项清除(基于密钥)。以下示例从图书缓存中收回所有条目:

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

使用allEntries属性从缓存中清除所有条目。

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

您还可以通过使用beforeInvocation属性来指示是在(默认)之后还是在调用方法之前进行逐出。前者提供了与其他注释相同的语义:一旦方法成功完成,就会对缓存执行一个操作(在本例中是驱逐)。如果该方法未运行(因为它可能被缓存)或引发异常,则不会发生逐出。后者(beforeInvocation=true)导致清除总是在调用方法之前发生。这在清除不需要与方法结果挂钩的情况下非常有用。

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

2.4 注释@Caching

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

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

2.5注释@CacheConfig

到目前为止,我们已经看到缓存操作提供了许多自定义选项,您可以为每个操作设置这些选项。然而,如果某些自定义选项适用于类的所有操作,那么它们的配置可能会很繁琐。例如,指定用于类的每个缓存操作的缓存的名称可以由单个类级定义替换。这就是@CacheConfig发挥作用的地方。以下示例使用@CacheConfig设置缓存的名称:

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

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

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

@CacheConfig是一个类级注释,允许共享缓存名称、自定义KeyGenerator、自定义CacheManager和自定义CacheResolver。在类上放置此注释不会打开任何缓存操作。

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

  • 全局配置,可用于CacheManager和KeyGenerator。

  • 在类级别,使用@CacheConfig。

  • 在操作层面。

2.6启用缓存注释

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

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

@Configuration
@EnableCaching
public class AppConfig {
}

表 10.缓存批注设置

XML 属性

注释属性

违约

描述

cache-manager

N/A(参见 CachingConfigurer javadoc)

cacheManager

要使用的缓存管理器的名称。在后面初始化默认值 具有此缓存管理器的场景(如果未设置)。欲了解更多信息 缓存分辨率的细粒度管理,考虑设置“缓存解析程序” 属性。CacheResolvercacheManager

cache-resolver

N/A(参见 CachingConfigurer javadoc)

A 使用配置的 .SimpleCacheResolvercacheManager

要用于解析后备缓存的缓存解析程序的 Bean 名称。 此属性不是必需的,只需指定为替代属性 “缓存管理器”属性。

key-generator

N/A(参见 CachingConfigurer javadoc)

SimpleKeyGenerator

要使用的自定义密钥生成器的名称。

error-handler

N/A(参见 CachingConfigurer javadoc)

SimpleCacheErrorHandler

要使用的自定义缓存错误处理程序的名称。默认情况下,在 与缓存相关的操作将回引发到客户端。

mode

mode

proxy

默认模式 () 使用 Spring 的 AOP 处理要代理的带注释的 bean 框架(遵循代理语义,如前所述,应用于方法调用 仅通过代理进入)。替代模式 () 相反,编织了 受影响的类与 Spring 的 AspectJ 缓存方面,修改目标类字节 要应用于任何类型的方法调用的代码。AspectJ 编织需要在类路径中以及启用装入时编织(或编译时编织)。(有关如何设置的详细信息,请参阅 Spring 配置 加载时编织。proxyaspectjspring-aspects.jar

proxy-target-class

proxyTargetClass

false

仅适用于代理模式。控制为哪种类型的缓存代理创建 使用 OR 注释注释的类。如果该属性设置为 ,则会创建基于类的代理。 如果是或省略属性,则为标准 JDK 创建基于接口的代理。(有关不同代理类型的详细检查,请参阅代理机制。@Cacheable@CacheEvictproxy-target-classtrueproxy-target-classfalse

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于用 或 注释的 Bean 的高速缓存建议的顺序。(有关与 订购 AOP 建议,请参阅建议订购。 没有指定的顺序意味着 AOP 子系统确定建议的顺序。@Cacheable@CacheEvict

2.7 使用自定义注释

自定义注释和AspectJ这一特性仅适用于基于代理的方法,但可以通过使用AspectJ进行一些额外的工作来启用。-
spring方面模块仅为标准注释定义一个方面。如果您已经定义了自己的注释,那么还需要为这些注释定义一个方面。有关示例,请检查AnnotationCheckAspect。

缓存抽象允许您使用自己的注释来确定触发缓存填充或逐出的方法。作为模板机制,这是非常方便的,因为它消除了重复缓存注释声明的需要,如果指定了键或条件,或者代码库中不允许外部导入(org.springframework),这尤其有用。与其他原型注释类似,您可以使用@Cacheable、@CachePut、@CacheEvect和@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)

下面的示例显示了可以替换前面代码的自定义注释:

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

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

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值