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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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 多克 多克创新科技企业店-淘宝网 全品优惠

                        目录
                        相关文章
                        |
                        19天前
                        |
                        运维 持续交付 云计算
                        深入解析云计算中的微服务架构:原理、优势与实践
                        深入解析云计算中的微服务架构:原理、优势与实践
                        52 1
                        |
                        12天前
                        |
                        Java 开发者 微服务
                        从单体到微服务:如何借助 Spring Cloud 实现架构转型
                        **Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
                        119 68
                        从单体到微服务:如何借助 Spring Cloud 实现架构转型
                        |
                        11天前
                        |
                        运维 监控 持续交付
                        微服务架构解析:跨越传统架构的技术革命
                        微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
                        113 36
                        微服务架构解析:跨越传统架构的技术革命
                        |
                        16天前
                        |
                        存储 Linux API
                        深入探索Android系统架构:从内核到应用层的全面解析
                        本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
                        |
                        18天前
                        |
                        弹性计算 持续交付 API
                        构建高效后端服务:微服务架构的深度解析与实践
                        在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
                        |
                        18天前
                        |
                        存储 缓存 监控
                        后端开发中的缓存机制:深度解析与最佳实践####
                        本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
                        |
                        17天前
                        |
                        负载均衡 Java 开发者
                        深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
                        深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
                        59 5
                        |
                        19天前
                        |
                        SQL 数据可视化 数据库
                        多维度解析低代码:从技术架构到插件生态
                        本文深入解析低代码平台,涵盖技术架构、插件生态及应用价值。通过图形化界面和模块化设计,低代码平台降低开发门槛,提升效率,支持企业快速响应市场变化。重点分析开源低代码平台的优势,如透明架构、兼容性与扩展性、可定制化开发等,探讨其在数据处理、功能模块、插件生态等方面的技术特点,以及未来发展趋势。
                        |
                        15天前
                        |
                        缓存 NoSQL Java
                        Spring Boot中的分布式缓存方案
                        Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
                        32 3
                        |
                        17天前
                        |
                        缓存 Java 数据库连接
                        深入探讨:Spring与MyBatis中的连接池与缓存机制
                        Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
                        34 4

                        推荐镜像

                        更多