SpringBoot2.x—SpringCache(2)使用

JAVA && Spring && SpringBoot2.x — 学习目录

SpringBoot2.x—SpringCache(1)集成
SpringBoot2.x—SpringCache(2)使用
SpringBoot2.x—SpringCache(3) CacheManager源码

在上文SpringBoot2.x集成SpringCache+Redis中我们学习了如何整合Redis+SpringCache。而本文,将重点讲述如何使用SpringCache进行透明化的缓存。

在Spring3.1版本后,Spring框架提供了对缓存透明化应用的支持。缓存抽象允许使用各种缓存解决方案,而对代码的影响最小。

从Spring4.1开始,通过支持JSR-107注释和更多自定义选项,来改善缓存抽象。

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

SpringCache是Service层的声明式缓存。即无需与业务代码耦合,通过注解完成缓存。

2.1 @Cacheable注解

@Cacheable的注解的处理流程如下图:

@Cacheable的处理流程.png

可以使用@Cacheable用来划分可缓存的方法,即将结果存储在缓存中的方法,以便在后续调用(使用相同的参数)时,返回缓存中的值而无需实际执行该方法。

  • 注释声明以最简单的形式:注解属性为CacheName。
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
  • 支持多个CacheName。
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

2.2 @Cacheable注解属性

调用者在调用方法时,会通过注解属性自动的去缓存中进行查询。那么我们需要指定cacheManager(CacheResolver)cacheNamekey(keyGenerator),来确定去那个缓存管理器(Redis,ConcurrentHashMap等)进行查询。而cacheName以及key会组装成对应的键。

2.2.1 CacheManager和CacheResolver

  1. @CacheManager:对于使用多个缓存管理器的应用程序,可以设置cacheMananger用于选择哪种缓存管理器(redis,EhCache...),非必需,当有多个才需要指定。
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") 
public Book findBook(ISBN isbn) {...}
  1. @CacheResolver:也可指定使用哪个缓存管理器。需要通过实现org.springframework.cache.interceptor.CacheResolver接口来解析
@Cacheable(cacheResolver="runtimeCacheResolver") 
public Book findBook(ISBN isbn) {...}

cacheManager和cacheResolver参数是互斥的,同时指定这两个参数会导致异常。因为实现CacheManager会忽略自定义的CacheResolver。

2.2.2 cacheName

CacheName属性也是value属性,定义@Cacheable注解时,必须使用该属性。即指定缓存的名字。使用默认CacheManager属性,以及使用默认的key属性(SimpleKey对象包含所有的参数值)。

2.2.3 key和KeyGenerator

  1. keyGenrator属性

SpringCache默认使用SimpleKeyGenerator,默认情况下将参数值作为键,但是可能会导致key重复出现。

我们在整合SpringCache中自定义CacheGenerator,将类名:方法名作为key的一部分。

而后@Cacheable注解中,指定自定义的KeyGenerator。

    @Cacheable(value = "book2",keyGenerator = "keyGenerator")
    public Account getAccInfo(String customerId, String accType) {
      //业务逻辑
    }

注意key和keyGenerator依旧是互斥的。

  1. key属性

当然若是使用key属性,也是可以指定类名和方法名等参数作为key。

SpringCache提供了与缓存相关的专用元数据,例如参数名称。下表描述了可用于上下文的项目,以便于key的生成和条件计算

名称 位置 描述 例子
methodMame root 被调用方法的名称 #root.methodName
method root 被调用的方法 #root.method.name
target root 被调用的目标对象 #root.target
targetClass root 被调用目标的类 #root.targetClass
args root 用于被调用目标的参数值(数组) #root.args[0]
caches root 执行当前方法缓存的集合 #root.caches[0].name
参数名称 调用的方法 方法的任何参数名称 #iban或#a0
result 调用的方法 仅用在unless,方法调用的结果(缓存值) #result
  1. cacheName无法使用SpEL表达式,#root.args是参数值。
    @Cacheable(cacheNames = "#root.methodName",key = "#root.args")
    public User getUser(int id) {
        User user = new User().setUserName("tom").setId(id);
        log.info("【调用getUser】方法");
        return user;
    }
图4- @Cacheable(cacheNames = "#root.methodName",key = "#root.args").png
  1. 两个SpEL表达式拼接,创建更具体的key值
    @Cacheable(value = "book2",  
            key = "#root.targetClass.getSimpleName().concat(':').concat(#root.methodName).concat(':').concat(#customerId)")  
    public User getUser(int id) {
        User user = new User().setUserName("tom").setId(id);
        log.info("【调用getUser】方法");
        return user;
    }
图5-两个SpEL表达式拼接.png

2.3.4 同步缓存

在多线程环境下,某些操作可能会为一个参数并发调用。默认情况下,SpringCache不会锁定任何内容,并且可能多次计算相同的值,从而破坏了缓存的目的。

对于那些特殊情况,可以使用sync属性来锁定。即只有一个线程正在忙于计算该值,而其他线程则被阻塞,直到缓存中更新该条目为止。

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

