Spring cache简单使用

前言

spring有一套和各种缓存的集成方式。类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache.

什么时候用缓存

首先,缓存是为了省略消耗时间的步骤,比如io。当我需要从数据库查询的数据几乎没有变化,或者变化很少的时候,我就没必要每次都去数据库里拿数据了。大可以放到本地,直接取出来就可以了。这时候需要注意的是数据一致性问题,缓存的数据是否被更改了,数据是否有效。

我的项目是分布式部署的,但还没有搭建分布式缓存服务。我采用的本地缓存,也就是说,我的缓存只能在本实例中,跨机器访问则不命中。即便如此也大大减少了访问数据库的开销了。

配置缓存

这里采用guava cache作为本地缓存。将guava cache注册到cacheManger里就可以调用了。

1.配置cacheManger

首先针对要缓存的类型,配置缓存策略。这里设置最大缓存数量和缓存过期时间

public static final String HOTEL_POSTION = "hotel_position";  //cache key
@Value("${cache.guavaCache.hotelPosition.maxSize}")
private long hotelPositionMaxSize;
@Value("${cache.guavaCache.hotelPosition.duration}")
private long hotelPositionDuration; private GuavaCache buildHotelPositionCache() {
return new GuavaCache(HOTEL_POSTION,
CacheBuilder.newBuilder()
.recordStats()
.maximumSize(hotelPositionMaxSize)
.expireAfterWrite(hotelPositionDuration, TimeUnit.DAYS)
.build());
}

将刚才创建的缓存策略添加到cacheManger:

	@Bean
public CacheManager cacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
List list = new ArrayList();
list.add(buildHotelPositionCache());
manager.setCaches( list ); return manager;
}
2.配置要缓存的方法

在需要使用这个缓存的地方,增加一行注解

@Cacheable(value = CacheManagementConfig.HOTEL_POSTION, key = "{#hotelId}", condition = "", unless = "!#result.isSuccessful()")
public BaseDomainResponse<HotelPosition> getHotelPosition(int hotelId, String apiToken) {
//......
}
  • @Cacheable表示这个方法要被缓存
  • value string,表示这个方法缓存的唯一性标识,即这方法缓存的key。语法为SpEL.
  • key String,表示每条请求缓存的key,即如果key相同,则返回缓存中对应的数据
  • condition boolean,可以额外添加缓存的条件.语法为SpEL.
  • unless boolean, 配置哪些条件下的记录不缓存。语法为SpEL.
  • result表示return的这个对象,可以同result来调用这个对象的属性,比如isSuccessful()就是我返回对象的一个方法。

官方文档

此处学习官方文档cache部分,spring版本4.1+。

At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the information available in the cache. That is, each time a targeted method is invoked, the abstraction will apply a caching behavior checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the result cached and returned to the user so that, the next time the method is invoked, the cached result is returned. This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result reused without having to actually execute the method again. The caching logic is applied transparently without any interference to the invoker.

这个缓存应用于java 方法级别缓存,通过缓存中的数据来减少方法执行次数。每当目标方法被调用,spring cache会执行一个缓存行为来检查这个相同参数的方法是否已经被执行。如果被执行过了,那么不执行方法直接返回缓存中的结果。 通过这样,代价高的方法(CPU或IO依赖)可以只执行一次,相同参数的结果会复用而不是真正的执行这个方法。这个缓存逻辑对调用者来说是透明的,也就是调用者不用管这个缓存逻辑。

Just like other services in the Spring Framework, the caching service is an abstraction (not a cache implementation) and requires the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching logic but does not provide the actual stores. This abstraction is materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.

There are a few implementations of that abstraction available out of the box: JDK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, Guava caches and JSR-107 compliant caches (e.g. Ehcache 3.x). See Section 36.7, “Plugging-in different back-end caches” for more information on plugging in other cache stores/providers.

spring cache是一个抽象的概念,没有提供实现方式去存储数据,开发者可以自己选择任意的实现。比如DK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, Guava caches and JSR-107 compliant caches (e.g. Ehcache 3.x)。

If you have a multi-process environment (i.e. an application deployed on several nodes), you will need to configure your cache provider accordingly. Depending on your use cases, a copy of the same data on several nodes may be enough but if you change the data during the course of the application, you may need to enable other propagation mechanisms.

