Spring AOP注解失效的坑及JDK动态代理
@transactional @async等注解不起作用
之前很多人在使用spring中的@transactional, @async等注解时,都多少碰到过注解不起作用的情况。
为什么会出现这些情况呢?因为这些注解的功能实际上都是spring aop实现的,而其实现原理是通过代理实现的。
jdk动态代理
以一个简单的例子理解一下jdk动态代理的基本原理:
//目标类接口 public interface jdkproxytestservice { void run(); } //目标类 public class jdkproxytestserviceimpl implements jdkproxytestservice { public void run(){ system.out.println("do something..."); } } //代理类 public class testjdkproxy implements invocationhandler { private object targetobject; //代理目标对象 //构造代理对象 public object newproxy(object targetobject) { this.targetobject = targetobject; return proxy.newproxyinstance(targetobject.getclass().getclassloader(), targetobject.getclass().getinterfaces(), this); } //利用反射,在原逻辑上进行逻辑增强 public object invoke(object proxy, method method, object[] args) throws throwable { //模拟事务开始 assumebegintransaction(); //原执行逻辑 object ret = method.invoke(targetobject, args); //模拟事务提交 assumecommittransaction(); return ret; } private void assumebegintransaction() { system.out.println("模拟事务开始..."); } private void assumecommittransaction() { system.out.println("模拟事务提交..."); } } //测试 public class test { public static void main(string[] args) { testjdkproxy jdkproxy = new testjdkproxy(); jdkproxytestservice proxy = (jdkproxytestservice) jdkproxy.newproxy(new jdkproxytestserviceimpl()); proxy.run(); } }
上面的例子应该能够清楚的解释jdk动态代理的原理了。它利用反射机制,生成了一个实现代理接口的匿名类,在调用具体方法前调用invokehandler来处理。我们通过代理类对象调用方法时,实际上会先调用其invoke方法,里面再调用原方法。这样我们可以在原方法逻辑的前后统一添加处理逻辑。
spring还有一种动态代理方式是cglib动态代理。它是把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。虽然处理方式不一样,但是代理的思想都是一致的。
如果被代理的目标对象实现了接口,那么spring会默认使用jdk动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个cglib代理。
spring aop注解失效及解决
基于以上对于动态代理原理的分析,我们来看以下两个常见的问题:
同一个类中,方法a调用方法b(方法b上加有注解),注解无效
针对所有的spring aop注解,spring在扫描bean的时候如果发现有此类注解,那么会动态构造一个代理对象。
如果你想要通过类x的对象直接调用其中带注解的a方法,此注解是有效的。因为此时,spring会判断你将要调用的方法上存在aop注解,那么会使用类x的代理对象调用a方法。
但是假设类x中的a方法会调用带注解的b方法,而你依然想要通过类x对象调用a方法,那么b方法上的注解是无效的。因为此时spring判断你调用的a并无注解,所以使用的还是原对象而非代理对象。接下来a再调用b时,在原对象内b方法的注解当然无效了。
解决方法:
最简单的方式当然是可以让方法a和b没有依赖,能够直接通过类x的对象调用b方法。
但是很多时候可能我们的逻辑拆成这样写并不好,那么就还有一种方法:想办法手动拿到代理对象。
aopcontext类有一个currentproxy()方法,能够直接拿到当前类的代理对象。那么以上的例子,就可以这样解决:
// 在a方法内部调用b方法 // 1.直接调用b,注解失效。 b() // 2.拿到代理类对象,再调用b。 ((x)aopcontext.currentproxy()).b()
aop注解方法里使用@autowired对象为null
在之前的使用中,出现过在加上注解的方法中,使用其他注入的对象时,发现对象并没有被注入进来,为null。
最终发现,导致这种情况的原因是因为方法为private。因为spring不管使用的是jdk动态代理还是cglib动态代理,一个是针对实现接口的类,一个是通过子类实现。无论是接口还是父类,显然都不能出现private方法,否则子类或实现类都不能覆盖到。
如果方法为private,那么在代理过程中,根本找不到这个方法,引起代理对象创建出现问题,也导致了有的对象没有注入进去。
所以如果方法需要使用aop注解,请把它设置为非private方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
JDK动态代理给Spring事务埋下的坑!
-
Spring AOP 代理实现的两种方式: JDK动态代理 和 Cglib框架动态代理
-
Spring AOP注解失效的坑及JDK动态代理
-
【Spring AOP源码】基于JDK动态代理和Cglib创建代理对象的原理分析
-
Java框架之spring(二)静态和动态代理、AOP及AOP的实现方式
-
JDK动态代理给Spring事务埋下的坑!
-
Java--简单的Spring AOP配置以及AOP事物管理,JDK/GCLib动态代理
-
Java--简单的Spring AOP配置以及AOP事物管理,JDK/GCLib动态代理
-
【Spring AOP源码】基于JDK动态代理和Cglib创建代理对象的原理分析