Jdk和Cglib动态代理
在Java编程中,我们经常会看到或使用动态代理机制,而本文将对这两个代理进行相关的介绍。
jdk动态代理
jdk动态代理只能针对接口进行代理,所以在实现jdk动态代理的时候,我们要先定义一个接口。
1 2 3
| public interface JdkProxyInterface { String sayHello(); }
|
然后就是我们需要代理的类,这个代理的类需要实现一个接口,这个接口是将来jdk动态代理类实现的接口,这里我们就实现JdkProxyInterface接口。
1 2 3 4 5 6 7
| public class JdkTarget implements JdkProxyInterface{ @Override public String sayHello() { System.out.println("hello"); return "hello"; } }
|
然后使用jdk自带的Proxy来创建代理对象。
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { Proxy.newProxyInstance(JdkProxyInterface.class.getClassLoader(), new Class[]{JdkProxyInterface.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); }
|
在代理执行的方法中,我们可以进行目标方法的增强,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class test { public static void main(String[] args) { JdkTarget jdkTarget = new JdkTarget();
JdkProxyInterface jdkProxy = (JdkProxyInterface) Proxy.newProxyInstance(JdkProxyInterface.class.getClassLoader(), new Class[]{JdkProxyInterface.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置增强"); method.invoke(jdkTarget, args); System.out.println("后置增强"); return null; } }); jdkProxy.sayHello(); } }
|
执行结果如下:
这样我们就看到了,比起原先直接执行jdkTarget.sayHello(),只输入“hello”,我们通过代理目标对象,并执行代理对象的方法来对目标对象的方法进行了增强。
接下来,我们来具体深究一下这个代理是这么实现的。
由于这个代理类是直接生成字节码的,所以我们看不到源码,在这里,我们就自己来是实现一个代理来间接了解jdk的代理是如何实现的。
我们直接定义一个代理类,命名为$Proxy0,jdk内部也是类似的名字,所以我们也取这个名字。因为jdk动态代理是基于接口的,所以这个代理类还是实现对应的接口。
1 2 3 4 5 6 7 8 9
| public class $Proxy0 implements JdkProxyInterface { @Override public String sayHello() { System.out.println("前置增强"); System.out.println("hello"); System.out.println("后置增强"); return null; } }
|
可以看出,这就是一个最简单的代理,我们代理的对象是jdkTarget,我们对其的方法进行了增强,但是这都已经把增强的逻辑都写死了,真正的代理是可以更具我们自己写的增强逻辑来执行增强的。而这其中的关键就在于我们传的InvocationHandler对象,这个InvocationHandler是一个接口,我们自己也来创建一个接口来模仿,在我们之前传入对象的时候是用匿名内部类的方式来传的,所以我们知道这个接口中还有个invoke方法。
1 2 3
| public interface MyInvocationHandler { Object invoke(); }
|
然后是在创建代理对象的时候传的InvocationHandler对象,所以我们给我们自己的代理类加一个构造方法,来接受我们自己的MyInvocationHandler对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class $Proxy0 implements JdkProxyInterface { private MyInvocationHandler myInvocationHandler;
public $Proxy0(MyInvocationHandler myInvocationHandler) { this.myInvocationHandler = myInvocationHandler; }
@Override public String sayHello() { System.out.println("前置增强"); System.out.println("hello"); System.out.println("后置增强"); return null; } }
|
之后我们创建代理对象就变成这样了
1 2 3 4 5 6
| $Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() { @Override public Object invoke() { return null; } });
|
怎么样,是不是和jdk的很类似了。
那么我们这么根据自己写啥它就增强啥呢
这里我们就稍微改改逻辑就可以实现了,直接看代码吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class test { public static void main(String[] args) { $Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() { @Override public Object invoke() { System.out.println("前置增强"); System.out.println("hello"); System.out.println("后置增强"); return null; } }); proxy0.sayHello(); } }
public class $Proxy0 implements JdkProxyInterface { private MyInvocationHandler myInvocationHandler;
public $Proxy0(MyInvocationHandler myInvocationHandler) { this.myInvocationHandler = myInvocationHandler; }
@Override public String sayHello() { this.myInvocationHandler.invoke(); return null; } }
|
我们直接让代理执行方法直接执行所传过来的myInvocationHandler的invoke方法即可,然后在invoke方法中编写我们自己的代理增强逻辑,这样不就实现了我写啥,它就增强啥嘛。
有些同学可能对这块的逻辑转不过来,在这里解释一些,那就是在执行构造方法的时候,我们传了一个匿名内部类的对象,而这个匿名内部类是实现了MyInvocationHandler的(不懂的可以补补基础),然后我们就重写了其中的invoke方法,然后在$Proxy0这个类中的myInvocationHandler对象的值就为传过来的匿名子类对象,当我们调用invoke方法的时候,就会调用子类重写的方法,而子类重写的方法逻辑就是我们在传对象的时候写的哪个invoke逻辑。
但是还有问题,那就是对于原先的逻辑我们也是写死了,没有根据传哪个目标对象就增强哪个目标对象的哪个方法,在这里我们就可以仿照jdk的InvocationHandler的invoke方法来写就行,将invoke改写成下面这样
1 2 3
| public interface MyInvocationHandler { Object invoke(Object proxy, Method method, Object[] args); }
|
我们的代理类就改写成这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class $Proxy0 implements JdkProxyInterface { private MyInvocationHandler myInvocationHandler; private static Method method0; static { try { method0 = JdkProxyInterface.class.getMethod("sayHello"); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); }
}
public $Proxy0(MyInvocationHandler myInvocationHandler) { this.myInvocationHandler = myInvocationHandler; }
@Override public String sayHello() { Object result = this.myInvocationHandler.invoke(this, method0, null); return (String) result; } }
|
然后proxy0就设置成这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("前置增强"); try { String result = (String) method.invoke(new JdkTarget(), args); System.out.println("后置增强"); return result; } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } }); proxy0.sayHello();
|
这样就跟jdk动态代理差不多了吧,其实jdk动态代理生成的字节码反编译后得到的代理类和我这个差不多。这样应该对jdk动态代理有了一个比较清晰的认识。
这里再提一嘴,jdk动态代理在反射调用方法达到16次后,之后调用方法不再是用反射调用方法,而是直接调用方法,这样能进行些优化,这和之后说的cglib不同
cglib动态代理
老样子,先来看看基本的使用
1 2 3 4 5 6 7 8 9
| public class CglibTarget { public void sayHello() { System.out.println("hello"); }
public void sayWorld(int msg) { System.out.println(msg + "world"); } }
|
然后创建代理对象,对方法进行增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class test { public static void main(String[] args) { CglibTarget cglibTarget = new CglibTarget(); CglibTarget cglibProxy = (CglibTarget) Enhancer.create(CglibTarget.class, new Class[0], new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("前置增强"); method.invoke(cglibTarget, args)); System.out.println("后置增强"); return null; } }); cglibProxy.sayWorld(666); }
|
由于代理类是直接生成字节码的,所以我们拿不到源码,所以还是自己来实现一个cglib代理来理解
由于cglib是基于继承来实现代理,即需要有父子关系,代理类是目标类的一个子类,所以创建出来的代理类需要继承目标类,所以,如果目标类如果加了final关键字,那么就不能为其创建代理,因为被final修饰的类不能有子类,如果是在类的方法上加final,那么代理类将不能重写目标类的方法,这样子就起不到增强的作用,不过不会报错,只是掉的方法就是目标类的方法,没有增强。
有了之前的基础,这里我们直接实现一个差不多的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class $Proxy1 extends CglibTarget {
private MethodInterceptor methodInterceptor;
private static Method method0; private static Method method1;
static { try { method0 = CglibTarget.class.getMethod("sayHello"); method1 = CglibTarget.class.getMethod("sayWorld", int.class); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } }
public $Proxy1(MethodInterceptor methodInterceptor) { this.methodInterceptor = methodInterceptor; }
@Override public void sayHello() { try { methodInterceptor.intercept(this, method0, new Object[0], null); } catch (Throwable e) { throw new RuntimeException(e.getMessage()); } }
@Override public void sayWorld(int msg) { try { methodInterceptor.intercept(this, method1, new Object[]{msg}, null); } catch (Throwable e) { throw new RuntimeException(e.getMessage()); } } }
|
然后测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { CglibTarget cglibTarget = new CglibTarget(); $Proxy1 $Proxy1 = new $Proxy1(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("前置增强"); method.invoke(cglibTarget, args); System.out.println("后置增强"); return null; } }); $Proxy1.sayHello(); }
|
测试结果:
可以注意到,cglib的intercept传入的参数还有一个MethodProxy,这个是能让我们每次调用方法不是用反射进行调用,而是直接调用方法。methodProxy执行方法如下:
1 2
| methodProxy.invoke(cglibTarget, args); methodProxy.invokeSuper(proxy, args);
|
接下来我们来改造一下代理类,为其增加带原始功能的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public class $Proxy1 extends CglibTarget {
private MethodInterceptor methodInterceptor;
private static Method method0; private static Method method1; public static MethodProxy methodProxy0; public static MethodProxy methodProxy1;
static { try { method0 = CglibTarget.class.getMethod("sayHello"); method1 = CglibTarget.class.getMethod("sayWorld", int.class); methodProxy0 = MethodProxy.create(CglibTarget.class, $Proxy1.class, "()V", "sayHello", "sayHelloSuper"); methodProxy1 = MethodProxy.create(CglibTarget.class, $Proxy1.class, "(I)V", "sayWorld", "sayWorldSuper"); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } }
public $Proxy1(MethodInterceptor methodInterceptor) { this.methodInterceptor = methodInterceptor; }
public void sayHelloSuper() { super.sayHello(); } public void sayWorldSuper(int msg) { super.sayWorld(666); }
@Override public void sayHello() { try { methodInterceptor.intercept(this, method0, new Object[0], methodProxy0); } catch (Throwable e) { throw new RuntimeException(e.getMessage()); } }
@Override public void sayWorld(int msg) { try { methodInterceptor.intercept(this, method1, new Object[]{msg}, methodProxy1); } catch (Throwable e) { throw new RuntimeException(e.getMessage()); } } }
|
methodProxy在执行方法时不会使用反射,其内部创建类代理类来避免反射,一个代理类配合invoke使用,一个代理类配合invokeSuper使用,在invoke和invokeSuper生成的代理类是FastClass的子类
1 2 3 4 5 6
| public abstract class FastClass { public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException; public abstract int getIndex(Signature var1); }
|
接着我们来简单实现一下这个代理类,我们只实现FastClass中比较关键的两个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class TargetFastClass { static Signature signature0 = new Signature("sayHello", "()V"); static Signature signature1 = new Signature("sayWorld", "(I)V");
public int getIndex(Signature signature) { if (signature0.equals(signature)) { return 0; } else if (signature1.equals(signature)) { return 1; } return -1; } public Object invoke(int index, Object target, Object[] args) { if (index == 0) { ((CglibTarget) target).sayHello(); } else if (index == 1) { ((CglibTarget) target).sayWorld((int) args[0]); } else { throw new RuntimeException("无此方法"); } return null; }
}
|
当我们创建MethodProxy对象是就会生成这个类,这个类是执行invoke方法时的代理类,我们创建MethodProxy对象时会传入一些参数,比如方法名,返回值和参数类型,之后这个类会根据那些参数来生成Signature,即方法签名,并且给这些方法签名编号,比如0、1、2、3。方法签名生成完后,当我们取执行methodProxy.invoke(…)时,就会用自己的那些参数(方法名、返回值和参数类型)生成一个前面Signature,然后传入getIndex方法中,getIndex方法根据你传的方法签名在类中找看是否有相应的签名,如果有,就放回改签名对象的方法编号,然后再调用本类中的invoke,这个invoke会拿到从getIndex传来的方法编号,目标对象和参数则是我们之前执行methodProxy.invoke(…)的时候传的,然后它就会根据你的方法编号找到对应的方法,再通过目标对象直接执行方法,这样就避免了反射。
接下来,来模拟一下这个methodProxy.invoke(…)的过程
1 2 3 4 5 6 7 8
| public static void main(String[] args) { CglibTarget cglibTarget = new CglibTarget(); TargetFastClass targetFastClass = new TargetFastClass();4 int index = targetFastClass.getIndex(new Signature("sayHello", "()V")); targetFastClass.invoke(index, cglibTarget, new Object[0]); }
|
运行结果:
还有一个代理类是来配合invokeSuper的,直接看吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class ProxyFastClass {
static Signature signature0 = new Signature("sayHelloSuper", "()V"); static Signature signature1 = new Signature("sayWorldSuper", "(I)V");
public int getIndex(Signature signature) { if (signature0.equals(signature)) { return 0; } else if (signature1.equals(signature)) { return 1; } return -1; }
public Object invoke(int index, Object proxy, Object[] args) { if (index == 0) { (($Proxy1) proxy).sayHelloSuper(); } else if (index == 1) { (($Proxy1) proxy).sayWorldSuper((int) args[0]); } else { throw new RuntimeException("无此方法"); } return null; } }
|
这里我们创建方法签名的方法应该是那些只带有原始功能的方法,即sayHelloSuper和sayWorldSuper,因为我们只是为了执行方法时避免反射来执行,所以没必要为重写的增强方法来创建方法签名(Signature),而且也不能这样做,因为如果我们为增强方法创建Signature,那之后调用methodProxy.invokeSuper(proxy, args)的时候会最终调用到ProxyFastClass的invoke,而这个invoke又会回去调用增强方法,就会造成死循环。
这里也简单测试一下
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { $Proxy1 $Proxy1 = new $Proxy1(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return null; } }); ProxyFastClass proxyFastClass = new ProxyFastClass(); int index = proxyFastClass.getIndex(new Signature("sayWorldSuper", "(I)V")); proxyFastClass.invoke(index, $Proxy1, new Object[]{666}); }
|
执行结果:
这就是cglib的避免反射调用,和jdk相比,cglib一上来就可以避免反射调用,不想jdk那样还需要多执行几次反射调用后才会避免反射调用,jdk避免反射调用原理和cglib的差不多。
jdk的避免反射调用是一个方法对应一个代理,cglib是一个代理类会对应两个FastClass,即两个代理类,一个是对应配合着代理对象使用,一个配合着目标对象使用,而且一个FastClass能对应多个方法,所以cglib产生的代理类会比jdk少。
OK,over!!