vlambda博客
学习文章列表

读书笔记《spring-5-design-patterns》第 9 章使用缓存模式提高应用程序性能

第 9 章使用缓存模式提高应用程序性能

在前面的章节中,我们已经了解了 Spring 如何在后端访问应用程序的数据。我们还看到了 Spring JDBC 模块如何为数据库访问提供 JdbcTemplate 帮助类。 Spring 提供与 Hibernate、JPA、JDO 等 ORM 解决方案集成的支持,并跨应用程序管理事务。现在,在本章中,我们将看到 Spring 如何提供缓存支持来提高应用程序性能。

当您深夜从办公室回到家时,您是否曾经遇到过妻子的一连串问题?是的,我知道当你累了和筋疲力尽的时候回答这么多问题是很烦人的。当你一遍又一遍地被问到同样的问题时,更令人恼火..

有些问题可以用 YesNo 来回答,但是对于某些问题,您必须详细解释。考虑一下如果一段时间后您再次被问到另一个冗长的问题会发生什么!类似地,应用程序中有一些无状态组件,这些组件的设计方式使得它们一遍又一遍地询问相同的问题以单独完成每个任务。类似于你老婆问的一些问题,系统中的一些问题需要一段时间才能获取到合适的数据——它可能背后有一些主要的复杂逻辑,或者它必须从数据库中获取数据,或者调用远程服务。

如果我们知道某个问题的答案不太可能经常更改,那么我们可以记住该问题的答案,以备以后被同一系统再次询问时使用。通过相同的渠道再次获取它是没有意义的,因为它会影响您的应用程序的性能,并且会浪费您的资源。在企业应用程序中,缓存是一种存储那些经常需要的答案的方法,这样我们就可以从缓存中获取,而不是通过适当的渠道一遍又一遍地获取同一个问题的答案。在本章中,我们将讨论 Spring 的缓存抽象特性,以及 Spring 如何以声明方式支持缓存实现。它将涵盖以下几点:

  • What is a cache?
  • Where do we do this caching?
  • Understanding the cache abstraction
  • Enabling caching via the Proxy pattern
  • Declarative Annotation-based caching
  • Declarative XML-based caching
  • Configuring the cache storage
  • Implementing custom cache annotations
  • Caching best practices

让我们开始。

什么是缓存?


简单来说,cache 是一个内存块,我们在其中存储应用程序的预处理信息。在这种情况下,键值对存储(例如映射)可能是应用程序中的缓存。在 Spring 中,缓存是抽象和表示缓存的接口。 cache 接口提供了一些将对象放入缓存存储的方法,它可以从缓存存储中检索给定键,它可以为给定键更新缓存存储中的对象,它删除对象从给定键的缓存存储中。这个缓存接口提供了很多函数来操作缓存。

我们在哪里使用缓存?

我们在 method 总是为相同参数返回相同结果的情况下使用缓存。这种方法可以做任何事情,例如动态计算数据、执行数据库查询以及通过 RMI、JMS 和 Web 服务请求数据等等。必须从参数生成唯一键。那是缓存键。

理解缓存抽象


基本上,Java applications 中的缓存应用于 Java 方法,以减少缓存中可用相同信息的执行次数。这意味着,无论何时调用这些 Java 方法,缓存抽象都会根据给定的参数将缓存行为应用于这些方法。如果给定参数的信息已经在缓存中可用,则无需执行目标方法即可返回。如果缓存中没有所需的信息,则执行 target 方法,并将结果缓存并返回给调用者。缓存抽象还提供其他与缓存相关的操作,例如更新和/或删除缓存中的内容。当应用程序中的数据有时发生变化时,这些操作很有用。

Spring Framework 通过使用 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 为 Spring 应用程序提供缓存抽象接口。缓存需要使用实际存储来存储缓存数据。但是缓存抽象只提供缓存逻辑。它不提供任何物理存储来存储缓存的数据。因此,开发人员需要在应用程序中实现缓存的实际存储。如果您有一个分布式应用程序,那么您将需要相应地配置您的缓存提供程序。这取决于您的应用程序的用例。您可以为分布式应用程序跨节点制作相同数据的副本,也可以制作集中式缓存。

