spring框架cacheAnnotation缓存注释声明解析
作者:FantasyBaby 发布时间:2022-04-14 17:13:05
目录
1.基于注释声明缓存
1.1@EnableCaching
1.2@Cacheable
1.2.1默认key生成规则
1.2.2声明自定义key 生成
1.2.3默认的cache resolution
1.2.4同步缓存
1.2.5 缓存的条件
1.2.6可用的Spel 评估上下文
1.基于注释声明缓存
声明缓存,Spring缓存抽象提供了一个java annotation集合.
@Cacheable:触发缓存填充.
@CacheEvict: 触发缓存删除.
@CachePut: 不干扰方法执行的情况下更新缓存.
@Caching: 把多种缓存操作应用重组到一个方法上
@CacheConfig: 在类上设置,将一些共用缓存相关设置共享
1.1@EnableCaching
将该注解放在配置类上开启缓存,使用该注解后,允许你指定各种选项,通过AOP的方式把这些影响添加到你的应用程序中. 类似于 @Transactional
1.2@Cacheable
触发数据存储于缓存
顾名思义,@Cacheable用于标示可缓存的方法 - 即将结果存储到缓存中的方法,以便在后续调用(具有相同的参数)时,返回缓存中的值而不必实际执行该方法.
下图是所有的缓存的存储结构
cacheNames:指定缓存的名称,不同缓存的数据是彼此隔离的,可以指定多个缓存名称.(就是生成多个缓存name).如果有一个缓存命中,关联值就会返回.
更新一个name的缓存,其他所有没有包含这个值的缓存也会被更新,即使这个缓存方法没有实际调用.
@Cacheable({"coffees","coffees2"})
public List<Coffee> findAllCoffee() {
return coffeeRepository.findAll();
}
1.2.1默认key生成规则
Key :
上面cacheNames指定了缓存名称,但是每个方法由于传参不同,其return数据也会不同,所以一个方法中可能会有多个缓存。要在同一个cacheNames中区别不同的缓存,就需要使用key。通过SPEL表达式,指定 name为key。Spring 会默认以参数为key 如果没有参数则有一个org.springframework.cache.interceptor.SimpleKey.
@Cacheable(cacheNames = "coffees",key = "#name")
public Optional<Coffee> findOneCoffee(String name) {
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", exact().ignoreCase());
Optional<Coffee> coffee = coffeeRepository.findOne(
Example.of(Coffee.builder().name(name).build(), matcher));
log.info("Coffee Found: {}", coffee);
return coffee;
}
因为Caches是一个key-value的存储形式,每次调用缓存方法需要翻译成对应的key来访问缓存。缓存抽象使用简单的 keygenerator 基于以下算法:
如果没有参数,返回SimpleKey.EMPTY.
如果只有一个参数,就会作为实例返回.
如果超过一个参数,就会返回包含所有参数的 SimpleKey.
该方法对大多数case有效,只要参数有nautral keys并且他们有效的实现了 hashcode()和equals()方法.如果不是这个case的需要去改变策略.
提供不同的key 生成器,需要去实现 org.springframework.cache.interceptor.KeyGenerator 这个接口.
默认的key生成策略在spring 4.0以后有所改变.之前的Spring版本使用用的key生成策略,对于多个key参数,仅用参数的hashCode()没有使用equeal().这可能造成key冲突(从 SPR-10237 查看背景).新的 SimpleKeyGenerator对于这种场景使用了组合key.
如果你想保持之前的key生成策略,你可以配置不推荐使用的org.springframework.cache.interceptor.DefaultKeyGenerator或者创建一个自定义的 基于hash的 key产生的实现
1.2.2声明自定义key 生成
因为缓存是通用的,所以目标方法很可能有多种写法,这些不能够被顶层缓存结构简单的映射.当一个方法有多个参数,但只有一些参数需要缓存时(其他key只给方法使用),这一点变得很明显.
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
上面的例子: 用到了两个布尔参数,他们对缓存可能没有用处,此外如果有一个有用另一个没用应该怎么办?
上面说过我们可以通过@Cacheable 来指定key通过哪些参数生成.可以使用SPEL 来选择感兴趣的参数(以及嵌套属性),执行操作,或者甚至调用任意方法都可以不需要写任何代码或者实现任何接口. 推荐的默认的key 产生方式在上面已经说过,但是默认的策略并不能适应所有的方法.
下面例子使用了多种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)
如果生成key的算法太过特殊或者需要共享,可以自定义keyGenerator.
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
注: key 和 keyGenerator是互斥的,同时指定会产生异常
1.2.3默认的cache resolution
默认的cache resolution是一个简单的 CacheResolver ,通过配置CacheManager来检索在操作级别定义的缓存.
通过继承实现org.springframework.cache.interceptor.CacheResolver 接口来提供不同的默认resolver.
默认的cache resolution是由单个CacheManager实现并且没有太复杂的resolution的需求.
默认的cache resolution是由单个CacheManager实现并且没有太复杂的resolution的需求.
当一个应用同时使用多个 cacheManager 可以通过cachemanager指定
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}
也可以通过cacheResolver替换resolver 类似于替换key generation.
@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
这个resolution在每次缓存操作都会请求,允许我们基于实时的参数来实现解析缓存.
从spring 4.1之后, value的属性不再强制要求传,因为无论annotation的内容传什么,这个特定信息都会被CacheResolver提供.
类似于key和keygenerator,cacheManager和cahceResolver这两个参数都是互斥的,同时指定会返回异常,因为定义的cacheManager会被定义的cacheResolver的实现给忽略.
1.2.4同步缓存
在多线程环境里,某些操作可能使用相同参数并发调用(典型的例如启动).默认情况下,缓存抽象不会加锁,如果一个同样的值多次被计算是违背缓存的初衷的.
在这种特殊的情况下,可以使用 sync 属性来指定缓存提供方在计算缓存实体的时候加锁.
@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}
这个可选特性,也许提供缓存的库并不支持.所有的 核心框架提供得CacheManager是支持的.
1.2.5 缓存的条件
有时,一个方法并不能在所有时候都适用于一个缓存(比如,它可能依赖于给定参数).缓存的注解支持这些情况,通过使用 condition 参数.
condition中使用 Spel 表达式可以得出 true 或者false.如果是true,方法缓存,反之,方法不会缓存(这个方法无论什么值或者使用任何参数都会被调用).
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)
1.上面设置的条件是name的长度 小于32
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)
2.使用unless, 否决掉对应的value到缓存中.不像condition,unless表达式是在方法调用完后被调用,上面的例子就是如果我们只想缓存paperback book, 我们可以阻挡 hardback.
缓存抽象同样支持 java.util.Optional,返回类型.如果Optional是present,则被关联到对应缓存,如果not present,将会存储一个null. #result 总是引用业务对象并且永远不会支持包装器,所以上面的例子可以改为
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)
上面的例子 #result一直引用Book 而不是Optional<Book>.因为有可能为空,所以使用SpEL's的Safe Navigation Operator
1.2.6可用的Spel 评估上下文
名称 | 位置 | 描述 | 例子 |
methodName | Root object | 被调用方法的名称 | #root.methodName |
method | Root object | 被调用的方法 | #root.method.name |
target | Root object | 被调用的目标对象 | #root.target |
targetClass | Root object | 被调用对象的类 | #root.targetClass |
args | Root object | 调用的参数数组 | #root.args[0] |
caches | Root object | 针对当前运行方法的所有缓存 | #root.caches[0].name |
Argument name | Evaluation context | 任何方法参数的名称.如果名称找不到(也许是没有debug信息),这个参数名称可能在#a<#arg>之下找到, 这个#arg表示参数索引(从0开始) | #iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias). |
result | Evaluation context | 方法返回调用(值将被缓存).只有在 unless表达式中, cache put表达式(去计算一个key)或者 cache evict 表达式(当 beforeInvocation=false)可用. 为了支持wrappers(比如 Optional),#result 引用的实际的对象而非 wrapper | #result |
来源:https://blog.csdn.net/FantasyBaby/article/details/120650838
猜你喜欢
- 用Java编写简单的五子棋,供大家参考,具体内容如下前言这两天在空闲时间做了个五子棋项目,分享给大家看一下,界面是这样的:界面很丑我知道,本
- 一.背景本文主要介绍Java 8中时间的操作方法java.util.Date是用于表示一个日期和时间的对象(注意与java.sql.Date
- 一、logback日志技术介绍Spring Boot中使用的日志技术为logback。其与Log4J都出自同一人,性能要优于Log4J,是L
- 话不多说,请看实例代码String ip = request.getHeader("x-forwarded-for");
- 本文以实例阐述了C++中形参与实参的区别,有助于读者加深对于C++形参与实参的认识。形参出现在函数定义中,在整个函数体内都可以使用, 离开该
- 前言异步调用几乎是处理高并发,解决性能问题常用的手段,如何开启异步调用?SpringBoot中提供了非常简单的方式,就是一个注解@Async
- 1.问题描述汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚
- 由C#转入Java一段时间了,总结下个人认为的Java同C#语法之间的不同之处,有不同意见之处还望各位海涵 刚学Java时觉得语法同C#大致
- idea pom文件图标不对今天遇到一个奇怪的现象,如下图原先pom的图标应该是有个m的,现在直接变成了xml的文件了。右边的Maven P
- 前言工作中使用mybatis时我们需要根据数据表字段创建pojo类、mapper文件以及dao类,并且需要配置它们之间的依赖关系,这样的工作
- 编写规范目的:能够在编码过程中实现规范化,为以后的程序开发中养成良好的行为习惯。1、 项目名全部小写2、 包名全部小写3、 类名首字母大写,
- Java BufferWriter写文件之后文件是空的或者数据不全在编程的过程中,读写文件是非常常见的操作,在这里我问介绍一下最近我遇到的集
- 本文实例总结了java判断字符串是否为数字的方法。分享给大家供大家参考,具体如下:方法一:用JAVA自带的函数public static b
- Map是键值对的集合,又叫作字典或关联数组等,是最常见的数据结构之一。在java如何让一个map按value排序呢? 看似简单,但却不容易!
- Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 1.方法声明时使
- 前言为什么用动静态库我们在实际开发中,经常要使用别人已经实现好的功能,这是为了开发效率和鲁棒性(健壮性);因为那些功能都是顶尖的工程师已经写
- 三目条件运算公式为 x?y:z 其中x的运算结果为boolean类型,先计算x的值,若为true,则整个三目运算的结果为表达式y
- 在实际应用中,大家使用的密码可以说多种多样,但是无论有多少,其组成不遑是有可打印字符组成的,我们可以认为class CreateDic{ p
- 题目一链表题——操作链表根据给定的链表按照指定条件删除其中节点并返回新的头节点具体题目如下解法/**
- AuthenticationProvider解析首先进入到AuthenticationProvider源码中可以看到它只是个简单的接口里面也