4. 条件缓存

  1. condition:方法可能不总适合缓存(例如:他可能取决于给你定的参数)。缓存注释通过condition支持这种功能,该参数采用SpEL表达式,该表达式的值等于true或false。如果为true,则缓存该方法。否则的话,每次调用该方法。例如:仅当参数name的长度小于32时才缓存以下方法:
@Cacheable(cacheNames="book", condition="#name.length() < 32") 
public Book findBook(String name)
  1. unless(如果不):可以使用unless参数来决定是否将值添加到缓存中,该参数也采用SpEl表达式,该表达式输出结果boolean类型。与condition不同的是,unless表达式是在调用方法后求值的,并且当SpEL返回false时,加入到缓存中(unless:如果不小于1000,则存储。)。
    @Cacheable(cacheNames="books", key="#isbn.rawNumber",unless ="#result.id < 1000" )
    public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {
        log.info("执行方法!");
        Book book = Book.builder().id(1101).bookName("java").build();
        return book;
    }

若出现org.springframework.expression.spel.SpelEvaluationException: EL1008E异常原因

SpringCache支持java.util.Optional,仅在支持时才将其内容作为缓存。#result始终引用业务实体,而不引用受支持的包装器。因此可以重写为下面代码:

    @Cacheable(cacheNames="books", key="#isbn.rawNumber",unless ="#result?.id >1000" )
    public Optional<Book> findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) {
        log.info("执行方法!");
        Book book = Book.builder().id(111).bookName("java").build();
        Optional<Book> optionalBook = Optional.of(book);
        return optionalBook;
    }

请注意:result仍然指的是Book而不是Optional。

1. @CachePut注解

在不影响方法执行的情况下更新缓存,可以使用@CachePut注解。也就是说,该方法始终执行,将其结果放入缓存(根据@CachePut选项)。他支持与@Cacheable缓存相同的属性。但是它应用于缓存填充而不是方法优化。

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
图1-更新缓存.png

2.2 @CacheEvict注解

Spring Cache不仅允许缓存的填充,还允许删除缓存。此过程对于从缓存中删除陈旧或未使用的数据很有用,相对于@Cacheable,@CacheEvict是从缓存中删除数据的注解。@CacheEvict需要指定一个或多个受操作影响的缓存,允许自定义缓存和Key或者条件。

  1. allEntries,该参数指示是否需要在整个缓存范围内逐出而不仅仅是基于Key的逐条逐出。
    代码1:逐出books缓存中的所有条目。
@CacheEvict(cacheNames="books", allEntries=true) 
public void loadBooks(InputStream batch)
  1. beforeInvocation,该参数指定逐出缓存是在方法执行前还是方法执行后(默认方法执行后)。在默认情况下,如果方法未执行(可能已经被缓存)或者引发异常,缓存是不会被移除的。而beforeInvocation=true逐出缓存则是在方法调用前发生。适用于移除操作和方法结果没有必要联系的情况。

代码2:方法执行前移除缓存

    @CacheEvict(cacheNames = "books", key = "#isbn",beforeInvocation = true)
    public void loadBooks(ISBN isbn) {
        log.info("清除缓存!");
        //出现异常,默认不会清除缓存
        throw new RuntimeException("aa");
    }

注:void方法可以与@CacheEvict一起使用,因为方法充当触发器,返回值将被忽略(因为他们不与缓存交互)。

2.3 Caching注解

指定多个相同类型的注解时(例如@CacheEvict或@CachePut)。因为Key或Key的表达式在不同的缓存间是不同的。@Caching允许嵌套多个@Cacheable,@CachePut和@CacheEvict注解来使用。

代码3:使用两个@CacheEvict注解

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

2.4 CacheConfig注解

@CacheConfig是一个类级别的注解,他允许共享cacheNames,custom KeyGenerator,custom CacheManager和custom CacheResolver。将此注解注释在类上不会打开任何缓存操作。

注:方法级别的注解会覆盖类注解

本文推荐

Spring官网对Cache的描述

SpringBoot官网对Cache的描述

spring-boot的spring-cache中的扩展redis缓存的ttl和key名

历史文章

mybatis&&数据库优化&&缓存目录
JAVA && Spring && SpringBoot2.x 目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,599评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,629评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,084评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,708评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,813评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,021评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,120评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,866评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,308评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,633评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,768评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,461评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,094评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,850评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,082评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,571评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,666评论 2 350

推荐阅读更多精彩内容

  • 一、前言   最近在和同事一块做一个需求的时候,看了下同事的代码,在使用缓存这块看到了Spring Cache这个...
    骑着乌龟去看海阅读 4,724评论 0 9
  • 本文转自被遗忘的博客 Spring Cache 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下...
    quiterr阅读 1,075评论 0 8
  • 史上最全的Spring Boot Cache使用与整合 一:Spring缓存抽象 Spring从3.1开始定义了o...
    eloong阅读 780评论 0 1
  • Spring Boot高级 内容概要 一、Spring Boot与缓存 二、Spring Boot与消息 三、Sp...
    顺毛阅读 371评论 0 2
  • 感悟:学会先去倾听,很多时候我们对于事情都会有不同的见解和认知度,先听对方说完然后再从中换位思考,最后再做出回应,...
    临淄茂业DDM黄红阅读 132评论 0 0