市场上有几个缓存提供程序,您可以根据您的应用程序要求使用它们。其中一些如下:

  • Redis
  • OrmLiteCacheClient
  • Memcached
  • In Memory Cache
  • Aws DynamoDB Cache Client
  • Azure Cache Client

要在应用程序中实现缓存抽象,您必须处理以下任务:

  • Caching declaration: This means that you have to recognize those methods in the application that need to be cached, and annotate these methods either with caching annotations, or you can use XML configuration by using Spring AOP
  • Cache configuration: This means that you have to configure the actual storage for the cached data--the storage where the data is stored and read from

现在让我们看看如何在 Spring 应用程序中启用 Spring 的缓存抽象。

通过代理模式启用缓存


您可以通过以下两种方式启用 Spring 的 cache 抽象:

  • Using Annotation
  • Using the XML namespace

Spring 通过使用 AOP 透明地将缓存应用于 Spring bean 的方法。 Spring applies 代理围绕 Spring bean 声明需要缓存的方法。此代理将缓存的动态行为添加到 Spring bean。下图说明了缓存行为:

读书笔记《spring-5-design-patterns》第 9 章使用缓存模式提高应用程序性能

在上图中,您可以看到 Spring 将 Proxy 应用于 AccountServiceImpl< /span> 类来添加缓存行为。 Spring 使用 GoF 代理模式在应用程序中实现 caching

让我们看看如何在 Spring 应用程序中启用此功能。

使用 Annotation 启用缓存代理

如您所知,Spring 提供 很多特性,但它们大多被禁用。您必须在使用前启用这些功能。如果您想在 your 应用程序中使用 Spring 的缓存抽象,则必须启用此功能。如果您使用 Java 配置,您可以通过将 @EnableCaching 注释添加到您的配置类之一来启用 Spring 的缓存抽象。以下配置类显示了 @EnableCaching 注释:

    package com.packt.patterninspring.chapter9.bankapp.config; 
 
    import org.springframework.cache.CacheManager; 
    import org.springframework.cache.annotation.EnableCaching; 
    import org.springframework.cache.concurrent.
      ConcurrentMapCacheManager; 
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.ComponentScan; 
    import org.springframework.context.annotation.Configuration; 
 
    @Configuration 
    @ComponentScan(basePackages=   
    {"com.packt.patterninspring.chapter9.bankapp"}) 
    @EnableCaching //Enable caching 
    public class AppConfig { 
    
     @Bean 
     public AccountService accountService() { ... } 
 
     //Declare a cache manager 
     @Bean 
     public CacheManager cacheManager() { 
         CacheManager cacheManager = new ConcurrentMapCacheManager(); 
         return cacheManager; 
    } 
   } 

在前面的Java配置文件中,我们在配置类AppConfig.java中添加了@EnableCaching注解;此注解指示 Spring Framework 为应用程序启用 Spring 缓存行为。

现在让我们看看如何使用 XML 配置启用 Spring 的缓存抽象。

使用 XML 命名空间启用缓存代理

如果您使用 XML 配置应用程序,则可以使用 <cache:annotation-driven> 元素 启用注释驱动的 caching来自 Spring的缓存命名空间,如下:

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> 
     
     <!-- Enable caching --> 
     <cache:annotation-driven /> 
     
     <context:component-scan base- package="com.packt.patterninspring.chapter9.bankapp"/> 
      
     <!-- Declare a cache manager --> 
     <bean id="cacheManager" class="org.springframework.cache.concurrent. ConcurrentMapCacheManager" /> 
   </beans> 
 

