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

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

 大家好,欢迎来到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){...}

            image.gif

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

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

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

            image.gif

            默认密钥生成

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

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

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

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

                  自定义密钥生成声明

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

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

                  image.gif

                  对于这种情况,@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)

                  image.gif

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

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

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

                  image.gif

                  默认的缓存解决方案

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

                  自定义缓存解决方案

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

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

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

                  image.gif

                  同步缓存

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

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

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

                  image.gif

                  条件缓存

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

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

                  image.gif

                  在@Cacheable上设置条件。

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

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

                  image.gif

                  使用“unless”属性阻止hardback

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

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

                  image.gif

                  2.2 注释@CachePut

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

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

                  image.gif

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

                  2.3 注释@CacheEvict

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

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

                  image.gif

                  使用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)

                  image.gif

                  2.5注释@CacheConfig

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

                  @CacheConfig("books") (1)
                  public class BookRepositoryImpl implements BookRepository {
                      @Cacheable
                      public Book findBook(ISBN isbn) {...}
                  }

                  image.gif

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

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

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

                    • 全局配置,可用于CacheManager和KeyGenerator。
                      • 在类级别,使用@CacheConfig。
                        • 在操作层面。

                        2.6启用缓存注释

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

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

                        @Configuration
                        @EnableCaching
                        public class AppConfig {
                        }

                        image.gif

                        表 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 {
                        }

                        image.gif

                        在前面的示例中,我们定义了自己的SlowService注释,该注释本身用@Cacheable注释。现在我们可以替换以下代码:

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

                        image.gif

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

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

                        image.gif

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

                        文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群你的支持和鼓励是我创作的动力❗❗❗

                        官网:Doker 多克; 官方旗舰店:首页-Doker 多克 多克创新科技企业店-淘宝网 全品优惠

                        目录
                        相关文章
                        |
                        20天前
                        |
                        Linux 编译器 开发者
                        Linux设备树解析:桥接硬件与操作系统的关键架构
                        在探索Linux的庞大和复杂世界时🌌,我们经常会遇到许多关键概念和工具🛠️,它们使得Linux成为了一个强大和灵活的操作系统💪。其中,"设备树"(Device Tree)是一个不可或缺的部分🌲,尤其是在嵌入式系统🖥️和多平台硬件支持方面🔌。让我们深入了解Linux设备树是什么,它的起源,以及为什么Linux需要它🌳。
                        Linux设备树解析:桥接硬件与操作系统的关键架构
                        |
                        30天前
                        |
                        安全 Java 数据安全/隐私保护
                        【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
                        【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
                        67 1
                        |
                        30天前
                        |
                        存储 XML 缓存
                        【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
                        【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
                        67 0
                        |
                        2天前
                        |
                        缓存 NoSQL Redis
                        深度解析Redis的缓存双写一致性
                        【4月更文挑战第20天】
                        13 1
                        |
                        3天前
                        |
                        canal 缓存 关系型数据库
                        Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
                        Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
                        |
                        3天前
                        |
                        XML 人工智能 Java
                        Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
                        Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
                        |
                        6天前
                        |
                        前端开发 Java
                        SpringBoot之三层架构的详细解析
                        SpringBoot之三层架构的详细解析
                        20 0
                        |
                        11天前
                        |
                        负载均衡 Java 开发者
                        细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
                        【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
                        |
                        15天前
                        |
                        XML Java 数据格式
                        Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
                        Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
                        21 0
                        Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道

                        热门文章

                        最新文章

                        推荐镜像

                        更多