【转】Spring Cache简介

从3.1开始, Spring引入了对 Cache的支持。其使用方法和原理都类似于 Spring对事务管理的支持。 Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用 Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。

​ 使用 Spring Cache需要我们做两方面的事:

  • 声明某些方法使用缓存

  • 配置 SpringCache的支持

​ 和 Spring对事务管理的支持一样, SpringCache的支持也有基于注解和基于 XML配置两种方式。下面我们先来看看基于注解的方式。

Spring为我们提供了几个注解来支持 Spring Cache。其核心主要是 @Cacheable@CacheEvict。使用 @Cacheable标记的方法在执行后 Spring Cache将缓存其返回结果,而使用 @CacheEvict标记的方法会在方法执行前或者执行后移除 Spring Cache中的某些元素。下面我们将来详细介绍一下 Spring基于注解对 Cache的支持所提供的几个注解。

1.1 @Cacheable

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。 @Cacheable可以指定三个属性, valuekeycondition

value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个 Cache上的,对应 Cache的名称。其可以是一个 Cache也可以是多个 Cache,当需要指定多个 Cache时其是一个数组。

@Cacheable("cache1")//Cache是发生在cache1上的
 public User find(Integer id) {
  return null;
 }

 @Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
 public User find(Integer id) {
  return null;
 }

key属性是用来指定 Spring缓存方法的返回结果时对应的 key的。该属性支持 SpringEL表达式。当我们没有指定该属性时, Spring将使用默认策略生成 key。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。

​ 自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用”#参数名”或者”#p参数index”。下面是几个使用参数作为key的示例。

  @Cacheable(value="users", key="#id")
  public User find(Integer id) {
   return null;
  }

  @Cacheable(value="users", key="#p0")
  public User find(Integer id) {
   return null;
  }

  @Cacheable(value="users", key="#user.id")
  public User find(User user) {
   return null;
  }

  @Cacheable(value="users", key="#p0.id")
  public User find(User user) {
   return null;
  }

​ 除了上述使用方法参数作为 key之外, Spring还为我们提供了一个 root对象可以用来生成 key。通过该root对象我们可以获取到以下信息。

当前方法名

当前方法

当前被调用的对象

当前被调用的对象的class

当前方法参数组成的数组

当前被调用的方法使用的Cache

​ 当我们要使用 root对象的属性作为 key时我们也可以将 #root省略,因为 Spring默认使用的就是 root对象的属性。如:

  @Cacheable(value={"users", "xxx"}, key="caches[1].name")
  public User find(User user) {
   return null;
  }

​ 有的时候我们可能并不希望缓存一个方法所有的返回结果。通过 condition属性可以实现这一功能。 condition属性默认为空,表示将缓存所有的调用情形。其值是通过 SpringEL表达式来指定的,当为 true时表示进行缓存处理;当为 false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。

@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
 System.out.println("find user by user " + user);
 return user;
}

1.2 @CachePut

​ 在支持 Spring Cache的环境下,对于使用 @Cacheable标注的方法, Spring在每次执行前都会检查 Cache中是否存在相同 key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。 @CachePut也可以声明一个方法支持缓存功能。与 @Cacheable不同的是使用 @CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CachePut也可以标注在类上和方法上。使用 @CachePut时我们可以指定的属性跟 @Cacheable是一样的。

@CachePut("users")//每次都会执行方法,并将结果存入指定的缓存中
  public User find(Integer id) {
   return null;
  }

1.3 @CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。 @CacheEvict可以指定的属性有 valuekeyconditionallEntriesbeforeInvocation。其中 valuekeycondition的语义与 @Cacheable对应的属性类似。即 value表示清除操作是发生在哪些 Cache上的(对应 Cache的名称); key表示需要清除的是哪个 key,如未指定则会使用默认策略生成的 keycondition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性 allEntriesbeforeInvocation

allEntriesboolean类型,表示是否需要清除缓存中的所有元素。默认为 false,表示不需要。当指定了 allEntriestrue时, Spring Cache将忽略指定的 key。有的时候我们需要 Cache一下清除所有的元素,这比一个一个清除元素更有效率。

 @CacheEvict(value="users", allEntries=true)
  public void delete(Integer id) {
   System.out.println("delete user by id: " + id);
 }

​ 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用 beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为 true时, Spring会在调用该方法之前清除缓存中的指定元素。

  @CacheEvict(value="users", beforeInvocation=true)
  public void delete(Integer id) {
   System.out.println("delete user by id: " + id);
  }

