经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
基于spring?@Cacheable?注解的spel表达式解析执行逻辑
来源:jb51  时间:2022/1/3 12:31:26  对本文有异议

日常使用中spring的 @Cacheable 大家一定不陌生,基于aop机制的缓存实现,并且可以选择cacheManager具体提供缓存的中间件或者进程内缓存,类似于 @Transactional 的transactionManager ,都是提供了一种多态的实现,抽象出上层接口,实现则供客户端选择,或许这就是架构吧,抽象的设计,使用interface对外暴露可扩展实现的机制,使用abstract 整合类似实现。

那么我们就看看 @Cacheable提供的一种方便的机制,spel表达式取方法 参数的逻辑,大家都写过注解,但是注解逻辑需要的参数可以使用spel动态取值是不是好爽~

直接进入主题 跟随spring的调用链

直接看 @Cacheable 注解就可以了

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. @Documented
  5. public @interface Cacheable {
  6. ? ? ? ? // spring的别名机制,这里不讨论,和cacheNames作用一致
  7. ? ? @AliasFor("cacheNames")
  8. ? ? String[] value() default {};
  9. ? ? @AliasFor("value")
  10. ? ? String[] cacheNames() default {};
  11. ? ? ? ? // 今天的主角,就从他入手
  12. ? ? String key() default "";
  13. ? ? ? // 拼接key的 抽象出来的接口
  14. ? ? String keyGenerator() default "";
  15. // 真正做缓存这件事的人,redis,caffine,还是其他的都可以,至于内存还是进程上层抽象的逻辑不关心,如果你使用caffine
  16. //就需要自己考虑 多服务实例的一致性了
  17. ? ? String cacheManager() default "";
  18. ? ? String cacheResolver() default "";
  19. // 是否可以执行缓存的条件 也是 spel 如果返回结果true 则进行缓存
  20. ? ? String condition() default "";
  21. // ?如果spel 返回true 则不进行缓存
  22. ? ? String unless() default "";
  23. // 是否异步执行
  24. ? ? boolean sync() default false;
  25. }

接下来看 key获取是在哪里

SpringCacheAnnotationParser#parseCacheableAnnotation 解析注解,还好就一个地方

没有任何逻辑就是一个组装

