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

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

前言


本文算是了解缓存注解原理的先行文章,因为它抽象出来的模块类比较多,所以做这篇文章进行关键类的打点。

若我们需要扩展缓存注解的能力,对这些抽象是非常有必要深入了解的~


Spring内置的三大注解缓存是:


  1. Cacheable:缓存
  2. CacheEvict:删除缓存
  3. CachePut:更新缓存


CacheOperation:缓存操作


它是缓存操作的基类。我们知道不同的缓存注解,都有不同的缓存操作并且注解内的属性也挺多,此类就是对缓存操作的抽象


// @since 3.1  三大缓存注解属性的基类~~~
public abstract class CacheOperation implements BasicOperation {
  private final String name;
  private final Set<String> cacheNames;
  private final String key;
  private final String keyGenerator;
  private final String cacheManager;
  private final String cacheResolver;
  private final String condition;
  // 把toString()作为一个成员变量记着了   因为调用的次数太多
  private final String toString;
  // 构造方法是protected 的~~~  入参一个Builder来对各个属性赋值
  // builder方式是@since 4.3提供的,显然Spring4.3对这部分进行了改造~
  protected CacheOperation(Builder b) {
    this.name = b.name;
    this.cacheNames = b.cacheNames;
    this.key = b.key;
    this.keyGenerator = b.keyGenerator;
    this.cacheManager = b.cacheManager;
    this.cacheResolver = b.cacheResolver;
    this.condition = b.condition;
    this.toString = b.getOperationDescription().toString();
  }
  ... // 省略所有的get/set(其实builder里都只有set方法~~~)
  // 它的public静态抽象内部类  Builder  简单的说,它是构建一个CacheOperation的构建器
  public abstract static class Builder {
    private String name = "";
    private Set<String> cacheNames = Collections.emptySet();
    private String key = "";
    private String keyGenerator = "";
    private String cacheManager = "";
    private String cacheResolver = "";
    private String condition = "";
    // 省略所有的get/set
    // 抽象方法  自行实现~
    public abstract CacheOperation build();
  }
}
// @since 4.1 
public interface BasicOperation {
  Set<String> getCacheNames();
}


它的继承图谱如下

image.png


对应着三个注解,Spring提供了三种不同的操作实现。基类CacheOperation里封装的是三哥们都共有的属性,所以实现类里处理各自的个性化属性~~~~


// @since 3.1
public class CacheableOperation extends CacheOperation {
  @Nullable
  private final String unless;
  private final boolean sync;
  public CacheableOperation(CacheableOperation.Builder b) {
    super(b);
    this.unless = b.unless;
    this.sync = b.sync;
  }
  ... // 省略get方法(无set方法哦)
  // @since 4.3
  public static class Builder extends CacheOperation.Builder {
    @Nullable
    private String unless;
    private boolean sync;
    ... // 生路set方法(没有get方法哦~)
    // Spring4.3抽象的这个技巧还是不错的,此处传this进去即可
    @Override
    public CacheableOperation build() {
      return new CacheableOperation(this);
    }
    @Override
    protected StringBuilder getOperationDescription() {
      StringBuilder sb = super.getOperationDescription();
      sb.append(" | unless='");
      sb.append(this.unless);
      sb.append("'");
      sb.append(" | sync='");
      sb.append(this.sync);
      sb.append("'");
      return sb;
    }
  }
}

因为三哥们的实现完全一样,所以此处只需要举CacheableOperation这一个例子即可


CacheOperationSource:缓存属性源



缓存属性源。该接口被CacheInterceptor它使用。它能够获取到Method上所有的缓存操作集合:


// @since 3.1
public interface CacheOperationSource {
  // 返回此Method方法上面所有的缓存操作~~~~CacheOperation 集合
  // 显然一个Method上可以对应有多个缓存操作~~~~
  @Nullable
  Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass);
}


它的继承树如下:


image.png


这里面有最为重要的AnnotationCacheOperationSource,以及还有NameMatchCacheOperationSource根据名称匹配的实现。


NameMatchCacheOperationSource


根据方法名称来匹配看看作用在此方法上的缓存操作有哪些~(不需要注解了)