​ 其实除了使用 @CacheEvict清除缓存元素外,当我们使用 Ehcache作为实现时,我们也可以配置 Ehcache自身的驱除策略,其是通过 Ehcache的配置文件来指定的。由于 Ehcache不是本文描述的重点,这里就不多赘述了,想了解更多关于 Ehcache的信息,请查看我关于Ehcache的专栏

1.4 @Caching

@Caching注解可以让我们在一个方法或者类上同时指定多个 Spring Cache相关的注解。其拥有三个属性: cacheableputevict,分别用于指定 @Cacheable@CachePut@CacheEvict

@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
    return null;
}

1.5 使用自定义注解

Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用 @Cacheable进行标注的自定义注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}

​ 那么在我们需要缓存的方法上使用 @MyCacheable进行标注也可以达到同样的效果。

@MyCacheable
public User findById(Integer id) {
    System.out.println("find user by id: " + id);
    User user = new User();
    user.setId(id);
    user.setName("Name" + id);
    return user;
  }

2.1 声明对Cache的支持

配置 Spring对基于注解的 Cache的支持,首先我们需要在 Spring的配置文件中引入 cache命名空间,其次通过

<cache:annotation-driven></cache:annotation-driven>就可以启用 Spring对基于注解的 Cache的支持。


<cache:annotation-driven></cache:annotation-driven>有一个 cache-manager属性用来指定当前所使用的 CacheManager对应的 bean的名称,默认是 cacheManager,所以当我们的 CacheManager的id为 cacheManager时我们可以不指定该参数,否则就需要我们指定了。

<cache:annotation-driven></cache:annotation-driven>还可以指定一个 mode属性,可选值有 proxyaspectj。默认是使用 proxy

​ 此外, <cache:annotation-driven></cache:annotation-driven>还可以指定一个 proxy-target-class属性,表示是否要代理 class,默认为 false。我们前面提到的 @Cacheable@cacheEvict等也可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置 proxy-target-classtrue或者 modeaspectj时,是直接基于 class进行操作的,定义在接口上的 @CacheableCache注解不会被识别到,那对应的 Spring Cache也不会起作用了。

​ 需要注意的是 <cache:annotation-driven></cache:annotation-driven>只会去寻找定义在同一个 ApplicationContext下的 @Cacheable等缓存注解。

​ 除了使用注解来声明对 Cache的支持外, Spring还支持使用 XML来声明对 Cache的支持。这主要是通过类似于 aop:advicecache:advice来进行的。在 cache命名空间下定义了一个 cache:advice元素用来定义一个对于 Cacheadvice。其需要指定一个 cache-manager属性,默认为 cacheManagercache:advice下面可以指定多个 cache:caching元素,其有点类似于使用注解时的 @Caching注解。 cache:caching元素下又可以指定 cache:cacheablecache:cache-putcache:cache-evict元素,它们类似于使用注解时的 @Cacheable@CachePut@CacheEvict。下面来看一个示例:


​ 上面配置定义了一个名为 cacheAdvicecache:advice,其中指定了将缓存 findById方法和 find方法到名为 users的缓存中。这里的方法还可以使用通配符 *,比如 find*表示任何以 find开始的方法。

​ 有了 cache:advice之后,我们还需要引入 aop命名空间,然后通过 aop:config指定定义好的 cacheAdvice要应用在哪些 pointcut上。如:


​ 上面的配置表示在调用 com.xxx.UserService中任意公共方法时将使用 cacheAdvice对应的 cache:advice来进行 Spring Cache处理。更多关于 Spring Aop的内容不在本文讨论范畴内。

2.2 配置CacheManager

CacheManagerSpring定义的一个用来管理 Cache的接口。 Spring自身已经为我们提供了两种 CacheManager的实现,一种是基于 Java APIConcurrentMap,另一种是基于第三方 Cache实现——Ehcache,如果我们需要使用其它类型的缓存时,我们可以自己来实现 SpringCacheManager接口或 AbstractCacheManager抽象类。下面分别来看看 Spring已经为我们实现好了的两种 CacheManager的配置示例。


​ 上面的配置使用的是一个 SimpleCacheManager,其中包含一个名为 xxxConcurrentMapCache


​ 上面的配置使用了一个 Spring提供的 EhCacheCacheManager来生成一个 SpringCacheManager,其接收一个 EhcacheCacheManager,因为真正用来存入缓存数据的还是 EhcacheEhcacheCacheManager是通过 Spring提供的 EhCacheManagerFactoryBean来生成的,其可以通过指定 ehcache的配置文件位置来生成一个 EhcacheCacheManager。若未指定则将按照 Ehcache的默认规则取 classpath根路径下的 ehcache.xml文件,若该文件也不存在,则获取 Ehcache对应 jar包中的 ehcache-failsafe.xml文件作为配置文件。更多关于 Ehcache的内容这里就不多说了,它不属于本文讨论的内容,欲了解更多关于 Ehcache的内容可以参考我之前发布的Ehcache系列文章,也可以参考官方文档等。