从前面的配置文件中可以看出,无论使用Java配置还是XML配置,注解@EnableCaching和命名空间<cache:annotation-driven> ; 通过使用触发 Spring 缓存注释的切入点创建切面来启用 Spring 的缓存抽象。

让我们看看如何使用 Spring 的缓存注解来定义缓存边界。

基于声明性注解的缓存


在 Spring 应用程序中,Spring 的 abstraction 提供了以下 Annotations 用于缓存声明:

  • @Cacheable: This indicates that before execution of the actual method, look at the return value of that method in the cache. If the value is available, return this cached value, if the value is not available, then invoke the actual method, and put the returned value into the cache.
  • @CachePut: This updates the cache without checking if the value is available or not. It always invokes the actual method.
  • @CacheEvict: This is responsible for triggering cache eviction.
  • @Caching: This is used for grouping multiple annotations to be applied on a method at once.
  • @CacheConfig: This indicates to Spring to share some common cache-related settings at the class level.

现在让我们仔细看看每个注释。

@Cacheable 注解

@Cacheable 标记一个用于缓存的方法。其结果存储在缓存中。对于具有相同参数的该方法的所有后续调用,它将使用键从缓存中获取数据。该方法将不会被执行。以下是 @Cacheable 属性:

  • value: This is the name of cache to use
  • key: This is the key for each cached data item
  • condition: This is a SpEL expression to evaluate true or false; if it is false, then the result of caching is not applied to the method call
  • unless: This too is a SpEL expression; if it is true, it prevents the return value from being put in the cache

您可以使用 SpEL 和方法的参数。让我们看看下面的代码,了解 @Cacheable 注解的最简单声明。它需要与该方法关联的缓存的名称。请参考以下代码:

    @Cacheable("accountCache ") 
    public Account findAccount(Long accountId) {...} 

在上述代码中,findAccount 方法使用 @Cacheable 注释进行了注释。这意味着此方法与缓存相关联。缓存的名称是 accountCache。每当为特定的 accountId 调用此方法时,都会检查缓存以查找给定 value >accountId。您还可以为缓存指定多个名称,如下所示:

    @Cacheable({"accountCache ", "saving-accounts"}) 
    public Account findAccount(Long accountId) {...} 

@CachePut 注释

如前所述,@Cacheable@CachePut 注解都有相同的目标,即填充缓存。但是它们的工作方式与 略有不同。 @CachePut 标记一个用于缓存的方法,其结果存储在缓存中。对于具有相同参数的该方法的每次调用,它总是调用实际方法而不检查该方法的返回值是否在缓存中可用。以下是 @CachePut 属性:

  • value: This is the name of the cache to use
  • key: This is the key for each cached data item
  • condition: This is a SpEL expression to evaluate true or false; if false, then the result of caching is not applied to the method call
  • unless: This is also a SpEL expression; if it is true, it prevents the return value from being put in the cache

您还可以将 SpEL 和方法的参数用于 @CachePut 注释。以下代码是 @CachePut 注解的最简单声明:

    @CachePut("accountCache ") 
    public Account save(Account account) {...} 

上述代码中,当调用save()时,会保存Account。然后将返回的 Account 放入 accountCache 缓存中。

如前所述,缓存由基于方法参数的方法填充。它实际上是一个默认的缓存键。在 @Cachable 注释的情况下,findAccount(Long accountId) 方法具有 accountId< /code> 作为参数,accountId 用作此方法的缓存键。但是在 @CachePut 注解的情况下,save() 的唯一参数是一个帐户。它用作缓存键。使用 Account 作为缓存键似乎不太好。在这种情况下,您需要缓存键是新保存的帐户的 ID,而不是帐户本身。因此,您需要自定义密钥生成行为。让我们看看如何自定义缓存键。

自定义缓存键

