如果你用过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的逻辑算是执行完毕。

还有两点需要注意的就是

  1. 上面的实现是通过proxy的形式实现,那么对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy失效,也就是注解失效。
  2. 非public方法同上
  3. @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的实现原理的更多相关文章

  1. Hibernate缓存原理与策略

    Hibernate缓存原理: 对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等 ...

  2. Django admin 组件 原理分析与扩展使用 之 sites.py (一)

    一 . 前言 Django 提供了admin 组件 为项目提供基本的管理后台功能(对数据表的增删改查). 本篇文章通过 admin源码 简单分析admin 内部原理 ,扩展使用方式,为以后进行定制和自 ...

  3. Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用(转)

    原文地址:https://www.cnblogs.com/fashflying/p/6908028.html 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对 ...

  4. varnish学习以及CDN的原理

    一.varnish学习Web Page Cache: squid --> varnish 程序的运行具有局部性特征: 时间局部性:一个数据被访问过之后,可能很快会被再次访问到: 空间局部性:一个 ...

  5. Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  6. Spring之缓存注解@Cacheable

    https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...

  7. webpack-loader原理

    loader loader 是导出为一个函数的 node 模块.该函数在 loader 转换资源的时候调用.给定的函数将调用 loader API,并通过 this 上下文访问. loader配置 { ...

  8. Java面向切面原理与实践

    Java面向切面原理与实践 一. 面向切面编程是什么 首先用一句话概括:面向切面编程(AOP)就是对某些具有相似点的代码进行增强. 相似点可以是同一个包.使用相同的注解.public的方法.以Impl ...

  9. Springboot中的缓存Cache和CacheManager原理介绍

    背景理解 什么是缓存,为什么要用缓存 程序运行中,在内存保持一定时间不变的数据就是缓存.简单到写一个Map,里面放着一些key,value数据,就已经是个缓存了 所以缓存并不是什么高大上的技术,只是个 ...

随机推荐

  1. 把连续动态bmp转换为avi

    把动态bmp24转换为avi BYTE tmp_buf[1024*768*4]; //生成avi void BMPtoAVI(CString szAVIName, CString strBmpDir) ...

  2. 用winscp从本地上传文件到服务器上出现复制文件到远端时错误。

    用winscp从本地上传文件到服务器上出现复制文件到远端时错误. 错误码:4 服务器返回的错误消息:write failed 报错如下图所示: 分析过程: 1.刚开始以为是权限不够,后面上网查了一下是 ...

  3. Python基础_如何用pip安装文件

    与其他语言相比,Python的一个很大的优势是由丰富的资源库,这就需我们按照自己的来安装文件和包.本节以在windows系统下安装pygame 为例来讲述一下安装步骤. 1. 检测python中有没有 ...

  4. ThreadPoolExecutor线程池参数设置技巧

    一.ThreadPoolExecutor的重要参数   corePoolSize:核心线程数 核心线程会一直存活,及时没有任务需要执行 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线 ...

  5. springboot--如何优雅的使用mybatis

    这两天启动了一个新项目因为项目组成员一直都使用的是mybatis,虽然个人比较喜欢jpa这种极简的模式,但是为了项目保持统一性技术选型还是定了 mybatis.到网上找了一下关于spring boot ...

  6. luoguP2711 小行星

    题目描述 星云中有n颗行星,每颗行星的位置是(x,y,z).每次可以消除一个面(即x,y或z坐标相等)的行星,但是由于时间有限,求消除这些行星的最少次数. 输入输出格式 输入格式: 第1行为小行星个数 ...

  7. Android JNI开发之C/C++层调用JAVA

    一.从C/C++层调用JAVA层代码(无参数调用) //在c代码里面调用java代码里面的方法 // java 反射 // 1 . 找到java代码的 class文件 // jclass (*Find ...

  8. selenium 断言与验证

    断言和验证都是判断结果是否跟预期效果是否一致,不一致的情况下,断言会导致测试用例直接失败,程序不会继续执行:验证的测试用例会继续执行. 断言的4种模式+5种手段: assert 断言失败时,该测试将终 ...

  9. 关于Unity的协程

    协程 认识协程 //协程不是多线程:是一段在主程序之外执行的代码 //协程不受生命周影响 //作用:能够口直代码在特定的时间执行. //1,延时操作 //2,等待某代码执行结束之后执行 /* 特点:1 ...

  10. windows下安装mysql以及启动

    配置环境变量,在path中添加 ;E:\wamp\Apache24\mysql(这是你的mysql安装路径),然后在修改一下配置文件my-default.ini(mysql安装文件夹目录下) 修改其中 ...