前言
本文算是了解缓存注解原理的先行文章,因为它抽象出来的模块类比较多,所以做这篇文章进行关键类的打点。
若我们需要扩展缓存注解的能力,对这些抽象是非常有必要深入了解的~
Spring内置的三大注解缓存是:
- Cacheable:缓存
- CacheEvict:删除缓存
- 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(); }
它的继承图谱如下
对应着三个注解,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); }
它的继承树如下:
这里面有最为重要的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,下面继续介绍。