您可以通过 使用 @Cacheable 的 key 属性和 @CachePut 注释来自定义缓存键.缓存键由 SpEL 表达式派生,该表达式使用对象的属性作为以下代码片段中突出显示的键属性。让我们看看以下示例:

    @Cacheable(cacheNames=" accountCache ", key="#accountId") 
    public Account findAccount(Long accountId) 
 
    @Cacheable(cacheNames=" accountCache ", key="#account.accountId") 
    public Account findAccount(Account account) 
 
    @CachePut(value=" accountCache ", key="#account.accountId") 
    Account save(Account account); 

您可以在前面的代码片段中看到我们是如何使用 @Cacheable 注释的 key 属性创建缓存键的。

让我们看看 Spring 应用程序中这些注解的另一个属性。

条件缓存

Spring 的缓存注释允许您在某些情况下通过 使用 @Cacheable@CachePut 的条件属性关闭缓存 注释。这些被赋予一个 SpEL 表达式来评估条件值。如果条件表达式的值为真,则缓存该方法。如果条件表达式的值为false,则该方法不被缓存,而是每次都执行而不执行任何缓存操作,无论缓存中的值是什么或使用什么参数。让我们看一个例子。仅当传递的参数的值大于或等于 2000 时,才会缓存以下方法:

    @Cacheable(cacheNames="accountCache", condition="#accountId >=   
    2000") 
    public Account findAccount(Long accountId); 

@Cacheable@CachePut 注释还有一个属性——除非< /代码>。这也给出了一个 SpEL 表达式。此属性可能看起来与条件属性相同,但它们之间存在一些差异。与条件不同,unless 表达式是在方法被调用后计算的。它可以防止将值放入缓存中。我们看下面的例子——我们只想在bank name不包含HDFC时缓存:

    @Cacheable(cacheNames="accountCache", condition="#accountId >= 
    2000", unless="#result.bankName.contains('HDFC')") 
    public Account findAccount(Long accountId); 

正如您在前面的代码片段中看到的,我们使用了两个属性——conditionunless。但是 unless 属性的 SpEL 表达式为 #result.bankName.contains('HDFC')。在此表达式中,结果是 SpEL 扩展或缓存 SpEL 元数据。以下是 SpEL 中可用的缓存元数据列表:

表达式

说明

#root.methodName

缓存方法的名称

#root.method

缓存的方法,即被调用的方法

#root.target

它评估正在调用的目标对象

#root.targetClass

它评估被调用的目标对象的类

#root.caches

执行当前方法的缓存数组

#root.args

传入缓存方法的参数数组

#result

缓存方法的返回值;仅在 @CachePut 的除非表达式中可用

笔记

Spring 的 @CachePut@Cacheable 注解不应该用在同一个方法上,因为它们有不同的行为。 @CachePut 注释强制执行缓存方法以更新缓存。但是 @Cacheable 注解只有在方法的返回值在缓存中不可用的情况下才会执行缓存的方法。

您已经了解了如何在 Spring 应用程序中使用 Spring 的 @CachePut@Cacheable 注释向缓存中添加信息。但是我们如何才能从缓存中删除这些信息呢? Spring 的缓存抽象提供了另一个用于从缓存中移除缓存数据的注解——@CacheEvict 注解。让我们看看如何使用 @CacheEvict 注释从缓存中删除缓存的数据。

@CacheEvict 注释

Spring 的缓存抽象不仅允许填充缓存,还允许从缓存中删除缓存的数据。在应用程序中有一个阶段,您必须从缓存中删除陈旧或未使用的数据。在这种情况下,您可以使用 @CacheEvict 注释,因为它不像 @Cacheable 那样不会向缓存添加任何内容注解。 @CacheEvict 注解仅用于执行缓存驱逐。让我们看看这个注解如何使 AccountRepositoryremove() 方法成为缓存驱逐:

    @CacheEvict("accountCache ") 
    void remove(Long accountId); 

正如您在前面的代码片段中看到的,与参数 accountId 关联的 value 已从 调用 remove() 方法时的 accountCache 缓存。以下是 @Cacheable 属性:

  • value: This is an array of names of the cache to use
  • key: This is a SpEL expression to evaluate the cache key to be used
  • condition: This is a SpEL expression to evaluate true or false; if it is false, then the result of caching is not being applied to the method call
  • allEntries: This implies that if the value of this attribute is true, all entries will be removed from the caches
  • beforeInvocation: This means that if the value of this attribute is true, the entries are removed from the cache before the method is invoked, and if the value of this attribute is false (the default), the entries are removed after a successful method invocation

笔记

我们可以在任何方法上使用 @CacheEvict 注释,甚至是 void 方法,因为它只会从缓存中删除值。但是对于 @Cacheable@CachePut 注释,我们必须使用非 void 返回值方法,因为这些注释需要缓存结果。

@Caching 注解

Spring 的缓存抽象允许您通过在 Spring 应用程序中使用 @Caching 注释来使用多个相同类型的 注释 来缓存方法。 @Caching 注解将其他注解分组,例如 @Cacheable@CachePut , 和 @CacheEvict 用于相同的方法。例如:

    @Caching(evict = {  
      @CacheEvict("accountCache "),  
      @CacheEvict(value="account-list", key="#account.accountId") }) 
      public List<Account> findAllAccount(){ 
      return (List<Account>) accountRepository.findAll(); 
   } 

@CacheConfig 注释

Spring 的缓存抽象允许 you 在类级别注解 @CacheConfig 以避免在每个方法中重复提及。在某些情况下,将缓存的自定义应用于所有方法可能非常乏味。在这里,您可以对类的所有操作使用 @CacheConfig 注解。例如:

     @CacheConfig("accountCache ") 
     public class AccountServiceImpl implements AccountService { 
 
      @Cacheable 
      public Account findAccount(Long accountId) { 
        return (Account) accountRepository.findOne(accountId); 
      } 
    } 

您可以在前面的代码片段中看到,@CacheConfig 注解用于类级别,它允许您共享 accountCache 使用所有 cacheable 方法进行缓存。

笔记

由于 Spring 的缓存抽象模块使用代理,因此您应该仅将缓存注释与公共可见性方法一起使用。在所有非公共方法中,这些注释不会引发任何错误,但使用这些注释注释的非公共方法不会显示任何缓存行为。

我们已经看到 Spring 还提供 XML 命名空间来在 Spring 应用程序中配置和实现缓存。让我们在下一节中看看如何。

声明式基于 XML 的缓存


为了使您的缓存配置代码与业务代码分开,并保持Spring特定注解和源代码之间的松散耦合,基于XML的缓存配置比基于注解的配置要优雅得多.因此,要使用 XML 配置 Spring 缓存,让我们使用缓存命名空间和 AOP 命名空间,因为缓存是 AOP 活动,它使用声明式缓存行为背后的代理模式。

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> 
    
     <!-- Enable caching --> 
     <cache:annotation-driven /> 
    
     <!-- Declare a cache manager --> 
     <bean id="cacheManager"class="org.springframework.cache. concurrent.ConcurrentMapCacheManager" /> 
    </beans> 

您可以在前面的 XML 文件中看到,我们已经包含了 cacheaop 命名空间。缓存命名空间使用以下元素定义缓存配置:

XML 元素

缓存说明

<cache:annotation-driven>

相当于Java配置中的@EnableCaching,用于开启Spring的缓存行为。

<cache:advice>

它定义了缓存建议

<cache:caching>

它相当于 @Caching 注解,用于在缓存通知中对一组缓存规则进行分组

<cache:cacheable>

相当于@Cacheable注解;它使任何方法都可缓存

<cache:cache-put>

它相当于 @CachePut 注解,用于填充缓存

<cache:cache-evict>

它相当于 @CacheEvict 注解,用于缓存驱逐。

 

让我们看看以下基于 XML 配置的示例。

创建配置文件,spring.xml如下:

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> 
    
   <context:component-scan base- package="com.packt.patterninspring.chapter9.bankapp.service, com.packt.patterninspring.chapter9.bankapp.repository"/> 
 
    <aop:config> 
    <aop:advisor advice-ref="cacheAccount" pointcut="execution(* com.packt.patterninspring.chapter9.bankapp.service.*.*(..))"/> 
   </aop:config> 
    
   <cache:advice id="cacheAccount"> 
     <cache:caching> 
       <cache:cacheable cache="accountCache" method="findOne" /> 
         <cache:cache-put cache="accountCache" method="save" key="#result.id" /> 
         <cache:cache-evict cache="accountCache" method="remove" /> 
         </cache:caching> 
      </cache:advice> 
 
   <!-- Declare a cache manager --> 
   <bean id="cacheManager" class="org.springframework.cache.concurrent. ConcurrentMapCacheManager" /> 
   </beans> 

在前面的 XML 配置文件中,突出显示的代码是 Spring 缓存配置。在缓存配置中,首先看到的是声明的 <aop:config> 然后是 <aop:advisor>,它引用了 ID 为 cacheAccount 的通知,并且还有一个切入点表达式来匹配通知。建议使用 <cache:advice> 元素声明。这个元素可以有很多 <cache:caching> 元素。但是,在我们的示例中,我们只有一个 <cache:caching> 元素,它有一个 <cache:cacheable> 元素,一个 <cache:cache-put> 和一个 <cache:cache-evict> 元素;每个都将切入点中的方法声明为可缓存的。

让我们看一下带有缓存注解的应用程序的 Service 类:

    package com.packt.patterninspring.chapter9.bankapp.service; 
 
    import org.springframework.beans.factory.annotation.Autowired; 
    import org.springframework.cache.annotation.CacheEvict; 
    import org.springframework.cache.annotation.CachePut; 
    import org.springframework.cache.annotation.Cacheable; 
    import org.springframework.stereotype.Service; 
 
    import com.packt.patterninspring.chapter9.bankapp.model.Account; 
    import com.packt.patterninspring.chapter9.
    bankapp.repository.AccountRepository; 
 
    @Service 
    public class AccountServiceImpl implements AccountService{ 
    
    @Autowired 
    AccountRepository accountRepository; 
 
    @Override 
    @Cacheable("accountCache") 
    public Account findOne(Long id) { 
      System.out.println("findOne called"); 
      return accountRepository.findAccountById(id); 
    } 
 
    @Override 
    @CachePut("accountCache") 
    public Long save(Account account) { 
      return accountRepository.save(account); 
    } 
 
    @Override 
    @CacheEvict("accountCache") 
    public void remove(Long id) { 
      accountRepository.findAccountById(id); 
    } 
    
   } 

在前面的文件定义中,我们使用了 Spring 的缓存注解在应用程序中创建缓存。现在让我们看看如何在应用程序中配置缓存存储。

配置缓存存储


Spring 的缓存抽象提供了大量的storage 集成。 Spring 为每个内存存储提供 CacheManager。您可以使用应用程序配置 CacheManager。然后 CacheManager 负责控制和管理缓存。让我们探索如何在应用程序中设置 CacheManager

设置缓存管理器

您必须在应用程序中指定一个缓存管理器进行存储,并将一些 cache 提供程序赋予 CacheManager,或者您可以编写自己的 CacheManager。 Spring 在 org.springframework.cache 包中提供了几个缓存管理器,例如 ConcurrentMapCacheManager,它创建了一个 ConcurrentHashMap

    @Bean 
    public CacheManager cacheManager() { 
      CacheManager cacheManager = new ConcurrentMapCacheManager(); 
      return cacheManager; 
    }

SimpleCacheManagerConcurrentMapCacheManager等是Spring Framework缓存抽象的缓存管理器。但是 Spring 提供了与第三方缓存管理器集成的支持,我们将在下一节中看到。

第三方缓存实现


Spring 的 SimpleCacheManager 可以用于测试,但没有 cache 控制选项(溢出、驱逐)。所以我们必须使用第三方替代品,如下所示:

  • Terracotta's EhCache
  • Google's Guava and Caffeine
  • Pivotal's Gemfire

让我们看一下第三方缓存管理器的配置之一。

基于 Ehcache 的缓存

Ehcache 流行的缓存提供程序之一。 Spring 允许您通过在应用程序中配置 EhCacheCacheManager 来与 Ehcache 集成。以 以下 Java 配置为例:

    @Bean 
    public CacheManager cacheManager(CacheManager ehCache) { 
      EhCacheCacheManager cmgr = new EhCacheCacheManager(); 
      cmgr.setCacheManager(ehCache); 
      return cmgr; 
    } 
    @Bean  
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() { 
      EhCacheManagerFactoryBean eh = new EhCacheManagerFactoryBean(); 
      eh.setConfigLocation(new  
      ClassPathResource("resources/ehcache.xml")); 
      return eh; 
    } 

上述代码中,bean方法cacheManager()创建了一个EhCacheCacheManager的对象,并用CacheManager。在这里,Ehcache 的 CacheManager 被注入到 Spring 的 EhCacheCacheManager 中。第二个 bean 方法 ehCacheManagerFactoryBean() 创建并返回 EhCacheManagerFactoryBean 的实例。因为它是一个工厂 bean,它会返回一个 CacheManager 的实例。 XML 文件 ehcache.xml 具有 Ehcache 配置。 ehcache.xml我们参考如下代码:

    <ehcache> 
       <cache name="accountCache" maxBytesLocalHeap="50m" timeToLiveSeconds="100"> 
       </cache> 
    </ehcache> 

ehcache.xml 文件的内容因应用程序而异,但您至少需要声明一个最小缓存。例如,以下 Ehcache 配置声明了一个名为 accountCache 的缓存,具有 50 MB 的最大堆存储空间和 100 秒的生存时间:

基于 XML 的配置

让我们为 Eache 创建基于 XML 的 configuration,它在此处配置 EhCacheCacheManager。请参考以下代码:

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/> 
 
    <!-- EhCache library setup --> 
    <bean id="ehcache" class="org.springframework.cache.ehcache. EhCacheManagerFactoryBean" p:config- location="resources/ehcache.xml"/> 

同样,在 XML 配置的情况下,您必须为 ehcache 配置缓存管理器,配置 EhCacheManagerFactoryBean 类,并将 config-location 值设置为 ehcache.xml,其中包含上一节中定义的 Ehcache 配置。

还有更多第三方缓存存储支持与 Spring Framework 的集成。在本章中,我只讨论了 ECache 管理器。

在下一节中,我们将讨论 Spring 如何允许您创建自己的自定义注解以进行缓存。

创建自定义缓存注释


Spring 的缓存抽象允许您为应用程序创建自定义 caching 注释,以识别缓存填充或缓存驱逐的缓存方法。 Spring 的 @Cacheable@CacheEvict 注解用作 Meta 注解来创建自定义缓存注解。让我们看一下应用程序中自定义注释的以下代码:

    @Retention(RetentionPolicy.RUNTIME) 
    @Target({ElementType.METHOD}) 
    @Cacheable(value="accountCache", key="#account.id") 
    public @interface SlowService { 
    } 

在前面的代码片段中,我们定义了一个名为 SlowService 的自定义注解,使用 Spring 的 @Cacheable 注解进行注解。如果我们在应用程序中使用 @Cacheable ,那么我们必须将其配置为如下代码:

    @Cacheable(value="accountCache", key="#account.id") 
    public Account findAccount(Long accountId) 

让我们用我们定义的自定义注解替换前面的配置,代码如下:

    @SlowService 
    public Account findAccount(Long accountId) 

如您所见,我们仅使用 @SlowService 注释来使方法在应用程序中可缓存。

现在让我们进入下一部分,我们将在其中看到在应用程序中实现缓存时应该考虑哪些最佳实践。

在 Web 应用程序中使用的顶级缓存最佳实践


在您的企业 Web 应用程序中,正确使用 caching 可以非常快速地呈现网页,最大限度地减少数据库命中,并减少服务器资源(如内存、网络等)的消耗。缓存是一种非常强大的技术,可以通过将陈旧数据存储在缓存中来提高应用程序的性能。以下是在设计和开发 Web 应用程序时应该考虑的最佳实践:

  • In your Spring web application, Spring's cache annotations such as @Cacheable, @CachePut, and @CacheEvict should be used on concrete classes instead of application interfaces. However, you can annotate the interface method as well, using interface-based proxies. Remember that Java annotations are not inherited from interfaces, which means that if you are using class-based proxies by setting the attribute proxy-target-class="true", then Spring cache annotations are not recognized by the proxying.
  • If you have annotated any method with the @Cacheable, @CachePut, or @CacheEvict annotations, then never call it directly by another method of the same class if you want to benefit from the cache in the application. This is because in direct calling of a cached method, the Spring AOP proxy is never applied.
  • In an enterprise application, Java Maps or any key/value collections should never be used as a Cache. Any key/value collection cannot be a Cache. Sometimes, developers use java map as a custom caching solution, but it is not a caching solution, because Cache provides more than a key/value storage, like the following:
    • Cache provides eviction policies
    • You can set the max size limit of Cache
    • Cache provides a persistent store
    • Cache provides weak reference keys
    • Cache provides statistics
      • The Spring Framework provides the best declarative approach to implement and configure the Cache solution in an application. So, always use the cache abstraction layer--it provides flexibility in the application. We know that the @Cacheable annotation allows you to separate business logic code from the caching cross-cutting concern.
      • Be careful whenever you use cache in the application. Always use cache in a place where it is actually required such as a web service or an expensive database call, because every caching API has an overhead.
      • At the time of cache implementation in an application, you have to ensure that the data in the cache is in sync with the data storage. You can use distributed cache managers like Memcached for proper cache strategy implementation to provide considerable performance.
      • You should use cache only as second option if data fetching is very difficult from the database because of slow database queries. It is because, whenever we use caching behavior in the application, first the value is checked in the cache if not available then it execute actual method, so it would be unnecessary.
  • 在本章中,我们看到了缓存如何帮助提高应用程序的性能。缓存主要作用于应用程序的服务层。在您的应用程序中,有一个方法返回的数据;如果应用程序代码根据相同的需求一遍又一遍地调用它,我们可以缓存该数据。缓存是避免针对相同需求执行应用程序方法的好方法。每当第一次调用此方法时,特定参数的方法的返回值都会存储在缓存中。对于相同参数的相同方法的进一步调用,将从该缓存中检索该值。缓存通过避免针对相同答案(例如执行数据库查询)的一些资源和时间消耗操作来提高应用程序性能。

概括


Spring 提供缓存管理器来管理 Spring 应用程序中的缓存。在本章中,您已经了解了如何为特定的缓存技术定义缓存管理器。 Spring为缓存提供了一些注解如@Cacheable@CachePut、< /em>@CacheEvict,我们可以在 Spring 应用程序中使用它们。我们还可以使用 XML 配置在 Spring 应用程序中配置缓存。 Spring 框架提供了缓存命名空间来实现这一点。 <cache:cacheable><cache:cache-put> <cache:cache-evict> 元素被用来代替相应的注解。

Spring 使用面向方面的编程使管理应用程序中的缓存成为可能。缓存是 Spring Framework 的一个横切关注点。这意味着,缓存是 Spring 应用程序的一个方面。 Spring 通过使用 Spring AOP 模块的环绕通知来实现缓存。

在接下来的第 10 章,使用 Spring 在 Web 应用程序中实现 MVC 模式 ,我们将探讨如何在 Web 层和 MVC 模式中使用 Spring。