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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 玩转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

相关文章
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
23天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
61 5
|
26天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
86 2
|
3月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
206 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
66 2
|
3月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
43 1
|
4月前
|
缓存 Java Spring
Spring缓存实践指南:从入门到精通的全方位攻略!
【8月更文挑战第31天】在现代Web应用开发中,性能优化至关重要。Spring框架提供的缓存机制可以帮助开发者轻松实现数据缓存,提升应用响应速度并减少服务器负载。通过简单的配置和注解,如`@Cacheable`、`@CachePut`和`@CacheEvict`,可以将缓存功能无缝集成到Spring应用中。例如,在配置文件中启用缓存支持并通过`@Cacheable`注解标记方法即可实现缓存。此外,合理设计缓存策略也很重要,需考虑数据变动频率及缓存大小等因素。总之,Spring缓存机制为提升应用性能提供了一种简便快捷的方式。
54 0
|
4月前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
68 0
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
|
4月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中数据的问题如何解决
Java本地高性能缓存实践问题之使用@CachePut注解来更新缓存中数据的问题如何解决