继续跟踪上述方法 SpringCacheAnnotationParser#parseCacheAnnotations 走到这里,

  1. ? ? @Nullable
  2. ? ? private Collection<CacheOperation> parseCacheAnnotations(
  3. ? ? ? ? ? ? DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
  4. ? ? ? ? Collection<? extends Annotation> anns = (localOnly ?
  5. ? ? ? ? ? ? ? ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
  6. ? ? ? ? ? ? ? ? AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
  7. ? ? ? ? if (anns.isEmpty()) {
  8. ? ? ? ? ? ? return null;
  9. ? ? ? ? }
  10. ? ? ? ? final Collection<CacheOperation> ops = new ArrayList<>(1);
  11. ? ? ? ? anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
  12. ? ? ? ? ? ? ? ? ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
  13. ? ? ? ? anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
  14. ? ? ? ? ? ? ? ? ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
  15. ? ? ? ? anns.stream().filter(ann -> ann instanceof CachePut).forEach(
  16. ? ? ? ? ? ? ? ? ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
  17. ? ? ? ? anns.stream().filter(ann -> ann instanceof Caching).forEach(
  18. ? ? ? ? ? ? ? ? ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
  19. ? ? ? ? return ops;
  20. ? ? }

也没有太多逻辑,将当前拦截到的方法可能存在的多个 SpringCache的注解解析为集合返回,那就是支持多个SpringCache注解同时放到一个方法喽。

  1. ? ? @Override
  2. ? ? @Nullable
  3. ? ? public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
  4. // 到上边发现这里入参是一个类,那么可以推断这里调用是启动或者类加载时进行注解解析,然后缓存注解的写死的参数返回
  5. ? ? ? ? DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
  6. ? ? ? ? return parseCacheAnnotations(defaultConfig, type);
  7. ? ? }
  8. //------------还有一个方法是对方法的解析也是对注解的解析返回------------------
  9. ? ? @Override
  10. ? ? @Nullable
  11. ? ? public Collection<CacheOperation> parseCacheAnnotations(Method method) {
  12. ? ? ? ? DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
  13. ? ? ? ? return parseCacheAnnotations(defaultConfig, method);
  14. ? ? }

再上边 AnnotationCacheOperationSource#findCacheOperations ,两个重载方法

  1. ? ? @Override
  2. ? ? @Nullable
  3. ? ? protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
  4. ? ? ? ? return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
  5. ? ? }
  6. ? ? @Override
  7. ? ? @Nullable
  8. ? ? protected Collection<CacheOperation> findCacheOperations(Method method) {
  9. ? ? ? ? return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
  10. ? ? }
  • AbstractFallbackCacheOperationSource#computeCacheOperations 这里有点看不懂暂时不细做追溯,目的就是spel
  • AbstractFallbackCacheOperationSource#getCacheOperations 还是处理解析注解返回

调用getCacheOperations方法的地方

如上图直接查看第一个调用

CacheAspectSupport#execute 查看这个execute调用方是CacheInterceptor#invoke 实现的MethodInterceptor接口,那不用看其他的了,这里就是执行方法拦截的地方,在这里会找到spel的动态解析噢
顺便看一下拦截方法中的执行逻辑

了解一下@Cacheable的拦截顺序

  1. ? ? @Override
  2. ? ? @Nullable
  3. ? ? public Object invoke(final MethodInvocation invocation) throws Throwable {
  4. ? ? ? ? Method method = invocation.getMethod();
  5. // 这是个一个 函数式接口作为回调,这里并没有执行,先执行下面execute方法 即CacheAspectSupport#execute
  6. ? ? ? ? CacheOperationInvoker aopAllianceInvoker = () -> {
  7. ? ? ? ? ? ? try {
  8. ? ? ? ? ? ? ? ? return invocation.proceed();
  9. ? ? ? ? ? ? }
  10. ? ? ? ? ? ? catch (Throwable ex) {
  11. ? ? ? ? ? ? ? ? throw new CacheOperationInvoker.ThrowableWrapper(ex);
  12. ? ? ? ? ? ? }
  13. ? ? ? ? };
  14. ? ? ? ? try {
  15. ? ? ? ? ? ? return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
  16. ? ? ? ? }
  17. ? ? ? ? catch (CacheOperationInvoker.ThrowableWrapper th) {
  18. ? ? ? ? ? ? throw th.getOriginal();
  19. ? ? ? ? }
  20. ? ? }

接下来看 execute方法

  1. ? @Nullable
  2. ? ? protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
  3. ? ? ? ? // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
  4. ? ? ? ? if (this.initialized) {
  5. ? ? ? ? ? ? Class<?> targetClass = getTargetClass(target);
  6. ? ? ? ? ? ? CacheOperationSource cacheOperationSource = getCacheOperationSource();
  7. ? ? ? ? ? ? if (cacheOperationSource != null) {
  8. ? ? ? ? ? ? ? ? Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
  9. ? ? ? ? ? ? ? ? if (!CollectionUtils.isEmpty(operations)) {
  10. ? ? ? ? ? ? ? ? ? ? return execute(invoker, method,
  11. ? ? ? ? ? ? ? ? ? ? ? ? ? ? new CacheOperationContexts(operations, method, args, target, targetClass));
  12. ? ? ? ? ? ? ? ? }
  13. ? ? ? ? ? ? }
  14. ? ? ? ? }
  15. ? ? ? ? ? // 方法逻辑是后执行噢,先进行缓存
  16. ? ? ? ? return invoker.invoke();
  17. ? ? }

