• spring源码分析之cache注解


    Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

    Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。

    其特点总结如下:

    • 通过少量的配置 annotation 注释即可使得既有代码支持缓存
    • 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
    • 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
    • 支持 AspectJ,并通过其实现任何方法的缓存支持
    • 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性

    1.EnableCaching

    开启spring注解驱动的cache管理能力,类似于spring xml命名空间<cache:*>的支持。将会和org.springframework.context.annotation.Configuration一起使用,示例:

     @Configuration
      @EnableCaching
      public class AppConfig {
        @Bean
          public MyService myService() {
              // configure and return a class having @Cacheable methods
              return new MyService();
          }
     
          @Bean
          public CacheManager cacheManager() {
              // configure and return an implementation of Spring's CacheManager SPI
              SimpleCacheManager cacheManager = new SimpleCacheManager();
              cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("default")));
              return cacheManager;
          }
      }

    相应的xml配置如下:

      <beans>
          <cache:annotation-driven/>
          <bean id="myService" class="com.foo.MyService"/>
          <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
             <property name="caches">
                 <set>
                      <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                          <property name="name" value="default"/>
                      </bean>
                  </set>
              </property>
          </bean>
      </beans>

      在上述的场景中,@EnableCaching和<cache:annotation-driven/>负责注册spring所需的管理注解驱动的cache管理组件,如org.springframework.cache.interceptor.CacheInterceptor,当org.springframework.cache.annotation.Cacheable方法触发时基于proxy-或者基于AspectJ的advice 会将interceptor织入调用栈中。

      必须注册org.springframework.cache.CacheManager类型的bean,原因是框架没有默认的可用。不同点在于<cache:annotation-driven>元素假定了一个名称为cacheManager的bean,而@EnableCaching通过类型检索一个cache manage bean。因此cache manager bean方法的命名不是显式的。

      如果希望使用一种@EnableCaching与cache manager bean更直接的方式,spring提供了CachingConfigurer回调接口的实现,注意实现语句和@override注解方法:

     @Configuration
     @EnableCaching
     public class AppConfig extends CachingConfigurerSupport {
    
         @Bean
         public MyService myService() {
             // configure and return a class having @Cacheable methods
             return new MyService();
         }
    
         @Bean
         @Override
         public CacheManager cacheManager() {
             // configure and return an implementation of Spring's CacheManager SPI
             SimpleCacheManager cacheManager = new SimpleCacheManager();
             cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
             return cacheManager;
         }
    
         @Bean
         @Override
         public KeyGenerator keyGenerator() {
             // configure and return an implementation of Spring's KeyGenerator SPI
             return new MyKeyGenerator();
         }
     }

     注意,上述示例中的keyGenerator方法,它允许自定义一个cache key产生策略。每个spring的org.springframework.cache.interceptor.KeyGenerator spi,通常将配置spring的org.springframework.cache.interceptor.SimpleKeyGenerator,一个key生成器必须显示指明。如果不需要定制,则直接返回new SimpleKeyGenerator()。

    1.1 方法

    boolean proxyTargetClass() default false;

    表明是创建基于子类代理(cglib)还是基于接口的标准java代理,默认是false。当且仅当mode()设置成Advice#PROXY时其作用。

    注意,这个属性设置为true时会影响到所有spring管理bean的代理,而不仅仅是被标识为@Cacheable的接口或者类。例如,spring标注的其它bean如@Transactional将会同时升级到子类代理。这种方式实际上没有负面影响,除非显示期望一种代理类型,例如测试。

    1.2 源码

    1.2.1 xml源码解析

    CacheNamespaceHandler.java

    public class CacheNamespaceHandler extends NamespaceHandlerSupport {
    
        static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
    
        static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
    
        static String extractCacheManager(Element element) {
            return (element.hasAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE) ? element
                    .getAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE)
                    : CacheNamespaceHandler.DEFAULT_CACHE_MANAGER_BEAN_NAME);
        }
    
        static BeanDefinition parseKeyGenerator(Element element, BeanDefinition def) {
            String name = element.getAttribute("key-generator");
            if (StringUtils.hasText(name)) {
                def.getPropertyValues().add("keyGenerator", new RuntimeBeanReference(name.trim()));
            }
            return def;
        }
    
        @Override
        public void init() {
            registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
            registerBeanDefinitionParser("advice", new CacheAdviceParser());
        }
    }

    1.2.1.1 注册了AnnotationDrivenCacheBeanDefinitionParser

        /**
         * Parses the '{@code <cache:annotation-driven>}' tag. Will
         * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary
         * register an AutoProxyCreator} with the container as necessary.
         */
        @Override
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            String mode = element.getAttribute("mode");
            if ("aspectj".equals(mode)) {
                // mode="aspectj"
                registerCacheAspect(element, parserContext);
            }
            else {
                // mode="proxy"
                AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
            }
    
            return null;
        }

    aspectJ方式

        /**
         * Registers a
         * <pre class="code">
         * <bean id="cacheAspect" class="org.springframework.cache.aspectj.AnnotationCacheAspect" factory-method="aspectOf">
         *   <property name="cacheManager" ref="cacheManager"/>
         *   <property name="keyGenerator" ref="keyGenerator"/>
         * </bean>
         * </pre>
         */
        private void registerCacheAspect(Element element, ParserContext parserContext) {
            if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME)) {
                RootBeanDefinition def = new RootBeanDefinition();
                def.setBeanClassName(AnnotationConfigUtils.CACHE_ASPECT_CLASS_NAME);
                def.setFactoryMethodName("aspectOf");
                parseCacheManagerProperty(element, def);
                CacheNamespaceHandler.parseKeyGenerator(element, def);
                parserContext.registerBeanComponent(new BeanComponentDefinition(def, AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME));
            }
        }

    aop方式:

    /**
         * Inner class to just introduce an AOP framework dependency when actually in proxy mode.
         */
        private static class AopAutoProxyConfigurer {
    
            public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
                AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
    
                if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME)) {
                    Object eleSource = parserContext.extractSource(element);
    
                    // Create the CacheOperationSource definition.
                    RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource");
                    sourceDef.setSource(eleSource);
                    sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                    String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
    
                    // Create the CacheInterceptor definition.
                    RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);
                    interceptorDef.setSource(eleSource);
                    interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                    parseCacheManagerProperty(element, interceptorDef);
                    CacheNamespaceHandler.parseKeyGenerator(element, interceptorDef);
                    interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));
                    String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
    
                    // Create the CacheAdvisor definition.
                    RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheOperationSourceAdvisor.class);
                    advisorDef.setSource(eleSource);
                    advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                    advisorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
                    advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
                    if (element.hasAttribute("order")) {
                        advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
                    }
                    parserContext.getRegistry().registerBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME, advisorDef);
    
                    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
                            eleSource);
                    compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
                    compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
                    compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME));
                    parserContext.registerComponent(compositeDef);
                }
            }

    1.2.1.2 注册了CacheAdviceParser,处理子注解

        @Override
        protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
            builder.addPropertyReference("cacheManager", CacheNamespaceHandler.extractCacheManager(element));
            CacheNamespaceHandler.parseKeyGenerator(element, builder.getBeanDefinition());
    
            List<Element> cacheDefs = DomUtils.getChildElementsByTagName(element, DEFS_ELEMENT);
            if (cacheDefs.size() >= 1) {
                // Using attributes source.
                List<RootBeanDefinition> attributeSourceDefinitions = parseDefinitionsSources(cacheDefs, parserContext);
                builder.addPropertyValue("cacheOperationSources", attributeSourceDefinitions);
            }
            else {
                // Assume annotations source.
                builder.addPropertyValue("cacheOperationSources",
                        new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource"));
            }
        }

    调用解析方法,包括

    cacheable,
    cache-evict,
    cache-put,
    method,
    caching
        private static final String CACHEABLE_ELEMENT = "cacheable";
    
        private static final String CACHE_EVICT_ELEMENT = "cache-evict";
    
        private static final String CACHE_PUT_ELEMENT = "cache-put";
    
        private static final String METHOD_ATTRIBUTE = "method";
    
        private static final String DEFS_ELEMENT = "caching";
    
    private RootBeanDefinition parseDefinitionSource(Element definition, ParserContext parserContext) {
            Props prop = new Props(definition);
            // add cacheable first
    
            ManagedMap<TypedStringValue, Collection<CacheOperation>> cacheOpMap = new ManagedMap<TypedStringValue, Collection<CacheOperation>>();
            cacheOpMap.setSource(parserContext.extractSource(definition));
    
            List<Element> cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
    
            for (Element opElement : cacheableCacheMethods) {
                String name = prop.merge(opElement, parserContext.getReaderContext());
                TypedStringValue nameHolder = new TypedStringValue(name);
                nameHolder.setSource(parserContext.extractSource(opElement));
                CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
                op.setUnless(getAttributeValue(opElement, "unless", ""));
    
                Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
                if (col == null) {
                    col = new ArrayList<CacheOperation>(2);
                    cacheOpMap.put(nameHolder, col);
                }
                col.add(op);
            }
    
            List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
    
            for (Element opElement : evictCacheMethods) {
                String name = prop.merge(opElement, parserContext.getReaderContext());
                TypedStringValue nameHolder = new TypedStringValue(name);
                nameHolder.setSource(parserContext.extractSource(opElement));
                CacheEvictOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
    
                String wide = opElement.getAttribute("all-entries");
                if (StringUtils.hasText(wide)) {
                    op.setCacheWide(Boolean.valueOf(wide.trim()));
                }
    
                String after = opElement.getAttribute("before-invocation");
                if (StringUtils.hasText(after)) {
                    op.setBeforeInvocation(Boolean.valueOf(after.trim()));
                }
    
                Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
                if (col == null) {
                    col = new ArrayList<CacheOperation>(2);
                    cacheOpMap.put(nameHolder, col);
                }
                col.add(op);
            }
    
            List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
    
            for (Element opElement : putCacheMethods) {
                String name = prop.merge(opElement, parserContext.getReaderContext());
                TypedStringValue nameHolder = new TypedStringValue(name);
                nameHolder.setSource(parserContext.extractSource(opElement));
                CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
                op.setUnless(getAttributeValue(opElement, "unless", ""));
    
                Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
                if (col == null) {
                    col = new ArrayList<CacheOperation>(2);
                    cacheOpMap.put(nameHolder, col);
                }
                col.add(op);
            }
    
            RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
            attributeSourceDefinition.setSource(parserContext.extractSource(definition));
            attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
            return attributeSourceDefinition;
        }

     其中,

      • @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
      • @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
      • @CachEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空

    @Cacheable、@CachePut、@CacheEvict 注释介绍

    通过上面的源码,我们可以看到 spring cache 主要使用两个注释标签,即 @Cacheable、@CachePut 和 @CacheEvict,我们总结一下其作用和配置方法。

    表 1. @Cacheable 作用和配置方法
    @Cacheable 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
    @Cacheable 主要的参数
    value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
    @Cacheable(value=”mycache”) 或者 
    @Cacheable(value={”cache1”,”cache2”}
    key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
    @Cacheable(value=”testcache”,key=”#userName”)
    condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:
    @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
    表 2. @CachePut 作用和配置方法
    @CachePut 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
    @CachePut 主要的参数
    value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
    @Cacheable(value=”mycache”) 或者 
    @Cacheable(value={”cache1”,”cache2”}
    key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
    @Cacheable(value=”testcache”,key=”#userName”)
    condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:
    @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
    表 3. @CacheEvict 作用和配置方法
    @CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空
    @CacheEvict 主要的参数
    value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
    @CachEvict(value=”mycache”) 或者 
    @CachEvict(value={”cache1”,”cache2”}
    key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
    @CachEvict(value=”testcache”,key=”#userName”)
    condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 例如:
    @CachEvict(value=”testcache”,
    condition=”#userName.length()>2”)
    allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如:
    @CachEvict(value=”testcache”,allEntries=true)
    beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如:
    @CachEvict(value=”testcache”,beforeInvocation=true)
     

    小结

      总之,注释驱动的 spring cache 能够极大的减少我们编写常见缓存的代码量,通过少量的注释标签和配置文件,即可达到使代码具备缓存的能力。且具备很好的灵活性和扩展性。但是我们也应该看到,spring cache 由于基于 spring AOP 技术,尤其是动态的 proxy 技术,导致其不能很好的支持方法的内部调用或者非 public 方法的缓存设置,当然这都是可以解决的问题,通过学习这个技术,我们能够认识到,AOP 技术的应用还是很广泛的,如果有兴趣,我相信你也能基于 AOP 实现自己的缓存方案。

    参考文献

    【1】https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/

  • 相关阅读:
    【IDEA】(4)---很好用的DEBUG功能
    【IDEA】(3)---非常实用提高开发效率和水平的插件
    【IDEA】(2)---MAC代码模版
    【IDEA】(1)---MAC下常用快捷键
    Shell(2)—数组
    MySQL(12)---纪录一次left join一对多关系而引起的BUG
    MySQL(11)---约束
    Shell(1)---变量
    算法(2)---算法复杂度理论
    MySQL(10)---自定义函数
  • 原文地址:https://www.cnblogs.com/davidwang456/p/5703037.html
Copyright © 2020-2023  润新知