// @since 3.1
public class NameMatchCacheOperationSource implements CacheOperationSource, Serializable {
  /** Keys are method names; values are TransactionAttributes. */
  private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>();
  // 你配置的时候,可以调用此方法。这里使用的也是add方法
  // 先拦截这个方法,然后看看这个方法有木有匹配的缓存操作,有点想AOP的配置
  public void setNameMap(Map<String, Collection<CacheOperation>> nameMap) {
    nameMap.forEach(this::addCacheMethod);
  }
  public void addCacheMethod(String methodName, Collection<CacheOperation> ops) {
    // 输出debug日志
    if (logger.isDebugEnabled()) {
      logger.debug("Adding method [" + methodName + "] with cache operations [" + ops + "]");
    }
    this.nameMap.put(methodName, ops);
  }
  // 这部分逻辑挺简单,就是根据方法去Map类里匹配到一个最为合适的Collection<CacheOperation>
  @Override
  @Nullable
  public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    // look for direct name match
    String methodName = method.getName();
    Collection<CacheOperation> ops = this.nameMap.get(methodName);
    // 若不是null就直接return了~  首次进来,都会进入到这个逻辑~~~~
    if (ops == null) {
      // Look for most specific name match. // 找打一个最适合的  最匹配的
      String bestNameMatch = null;
      // 遍历所有外部已经制定进来了的方法名们~~~~
      for (String mappedName : this.nameMap.keySet()) {
        // isMatch就是Ant风格匹配~~(第一步)  光Ant匹配上了还不算
        // 第二步:bestNameMatch=null或者
        if (isMatch(methodName, mappedName)
            && (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
          // mappedName为匹配上了的名称~~~~ 给bestNameMatch 赋值  
          // 继续循环,最终找到一个最佳的匹配~~~~
          ops = this.nameMap.get(mappedName);
          bestNameMatch = mappedName;
        }
      }
    }
    return ops;
  }
  protected boolean isMatch(String methodName, String mappedName) {
    return PatternMatchUtils.simpleMatch(mappedName, methodName);
  }
  ...
}


这个Collection<CacheOperation>的匹配规则很简单,就是使用methodName进行匹配的,支持Ant风格匹配模式。

AbstractFallbackCacheOperationSource


这个抽象方法主要目的是:让缓存注解(当然此抽象类并不要求一定是注解,别的方式也成)既能使用在类上,也能使用在方法上。方法上没找到,就Fallback到类上去找。


并且它还支持把注解写在接口上,哪怕你只是一个JDK动态代理的实现而已。比如我们的MyBatis Mapper接口上也是可以直接使用缓存注解的~


// @since 3.1
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
  private static final Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
  // 这个Map初始值可不小,因为它要缓存所有的Method
  private final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<>(1024);
  @Override
  @Nullable
  public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    // 如果这个方法是Object的方法,那就不考虑缓存操作~~~
    if (method.getDeclaringClass() == Object.class) {
      return null;
    }
    // 以method和Class作为上面缓存Map的key
    Object cacheKey = getCacheKey(method, targetClass);
    Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
    if (cached != null) {
      return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
    }
    // 核心处理逻辑:包括AnnotationCacheOperationSource的主要逻辑也是沿用的这个模版
    else {
      // computeCacheOperations计算缓存属性,这个方法是本类的灵魂,见下面
      Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
      if (cacheOps != null) {
        if (logger.isTraceEnabled()) {
          logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
        }
        this.attributeCache.put(cacheKey, cacheOps);
      } else { // 若没有标注属性的方法,用NULL_CACHING_ATTRIBUTE占位~ 不用null值哦~~~~ Spring内部大都不直接使用null
        this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
      }
      return cacheOps;
    }
  }
  @Nullable
  private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    // allowPublicMethodsOnly()默认是false(子类复写后的默认值已经写为true了)
    // 也就是说:缓存注解只能标注在public方法上~~~~不接收别非public方法~~~
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    // First try is the method in the target class.
    // 第一步:先去该方法上找,看看有木有啥缓存属性  有就返回
    Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
    if (opDef != null) {
      return opDef;
    }
    // Second try is the caching operation on the target class.
    // 第二步:方法上没有,就再去方法所在的类上去找。
    // isUserLevelMethod:我们自己书写的方法(非自动生成的) 才直接return,否则继续处理
    opDef = findCacheOperations(specificMethod.getDeclaringClass());
    if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
      return opDef;
    }
    // 他俩不相等,说明method这个方法它是标注在接口上的,这里也给与了支持
    // 此处透露的性息:我们的缓存注解也可以标注在接口方法上,比如MyBatis的接口上都是ok的~~~~
    if (specificMethod != method) {
      // Fallback is to look at the original method.
      opDef = findCacheOperations(method);
      if (opDef != null) {
        return opDef;
      }
      // Last fallback is the class of the original method.
      opDef = findCacheOperations(method.getDeclaringClass());
      if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
        return opDef;
      }
    }
    return null;
  }
  @Nullable
  protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
  @Nullable
  protected abstract Collection<CacheOperation> findCacheOperations(Method method);
  protected boolean allowPublicMethodsOnly() {
    return false;
  }
}