Caching a particular item is a direct equivalent of the typical get-if-not-found-then- proceed-and-put-eventually code blocks found with programmatic cache interaction: no locks are applied and several threads may try to load the same item concurrently. The same applies to eviction: if several threads are trying to update or evict data concurrently, you may use stale data. Certain cache providers offer advanced features in that area, refer to the documentation of the cache provider that you are using for more details.

To use the cache abstraction, the developer needs to take care of two aspects:

  • caching declaration - identify the methods that need to be cached and their policy
  • cache configuration - the backing cache where the data is stored and read from

如果你采用多过程环境(比如,一个项目部署到多个服务节点,即分布式部署),你需要配置相应的的缓存实现。在你的使用案例中,同样数据的拷贝已经足够使用了。但如果你在这期间修改了数据,你需要使用其他传播机制来控制缓存的一致性。

缓存一个指定的条目直接等价于获取-如果-不存在-然后-执行-并且-最好放入缓存的程序逻辑的代码块:不会阻塞并且多线程可以并发地加载相同的条目。缓存更新策略也一样:如果几个县城尝试并发地更新或者移除缓存的数据,你需要使用过期的数据。在这个领域,特定的缓存实现提供更先进的方式,参考你使用的缓存实现的文档来获取等多的详情。

想要使用这个抽象的缓存,开发者需要关心两个方面:

  • 缓存声明 - 定义需要被缓存的方法以及对应的缓存策略。
  • 缓存配置 - 数据存储和读取的实现。

1.基于注解的声明式缓存

缓存抽象提供了一系列的java注解:

  • @Cacheable 触发缓存逻辑
  • @CacheEvict 触发缓存逐出逻辑
  • @CachePut 不干涉方法执行地更新缓存
  • @Caching 重组一个方法上的多重缓存操作

1.1@Cacheable 注解

就像名字所暗示的,@Cacheable是用来区分方法是否可缓存的。也就是说,哪个方法可以把结果存储到cache中,所以随后调用(相同的参数)时会返回cache中的值,而且并不会实际上运行这个method。最简单的用法:注解需要一个cache的name来关联这个method。

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

在上述的片段中,method findBook关联到名字叫做books的cache。每次这个方法被调用的时候,cache会检查这个调用是否已经被执行过了并且不必重复执行。大多数情况下,只声明一个cache,但这个注解支持声明多个name,因此可以使用多个cahce。这样,在执行method之前每个cache都会检查是否存在 - 如果至少一个cache命中了,然后就会返回关联的值。

默认key注册模式

因为cache本质上是key-value存储,每次调用缓存的method需要被翻译成一个合适的key来获取缓存。Out of the box(这句话不知道该怎么翻译,box应该是指这个类,即在这个method所在的类之外), 缓存代理(cache abstraction)使用一个基于以下算法的简单的KeyGenerator

  • 如果没有参数,key就是SimpleKey.EMPTY.
  • 如果只有一个参数,则返回那个参数.
  • 如果多个参数,返回SimpleKey包含所有的参数

只要参数有__natural keys__ 并且实现了合法的hashCode()equals(),这个方法适合于大多数使用案例。如果不是,则key产生策略就需要改变。

不想使用默认的key生产机制,你需要实现接口:org.springframework.cache.interceptor.KeyGenerator.

自定义Key产生声明

因为caching是普遍的,所以很可能目标method有各种签名(signatures)不可以简单的映射到cache结构中。这个在目标mothod有多个参数但只有部分参数和缓存关联的时候就变得明显。

简单的说,cache默认把参数组合成一个key,这个key对应一个结果,下载遇到相同参数就会对应这个key,可以去除这个key对应的结果。然而,有时候,我们有多个参数,比如a,b,c。只有a和b和缓存的结果有关,c是变化的。

@Cacheable("books")
public Book findBook(ISBN a, String b, String token)

假设我这个findBook需要一个token来获取权限,但和book无关。那么我们遇到相同的a就可以返回对应的book了,不需要关心token。换句话说,自己可以定义缓存的条件,只要a和b相同,则命中同一个缓存。

然而,默认的会将a和b还有token组成一个key,只有这个三个相同的时候才会命中缓存。这时候就需要我们自定义key的组成了。

以下示例各种SpEL声明,通过SpEL语法来声明key:

