是什么导致了Spring Aop失效了?

2022-08-15 11:07:11

使用Spring Aop遇到的问题
我们在做一些统一操作的功能时,经常会使用到Spring的Aop技术,比如要在每个方法进入前做点什么事情,结束后做点什么事情,这个时候我们就会想到用到Aop去做,详细的使用方法这里就不展开了,默认大家都会用,如果不会用没有概念的话,可能没办法看懂下面的问题场景,这边主要面向的是对Spring和Aop有过实战的同学,好,题外话不说,看下面几种Aop失效的场景。

经常有同学会说,诶?我明明配置的切点范围都没错,为什么就是没有切到我的方法呢?下面讲一下,几种常见的情况下,导致Aop失效没有正常切到方法上。

  1. 对于传统的web项目来说,通常使用spring和springmvc,因此对于这种项目来讲,他是有两个容器的,一个是spring容器,一般我们会把Service层的东西注入到spring容器中,另一个是springmvc的容器,通常这个容器里注入的是Controller层的东西,这里我们认为spring容器是父容器,springmvc是子容器的概念,然后我们大家都知道通过父子继承关系可知,子容器是可以读取到父容器中的东西,但是父容器是无法读到子容器中的内容,因此基于这个场景,有的同学,把Aop的实现类注入到了spring容器中,并且将Aop的切点表达式配置aop:config <aop:pointcut的execution也配到了spring容器的xml,而巧了这位同学要切的类方法,正好是Controller,也就是springmvc容器中的东西,那么这时候问题就来了,aop在初始化时会在自己的容器中寻找能够匹配的类方法,然后给他套上一层代理,此时他在自己能够访问到的spring容器中根本找不到与之匹配的类和方法,因为这些类和方法是在springmvc容器中管理的,因此就没有代理成功。

    解决:那么对于上述问题要怎么修改呢?只需要确保你要Aop切的类和方法与你Aop配置切点aop:config <aop:pointcut的execution表达式声明是在同一个容器中即可,此时只需要讲这个配置移到springmvc容器的xml中即可

  2. 第二种情况与第一种情况有些许的类似,但并不相同,是关于重复扫描的,比如你在spring容器中配置了一个Aop,并且把他托管给spring容器管理,而且execution表达式切的也是spring容器中管理的类和方法,理论上这个时候是好用的,这批execution切到的类都被加了代理,但是巧了,springmvc容器中由于配置的是包路径扫描,恰好把execution表达式切的这一批对象又扫了一遍,又都托管给了springmvc容器,而此时扫到的这批对象,是重新new出来交给springmvc管理的,因此并没有被aop代理,所以在使用时,注入进来的可能是springmvc容器管理的这批对象,因此使用时发现Aop代理失效了。

    解决:这个问题的解决方案,就是避免两个容器重复扫描。

    1. 第三个问题就比较简单了,他的现象是有些方法被Aop代理成功了,但有个别方法没有代理成功,究其原因发现这部分没有代理成功的方法并不是通过代理对象调用的,而是自身调用的,故被调用的方法没有被Aop代理,无法织入横切逻辑。

    解决:这个问题如果理解起来困难的话我举个例子,比如A.a(),A.b()是被代理的类和方法,那么当我调用A.a()时,此时a被代理了,成功执行代理类的内容,但还没有完,a()方法中调用了自身的方法b(),此时我们以为b也会被代理类代理,但实际上并没有,因为他是自身方法调用了并不是通过代理类A调用的,如果通过A.b()这种调用方式,那么b是可以被成功代理的。
    第三种还有一种解决方案,A.a()调用A.b()时,A.b()需要被增强可以用((A) AopContext.currentProxy()).b()调用,不过要在<aop:config元素上加上expose-proxy="true"属性

Spring AOP通过动态代理实现的,不同场景下代理的支持

  1. 如果要切入的目标对象实现了接口,默认情况下Spring会直接采用JDK的动态代理的方式去实现AOP

  2. 如果要切入的目标对象实现了接口,也可以通过代码控制强制要求Spring使用CGLIB代理的方式去实现AOP

  3. 如果要切入的目标对象没有实现任何接口,那么此时Spring会自动采用CGLIB库,它会根据实际切入目标的情况,自动在JDK动态代理和CGLIB之间做切换,但是必须在项目中加入CGLIB需要的jar包,否则当Spring判定需要使用CGLIB的时候,而你没有引入CGLIB的jar包,此时就会报错,而当你引入CGLIB的jar包后,不需要额外做任何关于CGLIB配置,Spring直接就可以根据规则自动转换代理模式。

如何强制使用CGLIB实现AOP?

  1. 添加CGLIB库,SPRING_HOME/cglib/*.jar

  2. 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

为什么有了JDK动态代理还要CGLIB?
由于JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类不能不能被JDK动态代理机制代理,为了弥补JDK动态代理机制的这个缺陷,CGLIB针对类来实现代理,它的原理是对指定的目标类(可以实现或者不实现接口)生成一个子类,即:目标类为生成的代理类的父类,并且覆盖父类中的方法来实现目标类增强(采用的是继承),所以该类或方法最好不要声明成final,因为CGLIB原理是动态生成被代理类的子类。

JDK动态代理和CGLIB的执行效率相比谁更好一点呢?

关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:

  1. CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

  2. 但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

  3. 因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

    OK,以上就是我对Spring Aop的总结,Aop切点失效的几种场景,后续如果遇到其他的会来追加,也欢迎大家贴出自己遇到的一些场景。

  • 作者:linux_lzj_cainiao
  • 原文链接:https://blog.csdn.net/linux_lzj_cainiao/article/details/121924417
    更新时间:2022-08-15 11:07:11