@Cacheable的实现原理
如果你用过Spring Cache,你一定对这种配置和代码不陌生:
<cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" order="1" /> |
@Cacheable(value = "3600", key = "i'm a cache key")public List<Object> getData(){} |
上面两段代码,xml是启用Cache Annotation注解并注册一个cacheManager,第二段代码在getData的时候会先去缓存里取,如果缓存没有再执行getData的真实逻辑。
那么今天的“走进科学”讲的就是Spring是怎么做到仅仅一段xml配置和一个注解就实现方法级别的自动缓存。
我们滋道Spring最牛逼的地方就在于IOC容器对于bean的管理,可以说是Spring牛逼的基石,那么画风一换,到了我们今天讨论的起点就是Spring在启动时对xml里面的各种标签进行解析,比如对应<cache:annotation-driven>标签,负责解析的就是AnnotationDrivenCacheBeanDefinitionParser.parse方法,代码看起来很简单,根据mode属性注册Advisor Component:
我们今天先看默认mode=proxy的情况,进入方法,发现方法里面注册了三个Bean到Context里面,分别是CacheOperationSource、CacheInterceptor和BeanFactoryCacheOperationSourceAdvisor。
熟悉AOP原理的看到Interceptor和Advisor一般都会明白大半了,并且他们共同都有一个属性cacheOperationSources,实现类是org.springframework.cache.annotation.AnnotationCacheOperationSource。
下面我们先来喵两眼这两个类,先看BeanFactoryCacheOperationSourceAdvisor,里面有一个叫CacheOperationSourcePointcut的pointcut,用来匹配方法是否需要走拦截器。通过调用之前注入进去的cacheOperationSources.getCacheOperations获取CacheOperation,代码如下:
这样只有被CacheOperationSourcePointcut匹配的方法才会被拦截,并且通过attributeCache做了缓存。
再来看CacheInterceptor类,先看一眼继承结构:

这个类很简单,只是重写了MethodInterceptor的invoke方法:
下一步是调用CacheAspectSupport
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { // check whether aspect is enabled // to cope with cases where the AJ is pulled in automatically if (this.initialized) { Class<?> targetClass = getTargetClass(target); Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass)); } } return invoker.invoke();} |
其中根据 getCacheOperations获得cacheOperations后调用的execute是关键,其中getCacheOperationSource即是之前说到的bean里面的cacheOperationSources,也就是org.springframework.cache.annotation.AnnotationCacheOperationSource,它负责三个标签的调用:@Cacheable、@CachePut和@CacheEvict。
下面喽一眼execute方法的代码:
private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) { // Process any early evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT); // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); // Collect puts from any @Cacheable miss, if no cached item is found List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>(); if (cacheHit == null) { collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests); } Cache.ValueWrapper result = null; // If there are no put requests, just use the cache hit if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) { result = cacheHit; } // Invoke the method if don't have a cache hit if (result == null) { result = new SimpleValueWrapper(invokeOperation(invoker)); } // Collect any explicit @CachePuts collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(result.get()); } // Process any late evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); return result.get();} |
这段代码看起来还是比较“简单”的,也是Spring Cache逻辑的核心实现了吧,根据注解执行了方法前和方法后需要的缓存操作,注意对于失效的操作分为early evictions和late evictions,对应标签@CacheEvict中的beforeInvocation属性。自此,Spring cache的逻辑算是执行完毕。
还有两点需要注意的就是
- 上面的实现是通过proxy的形式实现,那么对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy失效,也就是注解失效。
- 非public方法同上
- @CacheEvict标签不会对抛出异常的方法的缓存进行清空,通过将beforeInvocation设置为true,即在方法执行前
最后的话:
本篇文章并没有讲Spring的AOP实现原理以及Spring Cache的更多细节。
参考:
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
http://www.cnblogs.com/chanedi/p/4552555.html
原文发表于2015年10月16日
@Cacheable的实现原理的更多相关文章
- Hibernate缓存原理与策略
Hibernate缓存原理: 对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等 ...
- Django admin 组件 原理分析与扩展使用 之 sites.py (一)
一 . 前言 Django 提供了admin 组件 为项目提供基本的管理后台功能(对数据表的增删改查). 本篇文章通过 admin源码 简单分析admin 内部原理 ,扩展使用方式,为以后进行定制和自 ...
- Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用(转)
原文地址:https://www.cnblogs.com/fashflying/p/6908028.html 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对 ...
- varnish学习以及CDN的原理
一.varnish学习Web Page Cache: squid --> varnish 程序的运行具有局部性特征: 时间局部性:一个数据被访问过之后,可能很快会被再次访问到: 空间局部性:一个 ...
- Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...
- Spring之缓存注解@Cacheable
https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...
- webpack-loader原理
loader loader 是导出为一个函数的 node 模块.该函数在 loader 转换资源的时候调用.给定的函数将调用 loader API,并通过 this 上下文访问. loader配置 { ...
- Java面向切面原理与实践
Java面向切面原理与实践 一. 面向切面编程是什么 首先用一句话概括:面向切面编程(AOP)就是对某些具有相似点的代码进行增强. 相似点可以是同一个包.使用相同的注解.public的方法.以Impl ...
- Springboot中的缓存Cache和CacheManager原理介绍
背景理解 什么是缓存,为什么要用缓存 程序运行中,在内存保持一定时间不变的数据就是缓存.简单到写一个Map,里面放着一些key,value数据,就已经是个缓存了 所以缓存并不是什么高大上的技术,只是个 ...
随机推荐
- 芝麻HTTP: Scrapy小技巧-MySQL存储
这两天上班接手,别人留下来的爬虫发现一个很好玩的 SQL脚本拼接. 只要你的Scrapy Field字段名字和 数据库字段的名字 一样.那么恭喜你你就可以拷贝这段SQL拼接脚本.进行MySQL入库处理 ...
- Django学习-7-ORM操作
select * from tb where id > 1 # 对应关系 models.tb.objects.filter(1id__gt=) models.tb. ...
- 通过回调函数的理解来进一步理解ajax及其注意的用法
一,再一次理解回调函数 (function($){ $.fn.shadow = function(opts){ //定义的默认的参数 var defaults = { copies: 5, opaci ...
- 【转】Swagger2 添加HTTP head参数
大家使用swagger往往会和JWT一起使用,而一般使用jwt会将token放在head里,这样我们在使用swagger测试的时候并不方便,因为跨域问题它默认不能自定义head参数.然后自己去网上找, ...
- BZOJ 3527: [ZJOI2014]力(FFT)
BZOJ 3527: [ZJOI2014]力(FFT) 题意: 给出\(n\)个数\(q_i\),给出\(Fj\)的定义如下: \[F_j=\sum \limits _ {i < j} \fra ...
- hadoop第二课
1.1为什么需要Hadoop (1)数据分析者面临的问题 – 数据日趋庞大,读写都出现性能瓶颈: – 用户的应用和分析结果,对实时性和响应时间 要求越来越高: – 使用的模型越来越复杂,计算量指数级 ...
- ambari下 hive metastore 启动失败
由字符集引起的hive 元数据进程启动失败 解决方法新增 这2句话 reload(sys)sys.setdefaultencoding('utf8')
- Mac下安装FFmpeg教程
一.安装ffmpeg 分为两种安装方式: 1. 命令行安装 brew install ffmpeg 2. 下载压缩包安装 去 http://evermeet.cx/ffmpeg/ 下载7z压缩包,解压 ...
- 面向对象和面向过程,python中的类class,python中程序的入口——main方法,
1.程序入口,让main显现出来: print(__name__)#__name___是模块中的隐藏字段,当前模块运行的函数名 if __name__ == __main__ __main__() # ...
- TypeScript入门知识三(函数新特性)
一,Rest and Spread操作符: 用来声明任意数量的方法参数也就是"..."操作符 输出结果: 18 jajj 89 function test (a, b, c) { ...