SpringAOP动态代理原理

1. 动态代理原理

SpringAOP是通过动态代理来实现的,有两种动态代理机制。

  • JDK动态代理:只能通过接口声明进行代理,使用Java反射,动态生成字节码技术。Spring调用的是java.lang.reflection.Proxy类来做处理。
  • CGLIB动态代理:CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

1.1 JDK动态代理

JDK动态代理只能通过接口声明进行代理,使用Java反射。主要步骤如下:

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
  2. 创建被代理的类以及接口;
  3. 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理;
  4. 通过代理调用方法。

1.1.1 InvocationHandler

该接口中仅定义了一个方法

public object invoke(Object obj,Method method, Object[] args)

在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。

1.1.2 Proxy

该类即是动态代理类,其中主要包含以下内容:

  • protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
  • static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
  • static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

所谓DynamicProxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个DynamicProxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

在使用动态代理类时,我们必须实现InvocationHandler接口。通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。

1.2 CGLIB动态代理

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法。缺点就是final方法无法继承,也就无法被代理。CGLIB基本结构如下图所示:

1.2.1 动态代理过程

CGLib 定义的 intercept() 接口方法, 拦截所有目标类方法的调用.  其中 o 代表目标类的实例,  method 为目标类方法的反射;  args为方法的动态入参,  proxy为代理类实例。

代理对象的生成由 Enhancer 类实现. Enhancer是CGLib的字节码增强器. 可以很方便的对类进行扩展.

Enhancer 创建代理对象的大概步骤如下:

  1. 生成代理类 Class 二进制字节码;

  2. 通过 Class.forname() 加载字节码文件, 生成 Class 对象;

  3. 通过反射机制获得实例构造, 并创建代理类对象。

2. 扩展:SpringAOP事务中,同一个类A方法调用B方法事务失效的问题

我们知道在Spring框架下,同一个类中A方法直接调用B方法,即使B方法有@Transactional注解,B的事务也是不会生效的。原因就是Spring的AOP实现机制问题。下面我们就来分析原因,并给出解决方案。

2.1 原因分析

我们知道,Spring的AOP实现方式有两种:JDK代理方式和Cglib动态增强方式,这两种方式在Spring中是可以无缝自由切换的。JDK代理方式的优点是不依赖第三方jar包,缺点是不能代理类,只能代理接口。

Spring通过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式。即在两种代理方式之上又进行了一层接口抽象。

现在看来,这种抽象同样带了一个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强子类的能力,Spring相当于把Cglib动态生成的子类,当普通的代理类了,这也是为什么会创建两个对象的原因。下图显示了Spring的AOP代理类的实际调用过程:

因此,从上面的分析可以看出,methodB没有被AopProxy通知到,导致最终结果是:被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。而这种结果,会造成什么影响呢:

  1:内部调用时,被调用方法的事务声明将不起作用

  2:换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中

  3:再换句话说,Spring的事务传播策略在内部方法调用时将不起作用。不管你希望某个方法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作用。

  4:不仅仅是事务通知,所有你自己利用Spring实现的AOP通知,都会受到同样限制。。。。

2.2 解决方案

2.2.1 通过ThreadLocal暴露Aop代理对象

Spring-content.xml上下文中,增加配置:<aop:aspectj-autoproxy expose-proxy=“true”/>。在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。

适合解决所有场景(不管是singleton Bean还是prototype Bean)的AOP代理获取问题(即能解决目标对象的自我调用问题);

2.2.2 通过ApplicationContext.getBean获取代理对象

通过ApplicationContext.getBean方法获取代理对象。这种方式用的比较多。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值