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

2019-09-03 15:22:33 浏览数 (1)

前言

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

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

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

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

CacheOperation:缓存操作

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

代码语言:javascript复制
// @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里封装的是三哥们都共有的属性,所以实现类里处理各自的个性化属性

代码语言:javascript复制
// @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上所有的缓存操作集合:

代码语言:javascript复制
// @since 3.1
public interface CacheOperationSource {

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

}

它的继承树如下:

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

NameMatchCacheOperationSource

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

代码语言:javascript复制
// @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接口上也是可以直接使用缓存注解的~

代码语言:javascript复制
// @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

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

代码语言:javascript复制
// @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,下面继续介绍。

CompositeCacheOperationSource

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

代码语言:javascript复制
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;
	}

}

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

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

代码语言:javascript复制
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

	@Nullable
	private CacheOperationSource cacheOperationSource;

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

CacheAnnotationParser:缓存注解解析器

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

代码语言:javascript复制
// @since 3.1
public interface CacheAnnotationParser {
	@Nullable
	Collection<CacheOperation> parseCacheAnnotations(Class<?> type);
	@Nullable
	Collection<CacheOperation> parseCacheAnnotations(Method method);
}

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

代码语言:javascript复制
// @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:执行上下文

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

代码语言:javascript复制
//@since 4.1  注意泛型O extends BasicOperation
public interface CacheOperationInvocationContext<O extends BasicOperation> {

	// 缓存操作属性CacheOperation
	O getOperation();

	// 目标类、目标方法
	Object getTarget();
	Method getMethod();

	// 方法入参们
	Object[] getArgs();
}

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

代码语言:javascript复制
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

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

代码语言:javascript复制
// @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提供的新特性。

代码语言:javascript复制
// @since 4.1
@FunctionalInterface
public interface CacheResolver {

	// 根据执行上下文,拿到最终的缓存Cache集合
	// CacheOperationInvocationContext:执行上下文
	Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);

}

它有一些内置实现如下:

AbstractCacheResolver

具体实现根据调用上下文提供缓存名称集合。

代码语言:javascript复制
// @since 4.1  实现了InitializingBean
public abstract class AbstractCacheResolver implements CacheResolver, InitializingBean {
	
	// 课件它还是依赖于CacheManager的
	@Nullable
	private CacheManager cacheManager;

	// 构造函数统一protected
	protected AbstractCacheResolver() {
	}
	protected AbstractCacheResolver(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}

	// 设置,指定一个CacheManager
	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}
	public CacheManager getCacheManager() {
		Assert.state(this.cacheManager != null, "No CacheManager set");
		return this.cacheManager;
	}
	
	
	// 做了一步校验而已~~~CacheManager 必须存在
	// 这是一个使用技巧哦   自己的在设计框架的框架的时候可以使用~
	@Override
	public void afterPropertiesSet()  {
		Assert.notNull(this.cacheManager, "CacheManager is required");
	}

	@Override
	public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
		// getCacheNames抽象方法,子类去实现~~~~
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
			return Collections.emptyList();
		}


		// 根据cacheNames  去CacheManager里面拿到Cache对象, 作为最终的返回
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
				throw new IllegalArgumentException("Cannot find cache named '"  
						cacheName   "' for "   context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

	// 子类需要实现此抽象方法
	@Nullable
	protected abstract Collection<String> getCacheNames(CacheOperationInvocationContext<?> context);
}

此抽象类一样的,只是模版实现了resolveCaches()方法。抽象方法的实现交给了实现类

SimpleCacheResolver
代码语言:javascript复制
public class SimpleCacheResolver extends AbstractCacheResolver {
	...
	@Override
	protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
		return context.getOperation().getCacheNames();
	}
	... 
}

这个实现太简单了,没啥好说的。

NamedCacheResolver
代码语言:javascript复制
public class NamedCacheResolver extends AbstractCacheResolver {

	@Nullable
	private Collection<String> cacheNames;


	public NamedCacheResolver() {
	}

	public NamedCacheResolver(CacheManager cacheManager, String... cacheNames) {
		super(cacheManager);
		this.cacheNames = new ArrayList<>(Arrays.asList(cacheNames));
	}


	/**
	 * Set the cache name(s) that this resolver should use.
	 */
	public void setCacheNames(Collection<String> cacheNames) {
		this.cacheNames = cacheNames;
	}

	@Override
	protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
		return this.cacheNames;
	}

}

