关于java:为什么私有方法上的Spring-Cache注解不生效

2次阅读

共计 3520 个字符,预计需要花费 9 分钟才能阅读完成。

申明:本文摘抄自公众号【看点代码再下班】。

目录

Spring Cache “ 错用 ”

错在哪里?

从 Spring Cache 原理解释为什么公有办法不能加缓存

从 Spring AOP 原理解释为什么公有办法上不能加缓存

类外部办法调用不反对加缓存

结语


​大家好,我是 tin,这是我的第 12 篇原创文章

Spring Cache “ 错用 ”

背景是这样的,一个共事开发的一个功能模块代码,大略是查问一个上游的内容接口,查问到数据并转发给端侧接口。这个功能模块 流量十分大,上游的内容接口的内容数据量也无限(内容 id 数无限,量级在几个 w),共事也意识到了须要在本人服务内对上游内容接口加本地缓存。做法和上面图截然不同:

这个代码公布到线上,当端侧拜访入口凋谢时,咱们后端服务就开始疯狂告警,都是内容接口不负重压、响应超时的告警。

而后咱们先去看的调用链,发现内容接口 QPS 曾经飙升到 10W!

为什么?明明曾经加了接口缓存,按咱们加缓存的预期,很多申请应该打到缓存上,而不应该再查上游内容接口才对啊?或者很多人都这么认为,但错了就是错了。

像上图的谬误是很“低级”的,如果都这么应用,对于略微不强壮的上游零碎,将是劫难,如果真到那样子,往年的绩效也就好不到哪了。

错在哪里?

上述代码对 spring cache 应用,总结来说有上面的谬误:

  • 1、在公有办法上加缓存
  • 2、类外部办法调用加缓存

这些问题都是比拟致命的,咱们很多应用缓存的敌人基本不晓得也不分明不能这么应用。上面我把问题一一地剖析透了。

从 Spring Cache 原理解释为什么 公有办法不能加缓存

Spring Cache 通过注解,并借助 Spring AOP 实现缓存。关上源码包,定位到咱们的 @Cacheable 注解地位:


cache 的实现都在 context 的 org.springframework.cache 包下。我的 spring 版本是 5.3.14,其余版本也根本一样。


matches 办法判断类或者办法中有没有 cache 相干的缓存注解,这个是怎么判断的呢?咱们一路跟进去,cas.getCacheOperations(method, targetClass)),到了 AbstractFallbackCacheOperationSource 类的 getCacheOperations 办法:


attributeCache 是一个 ConcurrentHashmap,只是把 computeCacheOperations(method, targetClass)计算失去的后果缓存一下,下次再进来就不必耗费 cpu 从新计算获取。


computeCacheOperations 办法中,真正解析 办法上cache 注解的中央在 findCacheOperations 办法:


一路跟进去,当咱们看到 SpringCacheAnnotationParser 类的 parseCacheAnnotations 办法时,就到看了 spring 把 cache 的相干注解进行解析,并把注解包装为 CacheOperation 类


看到这里就明确了吧,spring 把类和办法上的 cache 注解包装起来并放到一个汇合 Collection 中,在 aop 切面上通过判断是否有 CacheOperation 作为切入点。
而后,到这里我想重点说一下的是,spring cache 曾经做了判断,不反对非 public 办法上的缓存注解,逻辑在哪里呢?仔细的能够发现:


很显然的不反对非 public 办法,即便是 protected 办法都不行,更不用说 private 了!

这里再多说一下,看完 cache 的切入点代码,咱们也很容易找到办法拦截器:


咱们从 execute 办法一路跟进去,能够看到最初是在 CacheAspectSupport 类的 execute 办法实现对接缓存的读取或者更新。


如果咱们引入了三方的 cache,比方 Caffeine,那么,底层就是应用 Caffeine.Cache 来存储的。





如果想晓得如何应用三方缓存工具,能够看我另外一篇文章:

《人人都说好的 Spring Cache!用起来!【文末送书】》
论断:

spring cache 通过

