玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点【享学Spring】(中)

简介: 玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点【享学Spring】(中)

CompositeCacheOperationSource

又是Composite组合模式,此设计模式在Spring中大量存在。


public class CompositeCacheOperationSource implements CacheOperationSource, Serializable {
  // 这里用的数组,表面只能赋值一次  并且只能通过构造函数赋值
  private final CacheOperationSource[] cacheOperationSources;
  public CompositeCacheOperationSource(CacheOperationSource... cacheOperationSources) {
    this.cacheOperationSources = cacheOperationSources;
  }
  public final CacheOperationSource[] getCacheOperationSources() {
    return this.cacheOperationSources;
  }
  @Override
  @Nullable
  public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    Collection<CacheOperation> ops = null;
    for (CacheOperationSource source : this.cacheOperationSources) {
      Collection<CacheOperation> cacheOperations = source.getCacheOperations(method, targetClass);
      if (cacheOperations != null) {
        if (ops == null) {
          ops = new ArrayList<>();
        }
        ops.addAll(cacheOperations);
      }
    }
    return ops;
  }
}


对它的解释就是,所以组合进取的属性源,最终都会叠加生效

注意:它还是个抽象类哦~~~它的唯一实现如下(匿名内部类):


public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
  @Nullable
  private CacheOperationSource cacheOperationSource;
  private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
    @Override
    @Nullable
    protected CacheOperationSource getCacheOperationSource() {
      return cacheOperationSource;
    }
  };
  ...
}


CacheAnnotationParser:缓存注解解析器


解析Spring三大注解缓存的策略接口~~~


// @since 3.1
public interface CacheAnnotationParser {
  @Nullable
  Collection<CacheOperation> parseCacheAnnotations(Class<?> type);
  @Nullable
  Collection<CacheOperation> parseCacheAnnotations(Method method);
}


Spring内建了一个唯一实现类:SpringCacheAnnotationParser。它对把注解解析为缓存属性非常的重要。