此解析器的特点是,可以自己setCacheNames()

若内建的不符合条件,我们可以自己实现一个自己的**CacheResolver**。比如实现和业务相关的缓存处理器(若Class==某Class,做些特殊的操作之类的)

需要注意的是:即使你配置使用的是CacheResolver,你也必须在配置里提供cacheNames至少一个的,因为毕竟是根据你配置的CacheNames去CacheManager里找(当然,若是你的自定义实现除外

CacheOperationSourcePointcut

Pointcut小伙伴应该不陌生,在AOP章节重点又重点的描述过,我们知道Pointcut直接关乎到是否要生成代理对象,所以此类还是蛮重要的。

代码语言:javascript复制
// @since 3.1 它是个StaticMethodMatcherPointcut  所以方法入参它不关心
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {


	// 如果你这个类就是一个CacheManager,不切入
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		if (CacheManager.class.isAssignableFrom(targetClass)) {
			return false;
		}

		// 获取到当前的缓存属性源~~~getCacheOperationSource()是个抽象方法
		CacheOperationSource cas = getCacheOperationSource();

		// 下面一句话解释为:如果方法/类上标注有缓存相关的注解,就切入进取~~
		// 具体逻辑请参见方法:cas.getCacheOperations();
		return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof CacheOperationSourcePointcut)) {
			return false;
		}
		CacheOperationSourcePointcut otherPc = (CacheOperationSourcePointcut) other;
		return ObjectUtils.nullSafeEquals(getCacheOperationSource(), otherPc.getCacheOperationSource());
	}

	@Override
	public int hashCode() {
		return CacheOperationSourcePointcut.class.hashCode();
	}

	@Override
	public String toString() {
		return getClass().getName()   ": "   getCacheOperationSource();
	}


	/**
	 * Obtain the underlying {@link CacheOperationSource} (may be {@code null}).
	 * To be implemented by subclasses.
	 */
	@Nullable
	protected abstract CacheOperationSource getCacheOperationSource();

}

CacheErrorHandler

处理缓存发生错误时的策略接口。在大多数情况下,提供者抛出的任何异常都应该简单地被抛出到客户机上,但是在某些情况下,基础结构可能需要以不同的方式处理缓存提供者异常。这个时候可以使用此接口来处理

接口内容十分之简单:

代码语言:javascript复制
public interface CacheErrorHandler {
	void handleCacheGetError(RuntimeException exception, Cache cache, Object key);
	void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value);
	void handleCacheEvictError(RuntimeException exception, Cache cache, Object key);
	void handleCacheClearError(RuntimeException exception, Cache cache);	
}

Spring对它唯一内建实现为SimpleCacheErrorHandler,代码我不贴了,全是空实现,所以它是一个Adapter适配器形式的存在。

若你想自己提供CacheErrorHandler,你可以继承自SimpleCacheErrorHandler来弄~~~

AbstractCacheInvoker

它的作用是在进行缓存操作时发生异常时,调用组件CacheErrorHandler来进行处理~

代码语言:javascript复制
// @since 4.1
public abstract class AbstractCacheInvoker {
	//protected属性~
	protected SingletonSupplier<CacheErrorHandler> errorHandler;

	protected AbstractCacheInvoker() {
		this.errorHandler = SingletonSupplier.of(SimpleCacheErrorHandler::new);
	}
	protected AbstractCacheInvoker(CacheErrorHandler errorHandler) {
		this.errorHandler = SingletonSupplier.of(errorHandler);
	}

	// 此处用的obtain方法  它能保证不为null
	public CacheErrorHandler getErrorHandler() {
		return this.errorHandler.obtain();
	}

	@Nullable
	protected Cache.ValueWrapper doGet(Cache cache, Object key) {
		try {
			return cache.get(key);
		} catch (RuntimeException ex) {
			getErrorHandler().handleCacheGetError(ex, cache, key);
			// 只有它有返回值哦  返回null
			return null;  // If the exception is handled, return a cache miss
		}
	}
	... // 省略doPut、doEvict、doClear  处理方式同上
}

可见这个类在Spring4.1提出,专门用于处理异常的,毕竟CacheErrorHandler也是Spring4.1后才有。

总结

本篇文章为讲解缓存注解的深入原理分析进行铺垫,所以密切关注这篇文章:

【小家Spring】玩转Spring Cache — @Cacheable/@CachePut/@CacheEvict注解的使用以及原理深度剖析

0 人点赞