​ 键的生成策略有两种,一种是默认策略,一种是自定义策略。

3.1 默认策略

​ 默认的 Key生成策略是通过 KeyGenerator生成的,其默认策略如下:

  • 如果方法没有参数,则使用 0作为 key
  • 如果只有一个参数的话则使用该参数作为 key
  • 如果参数多余一个的话则使用所有参数的 hashCode作为 key

​ 如果我们需要指定自己的默认策略的话,那么我们可以实现自己的 KeyGenerator,然后指定我们的 Spring Cache使用的 KeyGenerator为我们自己定义的 KeyGenerator

​ 使用基于注解的配置时是通过 cache:annotation-driven指定的.


而使用基于 XML配置时是通过 cache:advice来指定的。


需要注意的是此时我们所有的 Cache使用的 Key的默认生成策略都是同一个 KeyGenerator

3.2 自定义策略

​ 自定义策略是指我们可以通过 SpringEL表达式来指定我们的 key。这里的 EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用 #&#x53C2;&#x6570;&#x540D;或者 #p[i]。下面是几个使用参数作为 key的示例。

  @Cacheable(value="users", key="#id")
  public User find(Integer id) {
   return null;
  }

  @Cacheable(value="users", key="#p0")
  public User find(Integer id) {
   return null;
  }

  @Cacheable(value="users", key="#user.id")
  public User find(User user) {
   return null;
  }

  @Cacheable(value="users", key="#p0.id")
  public User find(User user) {
   return null;
  }

​ 除了上述使用方法参数作为 key之外, Spring还为我们提供了一个 root对象可以用来生成 key。通过该 root对象我们可以获取到以下信息。

当前方法名

当前方法

当前被调用的对象

当前被调用的对象的class

当前方法参数组成的数组

当前被调用的方法使用的Cache

​ 当我们要使用 root对象的属性作为 key时我们也可以将 #root省略,因为 Spring默认使用的就是 root对象的属性。如:

@Cacheable(value={"users", "xxx"}, key="caches[1].name")
  public User find(User user) {
   return null;
  }

​ 前面介绍的内容是 Spring内置的对 Cache的支持,其实我们也可以通过 Spring自己单独的使用 EhcacheCacheManagerEhcache对象。通过在 Application Context中配置 EhCacheManagerFactoryBeanEhCacheFactoryBean,我们就可以把对应的 EhCacheCacheManagerEhcache对象注入到其它的 Spring bean对象中进行使用。

4.1 EhCacheManagerFactoryBean

EhCacheManagerFactoryBeanSpring内置的一个可以产生 EhcacheCacheManager对象的 FactoryBean。其可以通过属性 configLocation指定用于创建 CacheManagerEhcache配置文件的路径,通常是ehcache.xml文件的路径。如果没有指定 configLocation,则将使用默认位置的配置文件创建 CacheManager,这是属于 Ehcache自身的逻辑,即如果在 classpath根路径下存在 ehcache.xml文件,则直接使用该文件作为 Ehcache的配置文件,否则将使用 ehcache-xxx.jar中的 ehcache-failsafe.xml文件作为配置文件来创建 EhcacheCacheManager。此外,如果不希望创建的 CacheManager使用默认的名称(在 ehcache.xml文件中定义的,或者是由 CacheManager内部定义的),则可以通过 cacheManagerName属性进行指定。下面是一个配置 EhCacheManagerFactoryBean的示例。


4.2 EhCacheFactoryBean

EhCacheFactoryBean是用来产生 EhcacheEhcache对象的 FactoryBean。定义 EhcacheFactoryBean时有两个很重要的属性我们可以来指定。一个是 cacheManager属性,其可以指定将用来获取或创建 EhcacheCacheManager对象,若未指定则将通过 CacheManager.create()获取或创建默认的 CacheManager。另一个重要属性是 cacheName,其表示当前 EhCacheFactoryBean对应的是 CacheManager中的哪一个 Ehcache对象,若未指定默认使用 beanName作为 cacheName。若 CacheManager中不存在对应 cacheNameEhcache对象,则将使用 CacheManager创建一个名为 cacheNameCache对象。此外我们还可以通过 EhCacheFactoryBeantimeToIdletimeToLive等属性指定要创建的 Cache的对应属性,注意这些属性只对 CacheManager中不存在对应 Cache时新建的 Cache才起作用,对已经存在的 Cache将不起作用,更多属性设置请参考 SpringAPI文档。此外还有几个属性是对不管是已经存在还是新创建的 Cache都起作用的属性: statisticsEnabledsampledStatisticsEnableddisabledblockingcacheEventListeners,其中前四个默认都是 false,最后一个表示为当前 Cache指定 CacheEventListener。下面是一个定义 EhCacheFactoryBean的示例。


