一、Spring的AOP的动态代理实现机制有两种,分别是:
1、JDK动态代理:
具体实现原理:
1、通过实现InvocationHandler接口创建自己的调用处理器
2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理
3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,
Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。
两者对比:
JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。
CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。
使用注意:
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。
二、同一对象内嵌套方法注解不生效问题
在代理对象方法中,无论如何添加横切逻辑,不管添加多少横切逻辑,最终还是需要调用目标对象上的同一方法来执行最初所定义的方法逻辑。
如果目标对象中原始方法调用依赖于其他对象,我们可以为目标对象注入所需依赖对象的代理,并且可以保证想用的JoinPoint被拦截并织入横切逻辑。而一旦目标对象中的原始方法直接调用自身方法的时候,也就是说依赖于自身定义的其他方法时,就会出现如下图所示问题:
package demo.interf.impl;
import demo.interf.ICustomerService;
public class CustomerServiceProxy implements ICustomerService {
private ICustomerService customerService;
public void setCustomerService(ICustomerService customerService) {
this.customerService = customerService;
}
public void doSomething1() {
doBefore();
customerService.doSomething1();
doAfter();
}
public void doSomething2() {
doBefore();
customerService.doSomething2();
doAfter();
}
private void doBefore() {
// 例如,可以在此处开启事务
System.out.println("do some important things before...");
}
private void doAfter() {
// 例如,可以在此处提交或回滚事务、释放资源等等
System.out.println("do some important things after...");
}
}
在代理对象的method1()
方法执行经历了层层拦截器后,最终会将调用转向目标对象上的method1()
,之后的调用流程全部都是在TargetClassDefinition
中,当method1()
调用method2()
时,它调用的是TargetObject
上的method2()
而不是ProxyObject
上的method2()
。而针对method2()
的横切逻辑,只织入到了ProxyObject
上的method2()
方法中。所以,在method1()
中调用的method2()
没有能够被拦截成功。
解决方案:
1、applicationContext.getBean(XXX).getMethod();
2、((TargetClassDefinition) AopContext.currentProxy()).method2();(需要ProxyConfig或者它相应的子类的exposeProxy属性设置为true)
jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到;
cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。
execution(public * test.aop.ServiceA.*(..))
还有个奇怪的现象,execution里如果不写权限,那么public protected package的方法都能被拦截到
如果写了public,那就只拦截public方法这个没问题,
如果写了protected,他就什么事情都不做,连protected的方法也不拦截。
分析
private方法 在Spring使用纯Spring AOP(只能拦截public/protected/包)都是无法被拦截的 因为子类无法覆盖;包级别能被拦截的原因是,如果子类和父类在同一个包中是能覆盖的。
在cglib代理情况下, execution(* *(..)) 可以拦截 public/protected/包级别方法(即这些方法都是能代理的)。
如果想要实现拦截private方法的 可以使用 原生 AspectJ 编译期/运行期织入。