• @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的逻辑算是执行完毕。

    还有两点需要注意的就是

    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日

  • 相关阅读:
    改写promise并添加超时处理
    js将文案复制到剪贴板
    学习笔记(安装、命名实体识别、BERT、面试)
    读书笔记——安装
    Markdown使用
    奔波三载,虽死犹生
    .net工程师的利器
    .NET开发相关技术
    两行代码教你用React useContext代替React-redux
    记解决 `antd is not defined` 解决ant design 打包体积过大的问题
  • 原文地址:https://www.cnblogs.com/liubey/p/spring_cacheable.html
Copyright © 2020-2023  润新知