// @since 3.1
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
  private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
  // 它能处理的注解类型,一目了然
  static {
    CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
    CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
    CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
    CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
  }
  // 处理class和Method
  // 使用DefaultCacheConfig,把它传给parseCacheAnnotations()  来给注解属性搞定默认值
  // 至于为何自己要新new一个呢???其实是因为@CacheConfig它只能放在类上呀~~~(传Method也能获取到类)
  // 返回值都可以为null(没有标注此注解方法、类  那肯定返回null啊)
  @Override
  @Nullable
  public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
    DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
    return parseCacheAnnotations(defaultConfig, type);
  }
  @Override
  @Nullable
  public Collection<CacheOperation> parseCacheAnnotations(Method method) {
    DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
    return parseCacheAnnotations(defaultConfig, method);
  }
  // 找到方法/类上的注解们~~~
  private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
    // 第三个参数传的false:表示接口的注解它也会看一下~~~
    Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
    // 若出现接口方法里也标了,实例方法里也标了,那就继续处理。让以实例方法里标注的为准~~~
    if (ops != null && ops.size() > 1) {
      // More than one operation found -> local declarations override interface-declared ones...
      Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
      if (localOps != null) {
        return localOps;
      }
    }
    return ops;
  }
  private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
    // localOnly=true,只看自己的不看接口的。false表示接口的也得看~
    Collection<? extends Annotation> anns = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
    if (anns.isEmpty()) {
      return null;
    }
    // 这里为和写个1???因为绝大多数情况下,我们都只会标注一个注解~~
    final Collection<CacheOperation> ops = new ArrayList<>(1);
    // 这里的方法parseCacheableAnnotation/parsePutAnnotation等 说白了  就是把注解属性,转换封装成为`CacheOperation`对象~~
    // 注意parseCachingAnnotation方法,相当于~把注解属性转换成了CacheOperation对象  下面以它为例介绍
    anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
        ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
    anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
        ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
    anns.stream().filter(ann -> ann instanceof CachePut).forEach(
        ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
    anns.stream().filter(ann -> ann instanceof Caching).forEach(
        ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
    return ops;
  }
  // CacheableOperation是抽象类CacheOperation的子类~
  private CacheableOperation parseCacheableAnnotation(
      AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
    // 这个builder是CacheOperation.Builder的子类,父类规定了所有注解通用的一些属性~~~
    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());
    // DefaultCacheConfig是本类的一个内部类,来处理buider,给他赋值默认值~~~  比如默认的keyGenerator等等
    defaultConfig.applyDefault(builder);
    CacheableOperation op = builder.build();
    validateCacheOperation(ae, op); // 校验。key和KeyGenerator至少得有一个   CacheManager和CacheResolver至少得配置一个
    return op;
  }
  ... // 解析其余注解略,一样的。
  // 它其实就是把三三者聚合了,一个一个的遍历。所以它最后一个参数传的ops,在内部进行add
  private void parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {
    Cacheable[] cacheables = caching.cacheable();
    for (Cacheable cacheable : cacheables) {
      ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
    }
    CacheEvict[] cacheEvicts = caching.evict();
    for (CacheEvict cacheEvict : cacheEvicts) {
      ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
    }
    CachePut[] cachePuts = caching.put();
    for (CachePut cachePut : cachePuts) {
      ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
    }
  }
  // 设置默认值的私有静态内部类
  private static class DefaultCacheConfig {
    private final Class<?> target;
    @Nullable
    private String[] cacheNames;
    @Nullable
    private String keyGenerator;
    @Nullable
    private String cacheManager;
    @Nullable
    private String cacheResolver;
    private boolean initialized = false;
    // 唯一构造函数~
    public DefaultCacheConfig(Class<?> target) {
      this.target = target;
    }
    public void applyDefault(CacheOperation.Builder builder) {
      // 如果还没初始化过,就进行初始化(毕竟一个类上有多个方法,这种默认通用设置只需要来一次即可)
      if (!this.initialized) {
        // 找到类上、接口上的@CacheConfig注解  它可以指定本类使用的cacheNames和keyGenerator和cacheManager和cacheResolver
        CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);
        if (annotation != null) {
          this.cacheNames = annotation.cacheNames();
          this.keyGenerator = annotation.keyGenerator();
          this.cacheManager = annotation.cacheManager();
          this.cacheResolver = annotation.cacheResolver();
        }
        this.initialized = true;
      }
      // 下面方法一句话总结:方法上没有指定的话,就用类上面的CacheConfig配置
      if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
        builder.setCacheNames(this.cacheNames);
      }
      if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) && StringUtils.hasText(this.keyGenerator)) {
        builder.setKeyGenerator(this.keyGenerator);
      }
      if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
        // One of these is set so we should not inherit anything
      } else if (StringUtils.hasText(this.cacheResolver)) {
        builder.setCacheResolver(this.cacheResolver);
      } else if (StringUtils.hasText(this.cacheManager)) {
        builder.setCacheManager(this.cacheManager);
      }
    }
  }
}

经过这一番解析后,三大缓存注解,最终都被收集到CacheOperation里来了,这也就和CacheOperationSource缓存属性源接口的功能照应了起来。

CacheOperationInvocationContext:执行上下文


它代表缓存执行时的上下文。

//@since 4.1  注意泛型O extends BasicOperation
public interface CacheOperationInvocationContext<O extends BasicOperation> {
  // 缓存操作属性CacheOperation
  O getOperation();
  // 目标类、目标方法
  Object getTarget();
  Method getMethod();
  // 方法入参们
  Object[] getArgs();
}


它只有一个CacheAspectSupport内部类实现CacheOperationContext,此处也搬上来吧:

protected class CacheOperationContext implements CacheOperationInvocationContext<CacheOperation> {
  // 它里面包含了CacheOperation、Method、Class、Method targetMethod;(注意有两个Method)、AnnotatedElementKey、KeyGenerator、CacheResolver等属性
  // this.method = BridgeMethodResolver.findBridgedMethod(method);
  // this.targetMethod = (!Proxy.isProxyClass(targetClass) ? AopUtils.getMostSpecificMethod(method, targetClass)  : this.method);
  // this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
    private final CacheOperationMetadata metadata;
    private final Object[] args;
    private final Object target;
    private final Collection<? extends Cache> caches;
    private final Collection<String> cacheNames;
    @Nullable
    private Boolean conditionPassing; // 标志CacheOperation.conditon是否是true:表示通过  false表示未通过
    public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
        this.metadata = metadata;
        this.args = extractArgs(metadata.method, args);
        this.target = target;
        // 这里方法里调用了cacheResolver.resolveCaches(context)方法来得到缓存们~~~~  CacheResolver
        this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
        // 从caches拿到他们的names们
        this.cacheNames = createCacheNames(this.caches);
    }
  ... // 省略get/set
    protected boolean isConditionPassing(@Nullable Object result) {
        if (this.conditionPassing == null) {
      // 如果配置了并且还没被解析过,此处就解析condition条件~~~
            if (StringUtils.hasText(this.metadata.operation.getCondition())) {
        // 执行上下文:此处就不得不提一个非常重要的它了:CacheOperationExpressionEvaluator
        // 它代表着缓存操作中SpEL的执行上下文~~~  具体可以先参与下面的对它的介绍
        // 解析conditon~
                EvaluationContext evaluationContext = createEvaluationContext(result);
                this.conditionPassing = evaluator.condition(this.metadata.operation.getCondition(),
                        this.metadata.methodKey, evaluationContext);
            } else {
                this.conditionPassing = true;
            }
        }
        return this.conditionPassing;
    }
  // 解析CacheableOperation和CachePutOperation好的unless
    protected boolean canPutToCache(@Nullable 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.metadata.methodKey, evaluationContext);
        }
        return true;
    }
  // 这里注意:生成key  需要注意步骤。
  // 若配置了key(非空串):那就作为SpEL解析出来
  // 否则走keyGenerator去生成~~~(所以你会发现,即使咱们没有配置key和keyGenerator,程序依旧能正常work,只是生成的key很长而已~~~)
  // (keyGenerator你可以能没配置????)
  // 若你自己没有手动指定过KeyGenerator,那会使用默认的SimpleKeyGenerator 它的实现比较简单
  // 其实若我们自定义KeyGenerator,我觉得可以继承自`SimpleKeyGenerator `,而不是直接实现接口~~~
    @Nullable
    protected Object generateKey(@Nullable Object result) {
        if (StringUtils.hasText(this.metadata.operation.getKey())) {
            EvaluationContext evaluationContext = createEvaluationContext(result);
            return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
        }
        // key的优先级第一位,没有指定采用生成器去生成~
        return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
    }
    private EvaluationContext createEvaluationContext(@Nullable Object result) {
        return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args,
                this.target, this.metadata.targetClass, this.metadata.targetMethod, result, beanFactory);
    }
  ...
}

CacheOperationExpressionEvaluator:缓存操作执行器


在这之前,在讲解发布订阅、事件机制的文章中:

【小家Spring】从Spring中的(ApplicationEvent)事件驱动机制出发,聊聊【观察者模式】【监听者模式】【发布订阅模式】【消息队列MQ】【EventSourcing】…

讲到过EventExpressionEvaluator,它在解析@EventListener注解的condition属性的时候被使用到,它也继承自抽象父类CachedExpressionEvaluator:

image.png


可见本类也是抽象父类的一个实现,是不是顿时有种熟悉感了?