(注:本文是基于Spring3.1.0所写)

Original: https://www.cnblogs.com/yw0219/p/15707673.html
Author: 舒山
Title: 【转】Spring Cache简介

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/539784/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • ArrayList扩容机制

    写在前面 数据结构在面试中基本都是常问的,但是一般开发中基本上都是在使用没有去关心过怎么实现的。 在数据结构中,链表是一种线性存储结构,也就是常说的线性表。 概念:线性表,是数据结…

    Java 2023年6月5日
    095
  • 初踩坑JS加载与audio接口:点击头像开始/暂停背景音乐

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月5日
    071
  • 无题

    你是否曾有过一张照片,带入云雾弥漫的梦里? 是否有过一首歌,是深夜辗转反侧的陪伴? 有些人注定只能陪你走过那最艰难的岁月,却不能在阳光正好微分不燥的时空出现。 那些记忆深处的回忆让…

    Java 2023年6月16日
    075
  • Nginx: 解决反代时,超过1分钟Gateway Timeout 504问题

    打开Nginx的配置文件中,在对应的反代域名下,添加 proxy_connect_timeout 300;proxy_send_timeout 300;proxy_read_tim…

    Java 2023年5月30日
    093
  • clash TUN模式

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月16日
    0100
  • thymeleaf在不同session,request作用域的使用

    参考: https://blog.csdn.net/weixin_59668801/article/details/124421911 Original: https://www….

    Java 2023年6月6日
    077
  • 设计模式之中介者模式

    在我们实际业务中,可能存在多个类之间相互调用,形成了一个复杂的网状结构。这时候就需要有一种模式去”捋顺”他们之间的关系,引出一个中间者让类之间不再相互调用,…

    Java 2023年6月8日
    085
  • Java连载153-可变参数、多线程状态图

    一、可变参数 可变参数顾名思义就是,编写一个函数的时候,可以传入一个或者多个参数 实际原理:根据传入的参数类型以及个数,创建一个数组,用于存储这些数据,在函数中调用的时候,也可以使…

    Java 2023年6月13日
    0110
  • Java内部类初探

    Java内部类初探 之前对内部类的概念不太清晰,在此对内部类与外部类之间的关系以及它们之间的调用方式进行一个总结。 Java内部类一般可以分为以下三种: 成员内部类 静态内部类 匿…

    Java 2023年6月8日
    093
  • 8、线程休眠

    8、线程休眠 每个对象都有一把锁,sleep不会释放锁; package com.testthread1; public class TestThread3 implements …

    Java 2023年6月8日
    080
  • 狂神说java学习day01

    标题: 二级标题 Hello,World! Hello,World! Hello,World! Hello,World! 选择狂神说java,走向人生巅峰 分割线 超链接 A B …

    Java 2023年6月6日
    093
  • Spring Cloud认知学习(六):配置中心Spring Cloud Config的使用

    Spring Cloud Config 作用: 简单示例 创建配置中心 拉取配置 测试 补充: 💡上一篇介绍一个新的组件Zuul,Zuul是网关组件,对Api请求进行了统一的接收,…

    Java 2023年5月30日
    0102
  • 一文搞懂 Spring事务是怎么通过AOP实现的 ,让你醍醐灌顶

    阅读此文章需要掌握一定的AOP源码基础知识,可以更好的去理解事务,我在另外一篇文章有提过。 spring事务其实就是根据事务注解生成代理类,然后在前置增强方法里获取connecti…

    Java 2023年6月7日
    096
  • 链表的操作(但某些操作的结果有误)

    cpp;gutter:false;</p> <h1>include</h1> <h1>include</h1> <…

    Java 2023年6月7日
    064
  • Java异常机制

    什么是异常 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求;你的程序要打开某个文件,这个文件可能不存在或者文件格式不对;你要读取数据库的…

    Java 2023年6月5日
    092
  • 如何使用原生的Hystrix

    什么是Hystrix 前面已经讲完了 Feign 和 Ribbon,今天我们来研究 Netflix 团队开发的另一个类库–Hystrix。 从抽象层面看, Hystri…

    Java 2023年6月14日
    0133
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球