AbstractFallbackCacheOperation#computeCacheOperations 办法显式地不反对非 public 办法的注解缓存。

从 Spring AOP 原理解释为什么 公有办法上不能加缓存

下面讲到了 spring cache 本人做了一层限度,不反对非 public 办法加缓存注解,那么,spring cache 为什么这么做?如果只是看 spring cache 源码的逻辑,不加这个限度,不也一样是能够“走得通”么?
 

要解释这个问题,那就要从 Spring AOP 原理说起了。

咱们先把 BookService 的缓存注解地位调整一下,让办法可能失常走缓存逻辑:


启动咱们的 spring 容器,断点到咱们业务代码 bookService.findByBookNameWithSpringCache(bookName)的中央:


看到了没,BookService 援用是一个代理类,这也侧面阐明 spring cache 借用了 aop 的能力。

问题来了,为什么是 cglib 代理?

在咱们的常识外面,spring aop 默认都是采纳 java 的动静代理,其次才会应用 cglib 代理。从 spring 官网文档也能够证实:


em……我没有应用接口,所以采纳了 cglib 代理。这个解释也只算对一半吧。(因为即便你换成了接口实现,最初还是没能如你所愿,还是 cglib 代理,感兴趣的敌人自行尝试)

还是说下为什么咱们运行始终都是 cglib 代理的起因吧。

这是 spring boot 搞的鬼,在咱们的启动类上有一个 @SpringBootApplication 注解,这是一套组合注解,咱们顺着这个注解外部的定义,找到 @EnableAutoConfiguration,再找到 @Import(AutoConfigurationImportSelector.class)


AutoConfigurationImportSelector 类的 process 办法:


这外面有很多主动拆卸信息,依据 AopAutoConfiguration(这个类定义在 spring-boot 下而不是 spring-context 下)的定义:


AopAutoConfiguration 类的次要工作是依据配置参数应用注解 @EnableAspectJAutoProxy,正文也有阐明:


该类启用的条件是:配置参数 spring.aop.auto 值不为 false,咱们的 spring-configuration-metadata.json 中有配置:


AopAutoConfiguration 又蕴含了如下两个内置配置类,别离对应配置参数 spring.aop.proxy-target-class=true/false 两种状况 :


当 spring.aop.proxy-target-class 缺省配置时默认也是 true,咱们的 spring-boot 外面默认就是 true,所以默认应用 aop 的 cglib 代理。

到这里,咱们就根本晓得 spring-cache 中应用到的 aop 为何始终应用 cglib 代理的起因。

说完 cglib,终于能够回到主题上了,“为何不能在公有办法上应用 cache 注解”,如果从 aop 的角度去剖析,那么答案就是:因为 cglib

cglib 实现动静代理,其底层采纳了 ASM 字节码生成框架,间接对须要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有能够重写的办法。

因为 cglib 的代理类应用的是继承,这也就意味着cglib 不能代理 final 类,同时也不能对 private 办法进行代理!子类无奈重写 private 办法啊!

至于 cglib 是如何生成代理类的,这里不开展说了,前面有机会再专门出一个文章写一写,咱们到这里只有晓得,spring cache 的实现应用了 aop 性能,而 aop 不反对对 private 公有办法的拦挡,所以也就不反对公有办法上的 spring cache 注解。

类外部办法调用不反对加缓存

通过下面的剖析,spring cahe 的缓存性能是因为应用了 aop,如此可知咱们的类是被 cglib 从新加强代理过的类。

如果是类外部办法调用,为什么就不能失效?

这个问题很简略,咱们在外部调用办法的中央打个断点,一看便知:

是吧,没有走代理,怎么可能应用得上缓存性能呢?

结语

我是 tin,一个在致力让本人变得更优良的一般攻城狮。本人经历无限、学识肤浅,如有发现文章不妥之处,十分欢送加我提出,我肯定仔细斟酌加以批改。

保持创作不容易,你的正反馈是我保持输入的最弱小能源,谢谢!

最初!⏬⏬⏬

正文完
 0