再看 重载方法execute

  1. ? ? @Nullable
  2. ? ? private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
  3. ? ? ? ? // 注解上的是否异步的字段这里决定是否异步执行
  4. ? ? ? ? if (contexts.isSynchronized()) {?
  5. ? ? ? ? ? ? CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
  6. ? ? ? ? ? ? if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
  7. ? ? ? ? ? ? ? ? Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
  8. ? ? ? ? ? ? ? ? Cache cache = context.getCaches().iterator().next();
  9. ? ? ? ? ? ? ? ? try {
  10. ? ? ? ? ? ? ? ? ? ? return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
  11. ? ? ? ? ? ? ? ? }
  12. ? ? ? ? ? ? ? ? catch (Cache.ValueRetrievalException ex) {
  13. ? ? ? ? ? ? ? ? ? ? // Directly propagate ThrowableWrapper from the invoker,
  14. ? ? ? ? ? ? ? ? ? ? // or potentially also an IllegalArgumentException etc.
  15. ? ? ? ? ? ? ? ? ? ? ReflectionUtils.rethrowRuntimeException(ex.getCause());
  16. ? ? ? ? ? ? ? ? }
  17. ? ? ? ? ? ? }
  18. ? ? ? ? ? ? else {
  19. ? ? ? ? ? ? ? ? // No caching required, only call the underlying method
  20. ? ? ? ? ? ? ? ? return invokeOperation(invoker);
  21. ? ? ? ? ? ? }
  22. ? ? ? ? }
  23. // -------------同步执行缓存逻辑--------------
  24. // --------------------下面各种注解分别执行,可以看出来springCache注解之间的顺序 缓存删除(目标方法invoke前)并执行、缓存增
  25. //加(猜测是先命中一次缓存,如果没有命中先存入空数据的缓存,提前占住缓存数据,尽量减少并发缓存带来的缓存冲洗问题)、
  26. //缓存增加(带有数据的)、上述两个缓存增加的真正执行 、缓存删除(目标方法invoke 后)并执行
  27. //当然这个 是 invoke前执行 或者后执行 是取决于@CacheEvict 中的 beforeInvocation 配置,默认false在后面执行如果前面执行unless就拿不到结果值了
  28. // 那么spring cache 不是 延时双删噢,高并发可能存在数据过期数据重新灌入
  29. ? ? ? ? // Process any early evictions
  30. ? ? ? ? processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
  31. ? ? ? ? ? ? ? ? CacheOperationExpressionEvaluator.NO_RESULT);
  32. ? ? ? ? // Check if we have a cached item matching the conditions
  33. ? ? ? ? Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
  34. ? ? ? ? // Collect puts from any @Cacheable miss, if no cached item is found
  35. ? ? ? ? List<CachePutRequest> cachePutRequests = new LinkedList<>();
  36. ? ? ? ? if (cacheHit == null) {
  37. ? ? ? ? ? ? collectPutRequests(contexts.get(CacheableOperation.class),
  38. ? ? ? ? ? ? ? ? ? ? CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
  39. ? ? ? ? }
  40. ? ? ? ? ? ? ? // 方法入参解析 用于 key ?condition
  41. ? ? ? ? Object cacheValue;
  42. ? ? ? ? ? ? ? // 方法结果 解析 ?用于 unless
  43. ? ? ? ? Object returnValue;
  44. ? ? ? ? if (cacheHit != null && !hasCachePut(contexts)) {
  45. ? ? ? ? ? ? // If there are no put requests, just use the cache hit
  46. ? ? ? ? ? ? cacheValue = cacheHit.get();
  47. ? ? ? ? ? ? returnValue = wrapCacheValue(method, cacheValue);
  48. ? ? ? ? }
  49. ? ? ? ? else {
  50. ? ? ? ? ? ? // Invoke the method if we don't have a cache hit
  51. ? ? ? ? ? ? returnValue = invokeOperation(invoker);
  52. ? ? ? ? ? ? cacheValue = unwrapReturnValue(returnValue);
  53. ? ? ? ? }
  54. ? ? ? ? // Collect any explicit @CachePuts
  55. ? ? ? ? collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
  56. ? ? ? ? // Process any collected put requests, either from @CachePut or a @Cacheable miss
  57. ? ? ? ? for (CachePutRequest cachePutRequest : cachePutRequests) {
  58. ? ? ? ? ? ? cachePutRequest.apply(cacheValue);
  59. ? ? ? ? }
  60. ? ? ? ? // Process any late evictions
  61. ? ? ? ? processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
  62. ? ? ? ? return returnValue;
  63. ? ? }

