多平台统一管理软件接口,如何实现多平台统一管理软件接口
418
2022-09-05
基于spring @Cacheable 注解的spel表达式解析执行逻辑
目录直接进入主题 跟随spring的调用链直接看 @Cacheable 注解就可以了接下来看 key获取是在哪里没有任何逻辑就是一个组装了解一下@Cacheable的拦截顺序接下来看 execute方法再看 重载方法execute
日常使用中spring的 @Cacheable 大家一定不陌生,基于aop机制的缓存实现,并且可以选择cacheManager具体提供缓存的中间件或者进程内缓存,类似于 @Transactional 的transactionManager ,都是提供了一种多态的实现,抽象出上层接口,实现则供客户端选择,或许这就是架构吧,抽象的设计,使用interface对外暴露可扩展实现的机制,使用abstract 整合类似实现。
那么我们就看看 @Cacheable提供的一种方便的机制,spel表达式取方法 参数的逻辑,大家都写过注解,但是注解逻辑需要的参数可以使用spel动态取值是不是好爽~
直接进入主题 跟随spring的调用链
直接看 @Cacheable 注解就可以了
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// spring的别名机制,这里不讨论,和cacheNames作用一致
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 今天的主角,就从他入手
String key() default "";
// http://拼接key的 抽象出来的接口
String keyGenerator() default "";
// 真正做缓存这件事的人,redis,caffine,还是其他的都可以,至于内存还是进程上层抽象的逻辑不关心,如果你使用caffine
//就需要自己考虑 多服务实例的一致性了
String cacheManager() default "";
String cacheResolver() default "";
// 是否可以执行缓存的条件 也是 spel 如果返回结果true 则进行缓存
String condition() default "";
// 如果spel 返回true 则不进行缓存
String unless() default "";
// 是否异步执行
boolean sync() default false;
}
接下来看 key获取是在哪里
SpringCacheAnnotationParser#parseCacheableAnnotation 解析注解,还好就一个地方
没有任何逻辑就是一个组装
继续跟踪上述方法 SpringCacheAnnotationParser#parseCacheAnnotations 走到这里,
@Nullable
private Collection
DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
Collection extends Ahttp://nnotation> anns = (localOnly ?
AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
if (anns.isEmpty()) {
return null;
}
final Collection
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;
}
也没有太多逻辑,将当前拦截到的方法可能存在的多个 SpringCache的注解解析为集合返回,那就是支持多个SpringCache注解同时放到一个方法喽。
@Override
@Nullable
public Collection
// 到上边发现这里入参是一个类,那么可以推断这里调用是启动或者类加载时进行注解解析,然后缓存注解的写死的参数返回
DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
//------------还有一个方法是对方法的解析也是对注解的解析返回------------------
@Override
@Nullable
public Collection
DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
再上边 AnnotationCacheOperationSource#findCacheOperations ,两个重载方法
@Override
@Nullable
protected Collection
return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
}
@Override
@Nullable
protected Collection
return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}
AbstractFallbackCacheOperationSource#computeCacheOperations 这里有点看不懂暂时不细做追溯,目的就是spelAbstractFallbackCacheOperationSource#getCacheOperations 还是处理解析注解返回
调用getCacheOperations方法的地方
如上图直接查看第一个调用
CacheAspectSupport#execute 查看这个execute调用方是CacheInterceptor#invoke 实现的MethodInterceptor接口,那不用看其他的了,这里就是执行方法拦截的地方,在这里会找到spel的动态解析噢顺便看一下拦截方法中的执行逻辑
了解一下@Cacheable的拦截顺序
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// 这是个一个 函数式接口作为回调,这里并没有执行,先执行下面execute方法 即CacheAspectSupport#execute
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
接下来看 execute方法
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
Collection
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
// 方法逻辑是后执行噢,先进行缓存
return invoker.invoke();
}
再看 重载方法execute
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 注解上的是否异步的字段这里决定是否异步执行
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
// -------------同步执行缓存逻辑--------------
// --------------------下面各种注解分别执行,可以看出来springCache注解之间的顺序 缓存删除(目标方法invoke前)并执行、缓存增
//加(猜测是先命中一次缓存,如果没有命中先存入空数据的缓存,提前占住缓存数据,尽量减少并发缓存带来的缓存冲洗问题)、
//缓存增加(带有数据的)、上述两个缓存增加的真正执行 、缓存删除(目标方法invoke 后)并执行
//当然这个 是 invoke前执行 或者后执行 是取决于@CacheEvict 中的 beforeInvocation 配置,默认false在后面执行如果前面执行unless就拿不到结果值了
// 那么spring cache 不是 延时双删噢,高并发可能存在数据过期数据重新灌入
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
// 方法入参解析 用于 key condition
Object cacheValue;
// 方法结果 解析 用于 unless
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
不详细探究执行逻辑了,来看看生成key的逻辑,private 方法 generateKey
// 可以看出没有生成key 会抛出异常,不允许null
private Object generateKey(CacheOperationContext context, @Nullable Object result) {
Object key = context.generateKey(result);
if (key == null) {
throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " +
"using named params on classes without debug info?) " + context.metadata.operation);
}
if (logger.isTraceEnabled()) {
logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
}
return key;
}
//------------------------继续------------
/**
* Compute the key for the given caching operation.
*/
@Nullable
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
// 终于看到 spring核心包之一 org.springframework.expression 包里的类了。。。T.T
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
可以看到使用的 evaluator 是CacheOperationExpressionEvaluator类这个成员变量,类加载时便生成,里面有生成待解析实例的方法,有解析 key condition unless 的三个方法及ConcurrentMap 成员变量缓存到内存中,将所有的Cache注解的 spel表达式缓存于此,默认 64的大小,主要方法如下
public EvaluationContext createEvaluationContext(Collection extends Cache> caches,
Method method, Object[] args, Object target, Class> targetClass, Method targetMethod,
@Nullable Object result, @Nullable BeanFactory beanFactory) {
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
caches, method, args, target, targetClass);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
@Nullable
public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.keyCache, methodKey, keyExpFCcQkUlkiression).getValue(evalContext);
}
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)));
}
然后就返回想要的key了。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~