前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点【享学Spring】

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

作者头像
YourBatman
发布2019-09-03 15:22:33
4.8K0
发布2019-09-03 15:22:33
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

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

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

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注解的使用以及原理深度剖析

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年07月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • CacheOperation:缓存操作
  • CacheOperationSource:缓存属性源
    • NameMatchCacheOperationSource
      • AbstractFallbackCacheOperationSource
        • AnnotationCacheOperationSource
      • CompositeCacheOperationSource
      • CacheAnnotationParser:缓存注解解析器
      • CacheOperationInvocationContext:执行上下文
      • CacheOperationExpressionEvaluator:缓存操作执行器
      • CacheResolver
        • AbstractCacheResolver
        • CacheOperationSourcePointcut
        • CacheErrorHandler
        • AbstractCacheInvoker
          • 总结
          相关产品与服务
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档