//仅仅使用key(isbn)
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, String b, String token) //使用isbn的一个属性当做key
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) //调用某个类的某个方法来生成key
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) //组合key(a和b)
@Cacheable(cacheNames="books", key="#a.concat(#b)")
public Book findBook(String a, String b, String token)

上述代码片段显示了选择一个特定的参数或者一个参数的属性或者任意的方法或者组合参数作为key是多么简单。

如果产生key的算法太特殊或者如果这个key需要共享,你可以自定义一个keyGenerator。只要声明自定义的KeyGenerator的bean实现就可以了:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
`key`和`keyGenerator`是互斥的,如果同时声明会抛出异常。
默认的Cache Resolution

Out of the box, 缓存代理使用简单的CacheResolver来获取cache, 这个是可以使用CacheManager来手动配置的。

如果不想使用默认的cache resolver,你需要实现接口:org.springframework.cache.interceptor.CacheResolver

自定义Cache Resolution

默认的cache resolution适合于使用一个CacheManager并且没有复杂的cache resolution.

对于采用多个cache managers的应用,要设置cacheManger

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

当然也可以完全替换CacheResolver, 就像key generation一样简单。每次cache操作都会请求这个resolution,基于运行时的参数来交给它的实现。

@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
就像`key`和`keyGenerator`一样,`cacheManager`和`cacheResolver`参数也是互斥的,同时声明会抛出异常。
同步caching

在多线程环境,一个操作也许会并发的执行(比如启动的时候)。 默认的,cache代理不会lock并且同样的数据也许会计算多次,这与cache的目标相悖。

在这些特殊的场景,当计算的时候,参数sync可以用来通知将cache lock cache entry. 这样,只有一个线程可以计算,其他的等待entry被更新到cache。

@Cacheable(cacheNames="foos", sync="true")
public Foo executeExpensiveOperation(String id) {...}
条件缓存(conditional caching)

有时候,一个method也许并不适合全部缓存(比如,根据参数缓存)。cache注解通过参数condition来支持这种功能,同样使用SpEL表达式,结果为true或false, 如果是true则缓存,否则表现为这个method没有缓存。这个判断会在每次获取value的时候执行,无论缓存的value是什么以及无论使用哪个参数。

一个简单的示例,一下method只有在参数name长度小于32的时候执行缓存。

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

除了使用condition, unless可以用来否决把结果加入缓存。不同的是,unless的表达式会在method执行结束后考量,就是mehtod执行完后判断是否加入缓存。扩展之前的示例 -- 我们只需要缓存paperback books. unless为true的时候不缓存。

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

这里#result就是指向返回值。

可使用的SpEL表达式

每个SpEL表达式都有一个专门的context。除了采用参数构建表达式,框架提供了专门的与caching相关的元数据,比如参数名。下表列出了在context中可用的参数,你可以用来当做key和conditional 处理。

Name Location Description Example
methodName root object 被执行的method的名字 #root.methodName
method root object 被执行的method #root.method.name
target root object 执行的对象 #root.target
targetClass root object 执行对象的class #root.targetClass
args root object 执行对象的参数们(数组) #root.args[0]
caches root object 当前method对应的缓存集合 #root.caches[0].name
argument name evaluation context 任意method的参数。如果特殊情况下参数还没有被赋值(e.g. 没有debug信息),参数可以使用#a<#arg>来表示,其中#arg代表参数顺序,从0开始 #iban或者#a0(也可以使用#p0或者#p<#arg>注解来启用别名)
result evaluation context method执行的结果(要缓存的对象),仅仅在unless表达式中可以使用,或者cache put(用来计算key),或者cache evict表达式(当beforeInvocation=false). 为了支持wrapper,比如Optional#result指向世纪的对象,不是wrapper. #result
参考