// @since 3.1   注意抽象父类CachedExpressionEvaluator在Spring4.2才有
// CachedExpressionEvaluator里默认使用的解析器是:SpelExpressionParser  以及
// 还准备了一个ParameterNameDiscoverer  可以交给执行上文CacheEvaluationContext使用
class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {
  // 注意这两个属性是public的  在CacheAspectSupport里都有使用~~~
  public static final Object NO_RESULT = new Object();
  public static final Object RESULT_UNAVAILABLE = new Object();
  public static final String RESULT_VARIABLE = "result";
  private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<>(64);
  private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
  private final Map<ExpressionKey, Expression> unlessCache = new ConcurrentHashMap<>(64);
  // 这个方法是创建执行上下文。能给解释:为何可以使用#result这个key的原因
  // 此方法的入参确实不少:8个
  public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
      Method method, Object[] args, Object target, Class<?> targetClass, Method targetMethod,
      @Nullable Object result, @Nullable BeanFactory beanFactory) {
    // root对象,此对象的属性决定了你的#root能够取值的范围,具体下面有贴出此类~
    CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches, method, args, target, targetClass);
    // 它继承自MethodBasedEvaluationContext,已经讲解过了,本文就不继续深究了~
    CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject, targetMethod, args, getParameterNameDiscoverer());
    if (result == RESULT_UNAVAILABLE) {
      evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
    } else if (result != NO_RESULT) {
      // 这一句话就是为何我们可以使用#result的核心原因~
      evaluationContext.setVariable(RESULT_VARIABLE, result);
    }
    // 从这里可知,缓存注解里也是可以使用容器内的Bean的~
    if (beanFactory != null) {
      evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
    }
    return evaluationContext;
  }
  // 都有缓存效果哦,因为都继承自抽象父类CachedExpressionEvaluator嘛
  // 抽象父类@since 4.2才出来,就是给解析过程提供了缓存的效果~~~~(注意此缓存非彼缓存)
  // 解析key的SpEL表达式
  @Nullable
  public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
    return getExpression(this.keyCache, methodKey, keyExpression).getValue(evalContext);
  }
  // condition和unless的解析结果若不是bool类型,统一按照false处理~~~~
  public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
    return (Boolean.TRUE.equals(getExpression(this.conditionCache, methodKey, conditionExpression).getValue(evalContext, Boolean.class)));
  }
  public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
    return (Boolean.TRUE.equals(getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, Boolean.class)));
  }
  /**
   * Clear all caches.
   */
  void clear() {
    this.keyCache.clear();
    this.conditionCache.clear();
    this.unlessCache.clear();
  }
}
// #root可取的值,参考CacheExpressionRootObject这个对象的属性
// @since 3.1
class CacheExpressionRootObject {
  // 可见#root.caches竟然都是阔仪的~~~
  private final Collection<? extends Cache> caches;
  private final Method method;
  private final Object[] args;
  private final Object target;
  private final Class<?> targetClass;
  // 省略所有的get/set(其实只有get方法)
}

缓存操作的执行器,能让你深刻的理解到#root#result的使用,并且永远忘记不了了。

CacheResolver


其名字已经暗示了其是Cache解析器,用于根据实际情况来动态解析使用哪个Cache,它是Spring4.1提供的新特性。


// @since 4.1
@FunctionalInterface
public interface CacheResolver {
  // 根据执行上下文,拿到最终的缓存Cache集合
  // CacheOperationInvocationContext:执行上下文
  Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);
}


它有一些内置实现如下:

image.png

相关文章
|
5天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
1天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
4天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
17 2
|
5天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
15 2
|
10天前
|
XML 存储 缓存
Spring缓存是如何实现的?如何扩展使其支持过期删除功能?
总之,Spring的缓存抽象提供了一种方便的方式来实现缓存功能,并且可以与各种缓存提供商集成以支持不同的过期策略。您可以根据项目的具体需求选择适合的方式来配置和扩展Spring缓存功能。
15 0
|
11天前
|
Java Spring
springboot自带的@Scheduled注解开启定时任务
springboot自带的@Scheduled注解开启定时任务
|
12天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
22 0
|
14天前
|
XML JSON Java
【SpringBoot】springboot常用注解
【SpringBoot】springboot常用注解
|
5天前
|
存储 消息中间件 缓存
Redis缓存技术详解
【5月更文挑战第6天】Redis是一款高性能内存数据结构存储系统,常用于缓存、消息队列、分布式锁等场景。其特点包括速度快(全内存存储)、丰富数据类型、持久化、发布/订阅、主从复制和分布式锁。优化策略包括选择合适数据类型、设置过期时间、使用Pipeline、开启持久化、监控调优及使用集群。通过这些手段,Redis能为系统提供高效稳定的服务。
|
11天前
|
存储 缓存 NoSQL
【Go语言专栏】Go语言中的Redis操作与缓存应用
【4月更文挑战第30天】本文探讨了在Go语言中使用Redis进行操作和缓存应用的方法。文章介绍了Redis作为高性能键值存储系统,用于提升应用性能。推荐使用`go-redis/redis`库,示例代码展示了连接、设置、获取和删除键值对的基本操作。文章还详细阐述了缓存应用的步骤及常见缓存策略,包括缓存穿透、缓存击穿和缓存雪崩的解决方案。利用Redis和合适策略可有效优化应用性能。