不详细探究执行逻辑了,来看看生成key的逻辑,private 方法 generateKey

  1. // 可以看出没有生成key ?会抛出异常,不允许null
  2. ? ? private Object generateKey(CacheOperationContext context, @Nullable Object result) {
  3. ? ? ? ? Object key = context.generateKey(result);
  4. ? ? ? ? if (key == null) {
  5. ? ? ? ? ? ? throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " +
  6. ? ? ? ? ? ? ? ? ? ? "using named params on classes without debug info?) " + context.metadata.operation);
  7. ? ? ? ? }
  8. ? ? ? ? if (logger.isTraceEnabled()) {
  9. ? ? ? ? ? ? logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
  10. ? ? ? ? }
  11. ? ? ? ? return key;
  12. ? ? }
  13. //------------------------继续------------
  14. ? ? ? ? /**
  15. ? ? ? ? ?* Compute the key for the given caching operation.
  16. ? ? ? ? ?*/
  17. ? ? ? ? @Nullable
  18. ? ? ? ? protected Object generateKey(@Nullable Object result) {
  19. ? ? ? ? ? ? if (StringUtils.hasText(this.metadata.operation.getKey())) {
  20. // 终于看到 spring核心包之一 org.springframework.expression 包里的类了。。。T.T
  21. ? ? ? ? ? ? ? ? EvaluationContext evaluationContext = createEvaluationContext(result);
  22. ? ? ? ? ? ? ? ? return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
  23. ? ? ? ? ? ? }
  24. ? ? ? ? ? ? return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
  25. ? ? ? ? }

可以看到使用的 evaluator 是CacheOperationExpressionEvaluator类这个成员变量,类加载时便生成,里面有生成待解析实例的方法,有解析 key condition unless 的三个方法及ConcurrentMap 成员变量缓存到内存中,将所有的Cache注解的 spel表达式缓存于此,默认 64的大小,主要方法如下

  1. ? ? public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
  2. ? ? ? ? ? ? Method method, Object[] args, Object target, Class<?> targetClass, Method targetMethod,
  3. ? ? ? ? ? ? @Nullable Object result, @Nullable BeanFactory beanFactory) {
  4. ? ? ? ? CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
  5. ? ? ? ? ? ? ? ? caches, method, args, target, targetClass);
  6. ? ? ? ? CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
  7. ? ? ? ? ? ? ? ? rootObject, targetMethod, args, getParameterNameDiscoverer());
  8. ? ? ? ? if (result == RESULT_UNAVAILABLE) {
  9. ? ? ? ? ? ? evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
  10. ? ? ? ? }
  11. ? ? ? ? else if (result != NO_RESULT) {
  12. ? ? ? ? ? ? evaluationContext.setVariable(RESULT_VARIABLE, result);
  13. ? ? ? ? }
  14. ? ? ? ? if (beanFactory != null) {
  15. ? ? ? ? ? ? evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
  16. ? ? ? ? }
  17. ? ? ? ? return evaluationContext;
  18. ? ? }
  19. ? ? @Nullable
  20. ? ? public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
  21. ? ? ? ? return getExpression(this.keyCache, methodKey, keyExpression).getValue(evalContext);
  22. ? ? }
  23. ? ? public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
  24. ? ? ? ? return (Boolean.TRUE.equals(getExpression(this.conditionCache, methodKey, conditionExpression).getValue(
  25. ? ? ? ? ? ? ? ? evalContext, Boolean.class)));
  26. ? ? }
  27. ? ? public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
  28. ? ? ? ? return (Boolean.TRUE.equals(getExpression(this.unlessCache, methodKey, unlessExpression).getValue(
  29. ? ? ? ? ? ? ? ? evalContext, Boolean.class)));
  30. ? ? }

然后就返回想要的key了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号