V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OysterQAQ
V2EX  ›  分享发现

近期使用 Spring Cache 所发现问题与部分解决方案

  •  
  •   OysterQAQ · 2020-02-22 16:24:29 +08:00 · 2005 次点击
    这是一个创建于 1525 天前的主题,其中的信息可能已经有所发展或是发生改变。

    近期在使用 Spring Cache 期间碰到了一些问题,这里并不会描述 Spring Cache 的使用,仅仅只是记录问题的场景与解决方式

    问题 1:

    • 场景:在 controller 的 a 方法中调用了 service 类的某方法(这个方法上加了@Cacheable注解)返回了一个 List 对象,controller 的 a 方法有一个 aop 切面,逻辑是将返回的 List 中插入一个元素,再多次调用后,发现返回的 List 的元素在逐次递增,表现为调用几次增加几个.
    • **原因:**spring cache 在使用堆类型缓存存储方案的时候(Caffeine|Guava),缓存对象可以被修改(即返回的对象就是缓存内的对象,并不是复制品)
    • 解决:(仅仅是我的场景适用)在 aop 处理的时候构造一个新的 List 对象,并且执行浅拷贝,将原 List 中的对象加入新的 List 中,再进行插入一个元素的处理.

    问题 2:

    • 场景:Spring Cache 注解无法在 mapper 上使用,即在@Mapper标注的接口的方法上使用是无效的

    • 原因:尚未证明是接口问题还是@Mapper注解的问题,但是根据 Spring Cache 的官方文档中说道:@Cacheable 在接口方法上面时如果使用基于类的代理是无效的

      Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotation, as opposed to annotating interfaces. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class="true") or the weaving-based aspect ( mode="aspectj"), then the caching settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a caching proxy, which would be decidedly bad.

      这就设计到 spring aop 动态代理的实现方式了,spring boot 默认是 cglib,也就是基于类的代理,被代理者必须是类,经过查阅资料,实际上 spring 5 中动态代理方式是自动转换的.

      Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

      Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface or where you need to pass a proxied object to a method as a concrete type.

      It is important to grasp the fact that Spring AOP is proxy-based. See Understanding AOP Proxies for a thorough examination of exactly what this implementation detail actually means.

      Spring AOP 默认将标准 JDK 动态代理用于 AOP 代理。 这使得可以代理任何接口(或一组接口)。

      Spring AOP 也可以使用 CGLIB 代理。 这对于代理类而不是接口是必需的。 默认情况下,如果业务对象未实现接口,则使用 CGLIB。

      关于 spring aop 可以看一下这篇文章 https://juejin.im/post/5db7870a518825647178f16c

      那么按道理如果可以自动识别的,那么大概率是 @mapper 注解问题

    • **解决:**这里没有仔细研究,先放个 TODO,临时解决方案是将注解移动到 service 层,而移动到 service 层则引发了下一个问题

    问题 3

    • 场景:某个类的 a 方法调用了同在类里面的 b 方法(该方法有@Cacheable注解)而这时候 Cacheable 注解作为切点的 aop 事件并不会生效,即注解效果失效

    • 原因:由于 Spring Cache 默认使用 proxy mode 也就是基于动态代理实现 AOP,那么类自身调用是不会经过代理的,这个问题同样适用于@Transactional \ @Async等使用 spring aop 作为 aop 实现的注解

    • **解决:**使用 Aspectj 加载时织入(Load-Time Weaving)代替 Spring Aop,这个网上资料很少,我指的是 Spring Boot aspectj 加载时织入的资料

      经过一晚上的 google,我找到了跑起来的方法,主要参考

      https://www.credera.com/blog/technology-insights/open-source-technology-insights/aspect-oriented-programming-in-spring-boot-part-3-setting-up-aspectj-load-time-weaving/

      步骤如下:

      • pom 中加入依赖:spring-aspects\spring-instrument

      • resources 文件夹中新建 META-INF 并在其中新建 aop.xml,这是 Aspectj 运行所必须的,内容为

        <aspectj>
            <weaver options="-verbose -showWeaveInfo">//开启日志
                <include within="扫描的包"/>//可以全部扫描
            </weaver>
        </aspectj>
        
      • 在启动类加上注解,开启加载时织入:

        @EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
        
      • spring cache 生效注解中加入 mode 更换的参数,表示启用 aspectj 模式

        @EnableCaching(mode = AdviceMode.ASPECTJ)
        
      • JVM 启动参数中加入

        -javaagent:/path/to/spring-instrument-{version}.jar -javaagent:/path/to/aspectjweaver-{version}.jar
        

      以上解决方案仅仅只是表面,希望有相关经验的老哥提供一些探索的思路或者文章以及见解

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5349 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 09:12 · PVG 17:12 · LAX 02:12 · JFK 05:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.