• 002-spring cache 基于注解的缓存-02详细-Cacheable 、CachePut、CacheEvict、Caching、CacheConfig、EnableCaching、自定义


    三、关键注解说明

    3.1、@Cacheable【query】【标记在方法或类上,先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中】

      当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

      Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。

      @Cacheable可以指定属性,value、key和condition、unless

      用于划分可缓存的方法 - 也就是说,结果存储在缓存中的方法,以便后续调用(使用相同的参数),缓存中的值将被返回,而不必实际执行该方法。以最简单的形式,注释声明需要与注释方法关联的缓存的名称:

        @Override
        @Cacheable(unless = "#result == null")
        public List<AccountBalance> select() {
            return this.list();
        }

    源码解读:

    public @interface Cacheable {
        @AliasFor("cacheNames")
        String[] value() default {};            //缓存的名字,可以从多个缓存中读取数据
        String key() default ""; //缓存key,可以使用SpEL表达式来设定缓存的key,如果不设置,默认使用KeyGenerator生成, 默认会将方法上所有参数都会作为key的一部分
        String condition() default "";//满足缓存条件的数据才会从缓存中读取,或者在不存在的时候添加到缓存中
        String unless() default "";   //用于否决缓存更新的,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
    
        @AliasFor("value")
        String[] cacheNames() default {};        //缓存的名字,可以从多个缓存中读取数据
        String keyGenerator() default "";   //用来生成key,与key()不可以共用
        String cacheManager() default ""; //设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字
        String cacheResolver() default "";//使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用
    
        boolean sync() default false;//用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法;  则只允许一个线程通过去执行方法 
    }

    3.1.1、同步缓存

      在多线程环境中,可能会为同一个参数(通常在启动时)同时调用某些操作。默认情况下,缓存抽象不会锁定任何内容,并且可能会多次计算相同的值,从而破坏缓存的目的。

      对于这些特殊情况,可以使用sync属性指示基础缓存提供程序在计算值时锁定缓存条目。因此,只有一个线程会忙于计算值,而其他线程将被阻塞,直到该条目在缓存中更新。

    @Cacheable(cacheNames="foos", sync=true)
    public Foo executeExpensiveOperation(String id) {...}

      注意:这是一个可选功能,您最喜欢的缓存库可能不支持它。核心框架提供的所有CacheManager实现都支持它。

    3.1.2、条件缓存【condition、unless】

      有时,一个方法可能并不适合于一直进行缓存(例如,它可能取决于给定的参数)。缓存注释通过条件参数支持这种功能,该条件参数采用SpEL表达式,该表达式被评估为true或false。如果为true,则该方法被缓存 - 如果不是,则其行为就像该方法未被缓存一样,每当无论缓存中的值或使用什么参数时都会执行该方法。

      有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。

    @Cacheable(cacheNames="book", condition="#name.length() < 32")
    public Book findBook(String name)

      此外,condition参数还可以使用unless参数来否决向缓存中添加值。与条件不同,除非在调用方法后对表达式进行调用。扩展前面的例子 - 也许我们只想缓存平装书:

    @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
    public Book findBook(String name)

      缓存抽象支持java.util.Optional,仅当它存在时才将其内容用作缓存值。 #result始终引用业务实体,并且永远不会在受支持的包装器上,因此前面的示例可以重写为:

    @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
    public Optional<Book> findBook(String name)

       注意:Note that result still refers to Book and not Optional. As it might be null, we should use the safe navigation operator.

    3.2、@CachePut 【insert、update】【标注在类上和方法,新增/修改方法,调用方法时会自动把符合条件的数据存入缓存】

      在支持Spring Cache的环境下,

        对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。

        @CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

    public @interface CachePut {    
        String[] value();              //缓存的名字,可以把数据写到多个缓存    
        String key() default "";       //缓存key,如果不指定将使用默认的KeyGenerator生成
        String condition() default ""; //满足缓存条件的数据才会放入缓存,CachePut的condition只在调用方法之后判断,因此可以得到result    
        String unless() default "";    //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了    
    }

      对于需要更新缓存而不干扰方法执行的情况,可以使用@CachePut注释。也就是说,该方法将始终执行并将其结果放入缓存中(根据@CachePut选项)。

      注意:对同一方法使用@CachePut和@Cacheable注释通常是强烈的不鼓励,因为它们具有不同的行为。尽管后者导致通过使用缓存而跳过方法执行,但前者强制执行以执行缓存更新。这会导致意想不到的行为,并且除了特定的角落案例(例如具有相互排斥他们的条件的注释)之外,应该避免这种声明。还要注意,这样的条件不应该依赖于结果对象(即#result变量),因为这些条件将在前期进行验证以确认排除。

    3.3、@CacheEvict【delete】【标注在类上和方法,清除缓存,调用方法时会从缓存中移除符合条件的数据】

      当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。注解@CacheEvict划定了执行缓存驱逐的方法,即充当从缓存中移除数据的触发器的方法。就像其他的一样,@CacheEvict需要指定一个(或多个)受该操作影响的缓存,允许指定自定义缓存和key解析或条件

    public @interface CacheEvict {    
        String[] value();                        //缓存的名字,可以从多个缓存中移除数据
        String key() default "";                 //缓存key,如果不指定将使用默认的KeyGenerator生成
        String condition() default "";           //满足缓存条件的数据才会从缓存中移除,condition在调用方法之前和之后都会判断
        boolean allEntries() default false;      //是否移 除所有数据  当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
        boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除    
    }

      注意void方法可以和@CacheEvict一起使用是很重要的 - 因为这些方法充当触发器,所以返回值被忽略(因为它们不与缓存交互)@Cacheable不会将数据添加/更新到缓存中,因此需要结果。

    3.4、@Caching 注解【复合注解】

      在某些情况下,需要指定相同类型的多个注解(例如@CacheEvict或@CachePut),例如因为不同缓存之间的条件或key表达式不同。@Caching允许在同一个方法上使用多个嵌套的@Cacheable,@CachePut和@CacheEvict:

    public @interface Caching {    
        Cacheable[] cacheable() default {}; //声明多个@Cacheable    
        CachePut[] put() default {};        //声明多个@CachePut    
        CacheEvict[] evict() default {};    //声明多个@CacheEvict    
    }

    示例

    @Caching( put = {    
                    @CachePut(value = "mycache", key = "#user.id"),    
                    @CachePut(value = "mycache2", key = "#user.username.concat(#user.email)")   
            },evict = {
                    @CacheEvict(value = "tempcache", key = "#user.id")
            }  
    )    
    public User save(User user) {
        .............
    }

      上述配置表示:在保存一个用户信息的时候,以user.id为key将该user缓存在mycache中,并以user.username+user.email为key将该user缓存在mycache2中,同时从tempcache中删除key值为user.id的缓存。

    3.5、@CacheConfig 注解

    示例;

    @CacheConfig("books")
    public class BookRepositoryImpl implements BookRepository {
    
        @Cacheable
        public Book findBook(ISBN isbn) {...}
    }

      @CacheConfig是一个类级注释,允许共享缓存名称,自定义KeyGenerator,自定义CacheManager以及最终的自定义CacheResolver。将该注释放在类上不会启用任何缓存操作。

      操作级定制将始终覆盖@CacheConfig上的定制集。这给每个缓存操作三个级别的自定义:

    全局配置,可用于CacheManager,KeyGenerator
    在类级别上,使用@CacheConfig
    在操作层面

    3.6、启用缓存EnableCaching

    3.6.1、说明注解

    @Import(CachingConfigurationSelector.class)
    public @interface EnableCaching {
        boolean proxyTargetClass() default false;            //动态代理模式中,Spring AOP对于具体类的代理是使用JavaProxy还是cglib
        AdviceMode mode() default AdviceMode.PROXY;            //Spring AOP使用动态代理还是原生ASPECTJ来实现
        int order() default Ordered.LOWEST_PRECEDENCE;        //启动顺序
    }

    3.6.2、启用缓存注解

    方式一、请将注解@EnableCaching添加到其中一个@Configuration类中:

    @Configuration
    @EnableCaching
    public class AppConfig {
    }

    方式二、XML配置

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:cache="http://www.springframework.org/schema/cache"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
            <cache:annotation-driven />
    </beans>

      cache:annotation-driven元素和@EnableCaching注解都允许指定各种选项,以影响缓存行为通过AOP添加到应用程序的方式。该配置有意与@Transactional的配置类似:

      注意:用于处理缓存注释的默认建议模式是“代理”,它允许仅通过代理拦截调用;相同类的本地方法间调用不能被这种方式拦截。对于更高级的拦截模式,考虑切换到“aspectj”模式并结合编译时或加载时编织。

      使用Java配置的高级定制需要实现CachingConfigurer

    3.7、自定义注解

      自定义注释和AspectJ:此功能只能使用基于代理的方法直接使用,但可以通过使用AspectJ进行额外的工作来启用。

        Spring-aspects模块仅为标准注释定义了一个方面。如果您定义了自己的注释,则还需要为这些注释定义一个方面。检查AnnotationCacheAspect作为示例。

      缓存抽象允许您使用自己的注释来确定什么方法触发缓存填充或驱逐。这作为模板机制非常方便,因为它消除了重复缓存注释声明的需要(如果指定了key或条件,则特别有用)或者如果您的代码库中不允许使用外部导入(org.springframework)。类似于其他的构造型注释,@Cacheable,@CachePut,@CacheEvict和@CacheConfig可以用作元注释,这是可以注释其他注释的注释。换句话说,让我们用我们自己的自定义注释替换一个常见的@Cacheable声明:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Cacheable(cacheNames="books", key="#isbn")
    public @interface SlowService {
    }

      上面,我们定义了我们自己的SlowService注释,它本身用@Cacheable注解 - 现在我们可以替换下面的代码:

    @Cacheable(cacheNames="books", key="#isbn")
    public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

      使用

    @SlowService
    public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

    尽管@SlowService不是Spring注释,但容器会在运行时自动获取它的声明并理解其含义。请注意,如上所述,需要启用注解驱动的行为。

    四、原理说明

    4.1、Cache注释的解析

      005-spring cache-原理、缓存AOP机制、Spring Cache抽象集成机制、springboot自动配置机制

      在spring cache中,

      AnnotationCacheOperationSource的作用是对方法上的Cache注释进行解析,并将其转化为对应的CacheOperation;执行注释解析动作的时机是在第一次调用该方法的时候(并缓存解析结果供后面方法调用时使用)。

      AnnotationCacheOperationSource内部持有一个Set<CacheAnnotaionParser>的集合,默认只包含一个SpringCacheAnnotationParser。并使用CacheAnnotaionParser来实现AbstractFallbackCacheOperationSource定义的两个抽象模板方法:

    private final Set<CacheAnnotationParser> annotationParsers;
        
        public AnnotationCacheOperationSource() {
            this(true);
        }
     
     
        public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
            this.publicMethodsOnly = publicMethodsOnly;
            this.annotationParsers = new LinkedHashSet<CacheAnnotationParser>(1);
            this.annotationParsers.add(new SpringCacheAnnotationParser());
        }
        
        @Override  
        protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {  
            return determineCacheOperations(new CacheOperationProvider() {  
                @Override  
                public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {  
                    return parser.parseCacheAnnotations(clazz);  
                }  
            });  
        }  
        
        @Override  
        protected Collection<CacheOperation> findCacheOperations(final Method method) {  
            return determineCacheOperations(new CacheOperationProvider() {  
                @Override  
                public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {  
                    return parser.parseCacheAnnotations(method);  
                }  
            });  
        }  
        
        protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {  
            Collection<CacheOperation> ops = null;  
            for (CacheAnnotationParser annotationParser : this.annotationParsers) {  
                Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);  
                if (annOps != null) {  
                    if (ops == null) {  
                        ops = new ArrayList<CacheOperation>();  
                    }  
                    ops.addAll(annOps);  
                }  
            }  
            return ops;  
        }  
        /** 
         * Callback interface providing {@link CacheOperation} instance(s) based on 
         * a given {@link CacheAnnotationParser}. 
         */  
        protected interface CacheOperationProvider {  
      
      
            /** 
             * Returns the {@link CacheOperation} instance(s) provided by the specified parser. 
             * 
             * @param parser the parser to use 
             * @return the cache operations or {@code null} if none is found 
             */  
            Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser);  
        }
    View Code

      该实现中使用回调模式,用Set<CacheAnnotaionParser>中的每一个CacheAnnotaionParser去解析一个方法或类,然后将得到的List<CacheOperation>合并,最终返回。

      如上面代码所示,默认Set<CacheAnnotaionParser>中只有一个SpringCacheAnnotationParser,因此初次调用某方法的时候会在Cache切面上首先调用SpringCacheAnnotationParser的方法:

        @Override
        public Collection<CacheOperation> parseCacheAnnotations(Method method) {
            DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
            return parseCacheAnnotations(defaultConfig, method);
        }

      来获取方法上的Cache相关注释,并将其封装成对应的CacheOperation集合并返回。其中:

    DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());

      首先判断方法所在的类上是否配置了@CacheConfig注释,如果有就将该注释中的属性值设置到defaultConfig类中,用于后续配置。然后调用核心方法:

    protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
            Collection<CacheOperation> ops = null;
     
     
            Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
            if (!cacheables.isEmpty()) {
                ops = lazyInit(ops);
                for (Cacheable cacheable : cacheables) {
                    ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
                }
            }
            Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
            if (!evicts.isEmpty()) {
                ops = lazyInit(ops);
                for (CacheEvict evict : evicts) {
                    ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
                }
            }
            Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
            if (!puts.isEmpty()) {
                ops = lazyInit(ops);
                for (CachePut put : puts) {
                    ops.add(parsePutAnnotation(ae, cachingConfig, put));
                }
            }
            Collection<Caching> cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
            if (!cachings.isEmpty()) {
                ops = lazyInit(ops);
                for (Caching caching : cachings) {
                    Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
                    if (cachingOps != null) {
                        ops.addAll(cachingOps);
                    }
                }
            }
     
     
            return ops;
        }
    View Code

      该方法中使用Spring Core模块的AnnotatedElementUtils来得到标注到可被标注对象(在这里包括类和方法)上的指定类型的注释,包括了注释上再打注释等层级结构以及层级属性的合并操作。

      整体逻辑是首先获得标注在方法上的@Cacheable注释集合,并对其中的每个注释调用:

    CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
            CacheableOperation.Builder builder = new CacheableOperation.Builder();
     
            builder.setName(ae.toString());
            builder.setCacheNames(cacheable.cacheNames());
            builder.setCondition(cacheable.condition());
            builder.setUnless(cacheable.unless());
            builder.setKey(cacheable.key());
            builder.setKeyGenerator(cacheable.keyGenerator());
            builder.setCacheManager(cacheable.cacheManager());
            builder.setCacheResolver(cacheable.cacheResolver());
            builder.setSync(cacheable.sync());
     
     
            defaultConfig.applyDefault(builder);
            CacheableOperation op = builder.build();
            validateCacheOperation(ae, op);
     
     
            return op;
        }
    View Code

      利用CacheableOperation.Builder来构建一个CacheableOperation,并添加到Collection<CacheOperation> ops中。接下来对@CacheEvict和@CachePut也执行同样的操作,区别是它们分别使用CacheEvictOperation.Builder和CachePutOperation.Builder来构建CacheEvictOperation和CachePutOperation。  

      另外对于@Caching,调用:

    Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) {
            Collection<CacheOperation> ops = null;
     
     
            Cacheable[] cacheables = caching.cacheable();
            if (!ObjectUtils.isEmpty(cacheables)) {
                ops = lazyInit(ops);
                for (Cacheable cacheable : cacheables) {
                    ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
                }
            }
            CacheEvict[] cacheEvicts = caching.evict();
            if (!ObjectUtils.isEmpty(cacheEvicts)) {
                ops = lazyInit(ops);
                for (CacheEvict cacheEvict : cacheEvicts) {
                    ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
                }
            }
            CachePut[] cachePuts = caching.put();
            if (!ObjectUtils.isEmpty(cachePuts)) {
                ops = lazyInit(ops);
                for (CachePut cachePut : cachePuts) {
                    ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
                }
            }
     
     
            return ops;
        }
    View Code

      其中对caching的cacheable、evict及put属性对应的各组@Cacheable、@CacheEvict及@CachePut注释分别调用前面介绍的执行逻辑来构建相应的CacheOperation并添加到Collection<CacheOperation> ops中,然后将其返回。

      当首次调用某方法执行上述的解析操作后,AnnotationCacheOperationSource会将其缓存起来,后续再调用该方法时会直接从缓存中得到该方法对应的Collection<CacheOperation> ops以增加效率。

    4.2、根据CacheOperation执行核心Cache业务逻辑

      对于打了Cache相关注释的类,在创建其bean的时候已经由Spring AOP为其创建代理增强,并将BeanFactoryCacheOperationSourceAdvisor加入其代理中。

      当调用其方法的时候会通过代理执行到BeanFactoryCacheOperationSourceAdvisor定义的切面。该切面是一个PointcutAdvisor,在SpringAOP底层框架DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice方法中使用

    pointcutAdvisor.getPointcut().getMethodMatcher().matches(method, targetClass))

      来判断被调用方法是否匹配切点逻辑,如果匹配就执行其拦截器中的逻辑,BeanFactoryCacheOperationSourceAdvisor中注册的拦截器是CacheInterceptor,其执行逻辑为:

    @Override
        public Object invoke(final MethodInvocation invocation) throws Throwable {
            Method method = invocation.getMethod();
     
     
            CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
                @Override
                public Object invoke() {
                    try {
                        return invocation.proceed();
                    }
                    catch (Throwable ex) {
                        throw new ThrowableWrapper(ex);
                    }
                }
            };
     
     
            try {
                return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
            }
            catch (CacheOperationInvoker.ThrowableWrapper th) {
                throw th.getOriginal();
            }
        }
    View Code

      其中Spring AOP底层调用该方法时传递来的参数MethodInvocation invocation是一个把调用方法method与其对应的切面拦截器interceptors组装成类似于ChainFilter一样的结构。

      CacheOperationInvoker回调接口的意义是将决定切面逻辑与实际调用方法顺序的权利转交给CacheAspectSupport的execute方法。

      该逻辑中调用了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, method, new CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
     
     
            return invoker.invoke();
        }
    View Code

      该方法首先通过前面描述的AnnotationCacheOperationSource.getCacheOperations(method, targetClass)来获得调用方法上的Collection<CacheOperation>,然后将其和调用方法method、方法参数args、目标对象target、目标类targetClass一起创建CacheOperationContexts。

    public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
                    Object[] args, Object target, Class<?> targetClass) {
     
     
                for (CacheOperation operation : operations) {
                    this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
                }
                this.sync = determineSyncFlag(method);
        }
    View Code

      其中,为每个operation分别创建CacheOperationContext:

        protected CacheOperationContext getOperationContext(
                CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
     
     
            CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
            return new CacheOperationContext(metadata, args, target);
        }
    View Code

      获取CacheOperationMetadata metadata时的较重要的动作就是获取CacheOperation中用String名称定义的CacheResolver和KeyGenerator的bean。

      然后在创建CacheOperationContext时使用CacheResolver bean获得cache的信息:

        public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
                this.metadata = metadata;
                this.args = extractArgs(metadata.method, args);
                this.target = target;
                this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
                this.cacheNames = createCacheNames(this.caches);
                this.methodCacheKey = new AnnotatedElementKey(metadata.method, metadata.targetClass);
        }
    View Code

      在创建完上下文CacheOperationContexts后,调用SpringCache真正的核心业务逻辑:

    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)

      该方法执行逻辑如下:

      #首先判断是否需要在方法调用前执行缓存清除:

    processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); 

      其作用是判断是否需要在方法调用前执行缓存清除。判断是否存在beforeInvocation==true并且condition符合条件的@CacheEvict注释,如果存在则最终执行方法:

        private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) {
            Object key = null;
            for (Cache cache : context.getCaches()) {
                if (operation.isCacheWide()) {
                    logInvalidating(context, operation, null);
                    doClear(cache);
                }
                else {
                    if (key == null) {
                        key = context.generateKey(result);
                    }
                    logInvalidating(context, operation, key);
                    doEvict(cache, key);
                }
            }
        }
    View Code

      对于注释中定义的每一个cache都根据allEntries是否为true执行其clear()方法或evict(key)方法来清除全部或部分缓存。

      #然后检查是否能得到一个符合条件的缓存值:

    Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 

      其中调用了cache.get(key).

      #随后如果Cacheable miss(没有获取到缓存),就会创建一个对应的CachePutRequest并收集起来:

        List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
            if (cacheHit == null) {
                collectPutRequests(contexts.get(CacheableOperation.class),
                        CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

      注意,此时方法尚未执行,因此第二个参数为CacheOperationExpressionEvaluator.NO_RESULT,意思是当前上下文中#result不存在。

      #接下来判断返回缓存值还是实际调用方法的结果

      如果得到了缓存,并且CachePutRequests为空并且不含有符合条件(condition match)的@CachePut注释,那么就将returnValue赋值为缓存值;否则实际执行方法,并将returnValue赋值为方法返回值。

            Object cacheValue;
            Object returnValue;
     
     
            if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
                // If there are no put requests, just use the cache hit
                cacheValue = cacheHit.get();
                if (method.getReturnType() == javaUtilOptionalClass &&
                        (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
                    returnValue = OptionalUnwrapper.wrap(cacheValue);
                }
                else {
                    returnValue = cacheValue;
                }
            }
            else {
                // Invoke the method if we don't have a cache hit
                returnValue = invokeOperation(invoker);
                if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
                    cacheValue = OptionalUnwrapper.unwrap(returnValue);
                }
                else {
                    cacheValue = returnValue;
                }
            }
    View Code

      #方法调用后收集@CachePut明确定义的CachePutRequest

      收集符合条件的@CachePut定义的CachePutRequest,并添加到上面的cachePutRequests中:

    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

      注意,此时处于方法调用后,返回结果已经存在了,因此在condition定义中可以使用上下文#result。 

      #执行CachePutRequest将符合条件的数据写入缓存

      对于上面收集到的cachePutRequests,逐个调用其apply(cacheValue)方法:

    for (CachePutRequest cachePutRequest : cachePutRequests) {
                cachePutRequest.apply(cacheValue);
        }

      其中,CachePutRequest.apply方法首先判断unless条件,unless不符合的时候才会对operationContext中的每一个cache执行put动作:

        public void apply(Object result) {
                if (this.context.canPutToCache(result)) {
                    for (Cache cache : this.context.getCaches()) {
                        doPut(cache, this.key, result);
                    }
                }
        }

      判断是否执行put动作的方法如下:

        protected boolean canPutToCache(Object value) {
                String unless = "";
                if (this.metadata.operation instanceof CacheableOperation) {
                    unless = ((CacheableOperation) this.metadata.operation).getUnless();
                }
                else if (this.metadata.operation instanceof CachePutOperation) {
                    unless = ((CachePutOperation) this.metadata.operation).getUnless();
                }
                if (StringUtils.hasText(unless)) {
                    EvaluationContext evaluationContext = createEvaluationContext(value);
                    return !evaluator.unless(unless, this.methodCacheKey, evaluationContext);
                }
                return true;
            }
    View Code

      #最后判断是否需要在方法调用后执行缓存清除:

    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

      与步骤(1)相对应,其作用是判断是否需要在方法调用后执行缓存清除。判断是否存在beforeInvocation==false并且condition符合条件的@CacheEvict注释,注意此时上下文中结果#result是可用的。如果存在则最终执行缓存清除逻辑。

    小结:

    1、首先执行@CacheEvict(如果beforeInvocation=true且condition通过),如果allEntries=true,则清空所有  
    2、然后收集@Cacheable并检查是否能得到一个符合条件的缓存值
    3、如果@Cacheable的condition通过,并且key对应的数据不在缓存中,就创建一个CachePutRequest实例放入cachePutRequests中  
    4、如果得到了缓存值并且cachePutRequests为空并且没有符合条件的@CachePut操作,那么将returnValue=缓存数据 
    5、如果没有找到缓存,那么实际执行方法调用,并把返回结果放入returnValue  
    6、收集符合条件的@CachePut操作(此时是方法执行后,condition上下文中#result可用),并放入cachePutRequests  
    7、执行cachePutRequests,将数据写入缓存(unless为空或者unless解析结果为false);  
    8、执行@CacheEvict(如果beforeInvocation=false且condition通过),如果allEntries=true,则清空所有  



  • 相关阅读:
    能用HTML/CSS解决的问题,就不要用JS
    跨域
    从输入url到页面展示到底发生了什么
    hosts 文件
    了解Web及网络基础
    hybrid
    组件化和 React
    MVVM 和 VUE
    虚拟 DOM
    ES6模块化与常用功能
  • 原文地址:https://www.cnblogs.com/bjlhx/p/9167660.html
Copyright © 2020-2023  润新知