该抽象类提供的能力是:你的缓存属性可以放在方法上,方法上没有的话会去类上找,它有大名鼎鼎的实现类:AnnotationCacheOperationSource


AnnotationCacheOperationSource

从名字就可以看出,它是和缓存注解有关的缓存属性源。它能够处理上述的三大缓存注解。


// @since 3.1
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
  // 是否只允许此注解标注在public方法上???下面有设置值为true
  // 该属性只能通过构造函数赋值
  private final boolean publicMethodsOnly;
  private final Set<CacheAnnotationParser> annotationParsers;
  // 默认就设置了publicMethodsOnly=true
  public AnnotationCacheOperationSource() {
    this(true);
  }
  public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
    this.publicMethodsOnly = publicMethodsOnly;
    this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
  }
  ...
  // 也可以自己来实现注解的解析器。(比如我们要做自定义注解的话,可以这么搞~~~)
  public AnnotationCacheOperationSource(CacheAnnotationParser... annotationParsers) {
    this.publicMethodsOnly = true;
    this.annotationParsers = new LinkedHashSet<>(Arrays.asList(annotationParsers));
  }
  // 这两个方法:核心事件都交给了CacheAnnotationParser.parseCacheAnnotations方法
  @Override
  @Nullable
  protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
    return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
  }
  @Override
  @Nullable
  protected Collection<CacheOperation> findCacheOperations(Method method) {
    return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
  }
  // CacheOperationProvider就是一个函数式接口而已~~~类似Function~
  // 这块determineCacheOperations() + CacheOperationProvider接口的设计还是很巧妙的  可以学习一下
  @Nullable
  protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
    Collection<CacheOperation> ops = null;
    // 调用我们设置进来的所有的CacheAnnotationParser一个一个的处理~~~
    for (CacheAnnotationParser annotationParser : this.annotationParsers) {
      Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);
      // 理解这一块:说明我们方法上、类上是可以标注N个注解的,都会同时生效~~~最后都会combined 
      if (annOps != null) {
        if (ops == null) {
          ops = annOps;
        } else {
          Collection<CacheOperation> combined = new ArrayList<>(ops.size() + annOps.size());
          combined.addAll(ops);
          combined.addAll(annOps);
          ops = combined;
        }
      }
    }
    return ops;
  }
}

它专用于处理三大缓存注解,就是获取标注在方法上的缓存注解们。

另外需要说明的是:若你想扩展你自己的缓存注解(比如加上超时时间TTL),你的处理器可以可以继承自AnnotationCacheOperationSource,然后进行自己的扩展吧~


可以看到解析缓存注解这块,依托的一个处理器是:CacheAnnotationParser,下面继续介绍。

相关文章
|
6月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1119 5
|
6月前
|
存储 缓存 Java
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
缓存是提升应用性能的重要技术,Spring框架提供了丰富的缓存注解,如`@Cacheable`、`@CacheEvict`等,帮助开发者简化缓存管理。本文介绍了如何在Spring中配置缓存管理器,使用缓存注解优化数据访问,并探讨了缓存的最佳实践,以提升系统响应速度与可扩展性。
383 0
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
|
7月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
2043 10
|
10月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
323 32
|
8月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
652 0
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
641 73
|
11月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
403 0
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
306 10
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
534 4
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
450 3