最近在阅读到了Spring源码对于两种动态代理使用在不同场景下的使用,两种方式各有利弊写一篇文加深自己的认识。文中对于源码的涉及较少,更多的是作者自己的理解和举例,然后通过部分源码验证。
首先看两个面试经常会遇到的关于Spring的问题:
其实上面可以看出出Spring在使用两种代理方式时的不同处理:@Configuration修饰的类被Cglib动态代理后,类内部方法调用也可以走增强逻辑,而含有@Transactional注解的类无论是Cglib还是JDK动态代理都不能进行方法内部的相互调用。
两种代理方式的调用逻辑
JDK动态代理
标准的用法
public interface TestInterface {void sayHello();
}public static class Test implements TestInterface {@Overridepublic void sayHello() {System.out.println("hello");}
}//需要定义一个实现了InvocationHandler接口的对象,目的是获取被代理类的实例和自定义增强方法
public static class MyInvocationHandler implements InvocationHandler{//被代理类的实例对象protected Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("增强方法");//调用被代理类的实例对象通过反射执行目标方法Object result = method.invoke(target, args);return result;}
}public static void main(String[] args) {Test test = new Test();TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test));testInterface.sayHello();
}
复制代码
下面是代理类的逻辑代码,这个代理类并不是用反编译内存中的代理类来获取得,是作者自己整了一个类似的,如果要获取真正的代理类代码网上方法很多
//代理类的父类,里面有生成代理类的主要逻辑
public static class Proxy{//被代理对象实例的调用对象protected InvocationHandler h;
}
//生成的代理类继承Proxy主要是为了使用父类中的InvocationHandler对象来调用被代理类对象的目标方法
//实现共同接口是为了获取需要增强的目标方法
public static class TestProxy extends Proxy implements TestInterface{protected TestProxy(InvocationHandler h) {super(h);}@Overridepublic void sayHello() {try {//这里对获取接口方法做了简化处理//调用父类中存储的被代理对象的handler执行代理逻辑super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null);} catch (Throwable e) {throw new RuntimeException(e);}}
}
复制代码
逻辑图
Cglib动态代理
标准的用法
public static class Test implements TestInterface {@Overridepublic void sayHello() {System.out.println("hello");}
}//实现MethodInterceptor接口注册回调函数对代理类中所有方法进行拦截增强
public static class MyInvocationHandler implements MethodInterceptor {//o为继承了被代理类的代理类对象,method为执行方法,objects为方法参数//methodProxy为代理对象方法,其中有被代理方法和代理方法的映射关系@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("增强方法");return methodProxy.invokeSuper(o,objects);}
}public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Test.class);enhancer.setCallback(new MyInvocationHandler());TestInterface testInterface = (TestInterface)enhancer.create();testInterface.sayHello();
}
复制代码
动态生成代理类的伪代码,省略了很多很多细节
//Cglib中都是通过代理类中的方法来替换被代理类,然后直接调用代理类对象的方法即可
public static class TestProxy extends Test{public final void sayHello() {System.out.println("增强方法");super.sayHello();}
}
复制代码
逻辑图
==============================================================
从上面可以看出Cglib和JDK最大的区别在于Cglib实现的动态代理并没有被代理类的实例对象,所有的方法调用都是通过代理类来实现的(子类方法 -> 增强逻辑 -> 子类代理方法 -> 父类方法),而JDK则同时生成了被代理类和代理类的实例对象,然后在代理类中保存有被代理类的引用,目标方法的调用还是被代理对象执行的。Cglib方法调用时是使用代理类对象内部方法的相互调用实现的,由于代理类的所有方法都进行了改写,所以内部调用也会被增强,JDK方法调用时是代理类对象和被代理类对象间方法的相互调用实现的,只有通过调用代理类对象的代理方法时才会走增强逻辑,而如果是被代理对象自己的内部调用,被代理对象方法没有改变,所以无法增强。 理解了这一点再看Spring动态代理的使用就好理解了
Spring源码验证
调用@Configuration注解的类时会用到的代理类拦截器
//Spring中Enhancer对象注册的三种拦截器
//回调数组,根据CALLBACK_FILTER中accept方法返回的索引值去从该数组中选择对应的Callback
private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(),new BeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE
};//BeanMethodInterceptor的intercept方法,对父类中所有带有@Bean注解的方法都进行拦截增强
//无论是Spring通过反射实例化Bean还是配置类中方法的内部调用,都会通过BeanFactory来生成和获取Bean实例
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,MethodProxy cglibMethodProxy) throws Throwable {//是否为Spring通过反射调用 if (isCurrentlyInvokedFactoryMethod(beanMethod)) {//调用父类方法生成新的Bean对象,并将其注册到Spring上下文中return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);}//类方法的内部调用,从BeanFactory中获取bean//即使通过内部方法直接调用为能保证获取的对象为同一实例return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
复制代码
Cglib对于@Transactional注解采用的代理类拦截器DynamicAdvisedInterceptor
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Class> targetClass = null;Object target = null;try {if (this.advised.exposeProxy) {oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}//Spring缓存了被代理类的实例//获取被代理类实例target = getTarget();if (target != null) {targetClass = target.getClass();}//获取目标方法的拦截器链,被@Transactional修饰的方法会有缓存方法和调用链关系List
JDK的处理逻辑同样是调用被代理类来执行未加@Transactional注解的方法,就不多写了。
小结
Cglib动态代理与JDK动态代理的区别本质上应该对于代理对象的调用方式有差别,Cglib是直接将代理类对象作为目标对象使用,增强逻辑直接写入代理类的子类方法中,调用方法时只需一个代理类对象即可,而JDK则是将被代理类对象引用存放在代理类对象中,增强逻辑在代理对象中存放而实际执行方法还需要调用被代理对象。当然Cglib通过缓存被代理类的实例对象也可以做到JDK的效果。
两种代理方式一个通过继承获取类方法信息,一种通过接口获取类方法信息,在理解其原理后,如何选型使用还是看业务场景和两种方式的执行效率来决定。