Spring cache简单使用guava cache的更多相关文章

  1. Spring Boot 揭秘与实战(二) 数据缓存篇 - Guava Cache

    文章目录 1. Guava Cache 集成 2. 个性化配置 3. 源代码 本文,讲解 Spring Boot 如何集成 Guava Cache,实现缓存. 在阅读「Spring Boot 揭秘与实 ...

  2. Ehcache与Guava Cache的区别浅谈

    最近在做一些缓存改造的场景,有如下一些经验总结: 缓存版本: Ehcache:2.8.3 Guava:17.0 Ehcache支持持久化到本地磁盘,Guava不可以: Ehcache有现成的集群解决方 ...

  3. Guava Cache 原理分析与最佳实践

    前言 目前大部分互联网架构 Cache 已经成为了必可不少的一环.常用的方案有大家熟知的 NoSQL 数据库(Redis.Memcached),也有大量的进程内缓存比如 EhCache .Guava ...

  4. 分布式系统缓存系列之guava cache

      guava是google的一个开源java框架,其github地址是 https://github.com/google/guava.guava工程包含了若干被Google的 Java项目广泛依赖 ...

  5. 是什么让spring 5放弃了使用Guava Cache?

    一路走来,Spring社区从刚开始的核心模块一直发展到现在,最近Sping5也完成了M5的发布, 相信不久之后第一个RELEASE版本也会发布.里面有很多特性是和即将要发布的JAVA 9息息相关的.今 ...

  6. Spring Cache简单介绍和使用

    Spring Cache 缓存是实际工作中非经常常使用的一种提高性能的方法, 我们会在很多场景下来使用缓存. 本文通过一个简单的样例进行展开,通过对照我们原来的自己定义缓存和 spring 的基于凝视 ...

  7. Guava Cache探索及spring项目整合GuavaCache实例

    背景 对于高频访问但是低频更新的数据我们一般会做缓存,尤其是在并发量比较高的业务里,原始的手段我们可以使用HashMap或者ConcurrentHashMap来存储. 这样没什么毛病,但是会面临一个问 ...

  8. guava cache与spring集成

    缓存的背景 缓存,在我们日常开发中是必不可少的一种解决性能问题的方法.简单的说,cache 就是为了提升系统性能而开辟的一块内存空间.在cpu进行计算的时候, 首先是读取寄存器,然后内存,再是硬盘.由 ...

  9. spring boot guava cache 缓存学习

    http://blog.csdn.net/hy245120020/article/details/78065676 ****************************************** ...

随机推荐

  1. php数字索引数组去重及恢复索引

    $tmp = array('a','b','c','a'); $tmp = array_values(array_unique($tmp)); print_r($tmp);exit; //输出 Arr ...

  2. java易混淆概念之类变量、成员变量、局部变量

      类变量.成员变量.局部变量 类变量(也叫静态变量)是类中独立于方法之外的变量,用static 修饰.(static表示“全局的”.“静态的”,用来修饰成员变量和成员方法,或静态代码块(静态代码块独 ...

  3. 《C语言深度解剖》面试题整理

    请在40分钟内完成以下20道C语言基础题.在没有任何提示的情况下,如果能得满分,那么你可以扔掉本书了,你的水平已经大大超过了作者:如果能的80分以上,说明你的C语言基础还不错,学习本书可能会比较轻松: ...

  4. SecurityError: Blocked a frame with origin from accessing a cross-origin frame

    问题描述:浏览器报错I am loading an <iframe> in my HTML page and trying to access the elements within it ...

  5. Bootstrap——网站添加字体图标

    @font-face { font-family: 'itcast'; src: url('../font/MiFie-Web-Font.eot') format('embedded-opentype ...

  6. 2018.09.05 bzoj2726: [SDOI2012]任务安排(斜率优化dp+二分)

    传送门 跟Ti" role="presentation" style="position: relative;">TiTi为正数的时候差不多. ...

  7. Facebook支撑万亿Post搜索背后的技术窥探

    转自http://www.csdn.net/article/2013-10-29/2817333-under-the-hood-building-posts-search 近日,Facebook为po ...

  8. ThinkPHP5.1中数据查询使用field方法数组参数起别名时遇到的问题

    首先数据库基本查询是没有问题的 <?php namespace app\index\controller; use think\Db; class Demo5 { //1.单条查询 public ...

  9. beego学习笔记(4):开发文档阅读(1)

    1.beego的设计是高度模块化的.每个模块,都可以单独使用.一共八大模块: cache;session;log;orm;context;httplibs;toolbox 2.beego的执行逻辑 3 ...

  10. python在windows下连接mysql数据库

    一,安装MySQL-python python 连接mysql数据库需要 Python interface to Mysql包,包名为 MySQL-python ,PyPI上现在到了1.2.5版本.M ...