大家好,欢迎来到Doker,这是一篇涉及到架构、框架的文章,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注释,容器也会在运行时自动获取其声明并理解其含义。注意,如前所述,需要启用注释驱动行为。
文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群你的支持和鼓励是我创作的动力❗❗❗
官网:Doker 多克; 官方旗舰店:首页-Doker 多克 多克创新科技企业店-淘宝网 全品优惠