介绍 JDK 动态代理 Java 标准库提供了动态代理功能,允许程序在运行期动态创建指定接口的实例。
CGLib 动态代理 使用 ASM 框架,加载代理对象的 Class 文件,通过修改其字节码生成子类。
cglib Github 仓库
适用场景
JDK 动态代理适用于实现接口的类,对未实现接口的类无能为力。
CGLib 不要求类实现接口,但对 final 方法无能为力。
性能比较
在 JDK 8 以前,CGLib 性能更好
从 JDK 8 开始,JDK 动态代理性能更好
根据 README.md 的提醒,cglib 已经不再维护,且在较新版本的 JDK 尤其是 JDK 17+ 中表现不佳,官方推荐可以考虑迁移到 ByteBuddy 。在如今越来越多的项目迁移到 JDK 17 的背景下,值得注意。
使用 代理对象的类和接口 代理对象的类和实现的接口:
1 2 3 public interface HelloService { void sayHello (String name) ; }
1 2 3 4 5 6 7 8 9 public class HelloServiceImpl implements HelloService { @Override public void sayHello (String name) { if (name == null ) { throw new IllegalArgumentException ("name can not be null" ); } System.out.println("Hello " + name); } }
JDK 动态代理示例
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 UserServiceInvocationHandler implements InvocationHandler { private Object target; public UserServiceInvocationHandler (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("do sth. before invocation" ); Object ret = method.invoke(target, args); System.out.println("do sth. after invocation" ); return ret; } catch (Exception e) { System.out.println("do sth. when exception occurs" ); throw e; } finally { System.out.println("do sth. finally" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class JdkProxyTest { public static void main (String[] args) { HelloService target = new HelloServiceImpl (); ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); UserServiceInvocationHandler invocationHandler = new UserServiceInvocationHandler (target); HelloService proxy = (HelloService) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); proxy.sayHello("Tom" ); System.out.println("=================" ); proxy.sayHello(null ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 do sth. before invocation Hello Tom do sth. after invocation do sth. finally ================= do sth. before invocation do sth. when exception occurs do sth. finally Exception in thread "main" java.lang.reflect.UndeclaredThrowableException at com.sun.proxy.$Proxy0.sayHello(Unknown Source) at com.moralok.proxy.jdk.JdkProxyTest.main(JdkProxyTest.java:19) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.moralok.proxy.jdk.UserServiceInvocationHandler.invoke(UserServiceInvocationHandler.java:18) ... 2 more Caused by: java.lang.IllegalArgumentException: name can not be null at com.moralok.proxy.HelloServiceImpl.sayHello(HelloServiceImpl.java:8)
CGLib 动态代理示例
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 3.3.0</version > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class UserServiceMethodInterceptor implements MethodInterceptor { @Override public Object intercept (Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { try { System.out.println("do sth. before invocation" ); Object ret = proxy.invokeSuper(obj, args); System.out.println("do sth. after invocation" ); return ret; } catch (Exception e) { System.out.println("do sth. when exception occurs" ); throw e; } finally { System.out.println("do sth. finally" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class CglibTest { public static void main (String[] args) { UserServiceMethodInterceptor methodInterceptor = new UserServiceMethodInterceptor (); Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(HelloServiceImpl.class); enhancer.setCallback(methodInterceptor); HelloService proxy = (HelloService) enhancer.create(); proxy.sayHello("Tom" ); System.out.println("=================" ); proxy.sayHello(null ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 do sth. before invocation Hello Tom do sth. after invocation do sth. finally ================= do sth. before invocation do sth. when exception occurs do sth. finally Exception in thread "main" java.lang.IllegalArgumentException: name can not be null at com.moralok.proxy.HelloServiceImpl.sayHello(HelloServiceImpl.java:8) at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.CGLIB$sayHello$0(<generated>) at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at com.moralok.proxy.cglib.UserServiceMethodInterceptor.intercept(UserServiceMethodInterceptor.java:14) at com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.sayHello(<generated>) at com.moralok.proxy.cglib.CglibTest.main(CglibTest.java:18)
查看 JDK 生成的代理类 使用以下语句,将在工作目录下生成代理类的 Class 文件。
1 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" );
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 56 57 58 59 60 61 62 63 64 65 66 public final class $Proxy0 extends Proxy implements HelloService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super (var1); } public final boolean equals (Object var1) throws { try { return (Boolean)super .h.invoke(this , m1, new Object []{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException (var4); } } public final void sayHello (String var1) throws { try { super .h.invoke(this , m3, new Object []{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException (var4); } } public final String toString () throws { try { return (String)super .h.invoke(this , m2, (Object[])null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException (var3); } } public final int hashCode () throws { try { return (Integer)super .h.invoke(this , m0, (Object[])null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException (var3); } } static { try { m1 = Class.forName("java.lang.Object" ).getMethod("equals" , Class.forName("java.lang.Object" )); m3 = Class.forName("com.moralok.proxy.HelloService" ).getMethod("sayHello" , Class.forName("java.lang.String" )); m2 = Class.forName("java.lang.Object" ).getMethod("toString" ); m0 = Class.forName("java.lang.Object" ).getMethod("hashCode" ); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError (var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError (var3.getMessage()); } } }
查看 CGLib 生成的子类 使用以下语句,将 CGLib 生成的子类的 Class 文件输出到指定目录,会发现出现了 3 个 Class 文件。
1 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\Users\\username\\Class" );
HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31.class,代理类
HelloServiceImpl$$FastClassByCGLIB$$a5654167.class,被代理类的 FastClass
HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511.class,代理类的 FastClass
代理类定义 继承了被代理类。
1 2 public class HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31 extends HelloServiceImpl implements Factory {}
静态代码块 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 static { CGLIB$STATICHOOK1(); } static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal (); CGLIB$emptyArgs = new Object [0 ]; Class var0 = Class.forName("com.moralok.proxy.HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31" ); Class var1; Method[] var10000 = ReflectUtils.findMethods(new String []{"equals" , "(Ljava/lang/Object;)Z" , "toString" , "()Ljava/lang/String;" , "hashCode" , "()I" , "clone" , "()Ljava/lang/Object;" }, (var1 = Class.forName("java.lang.Object" )).getDeclaredMethods()); CGLIB$equals$1 $Method = var10000[0 ]; CGLIB$equals$1 $Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z" , "equals" , "CGLIB$equals$1" ); CGLIB$toString$2 $Method = var10000[1 ]; CGLIB$toString$2 $Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;" , "toString" , "CGLIB$toString$2" ); CGLIB$hashCode$3 $Method = var10000[2 ]; CGLIB$hashCode$3 $Proxy = MethodProxy.create(var1, var0, "()I" , "hashCode" , "CGLIB$hashCode$3" ); CGLIB$clone$4 $Method = var10000[3 ]; CGLIB$clone$4 $Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;" , "clone" , "CGLIB$clone$4" ); CGLIB$sayHello$0 $Method = ReflectUtils.findMethods(new String []{"sayHello" , "(Ljava/lang/String;)V" }, (var1 = Class.forName("com.moralok.proxy.HelloServiceImpl" )).getDeclaredMethods())[0 ]; CGLIB$sayHello$0 $Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V" , "sayHello" , "CGLIB$sayHello$0" ); }
MethodProxy 稍后再做介绍。
构造器方法 构造器方法内,调用了绑定回调(Callbacks)方法。
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 public HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31() { CGLIB$BIND_CALLBACKS(this ); } private boolean CGLIB$BOUND;private static final void CGLIB$BIND_CALLBACKS(Object var0) { HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31 var1 = (HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31)var0; if (!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true ; Object var10000 = CGLIB$THREAD_CALLBACKS.get(); if (var10000 == null ) { var10000 = CGLIB$STATIC_CALLBACKS; if (var10000 == null ) { return ; } } var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0 ]; } }
生成的代理方法 CGLib 会为每一个代理方法生成两个对应的方法,一个直接调用父类方法,一个则调用回调(拦截器)的 intercept 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 final void CGLIB$sayHello$0 (String var1) { super .sayHello(var1); } public final void sayHello (String var1) { MethodInterceptor var10000 = this .CGLIB$CALLBACK_0; if (var10000 == null ) { CGLIB$BIND_CALLBACKS(this ); var10000 = this .CGLIB$CALLBACK_0; } if (var10000 != null ) { var10000.intercept(this , CGLIB$sayHello$0 $Method, new Object []{var1}, CGLIB$sayHello$0 $Proxy); } else { super .sayHello(var1); } }
CGLib 通过继承实现动态代理的过程,在查看生成的子类的 Class 后,是非常容易理解的。拦截器的参数有代理对象、Method、方法参数和 MethodProxy 对象。
分析 MethodProxy 如何在拦截器中调用被代理的方法呢?就是通过 MethodProxy 实现的。
创建 MethodProxy MethodProxy 是 CGLib 为每一个代理方法创建的方法代理,当调用拦截的方法时,它被传递给 MethodInterceptor 对象的 intercept 方法。它可以用于调用原始方法,或对同一类型的不同对象调用相同方法。
1 2 3 4 5 6 7 8 9 10 11 12 CGLIB$sayHello$0 $Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V" , "sayHello" , "CGLIB$sayHello$0" ); public static MethodProxy create (Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy (); proxy.sig1 = new Signature (name1, desc); proxy.sig2 = new Signature (name2, desc); proxy.createInfo = new CreateInfo (c1, c2); return proxy; }
CreateInfo 静态内部类,保存被代理类和代理类以及其他一些信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static class CreateInfo { Class c1; Class c2; NamingPolicy namingPolicy; GeneratorStrategy strategy; boolean attemptLoad; public CreateInfo (Class c1, Class c2) { this .c1 = c1; this .c2 = c2; AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); if (fromEnhancer != null ) { namingPolicy = fromEnhancer.getNamingPolicy(); strategy = fromEnhancer.getStrategy(); attemptLoad = fromEnhancer.getAttemptLoad(); } } }
FastClass 和方法索引对 调用原始方法 invokeSuper MethodProxy 通过 invokeSuper 调用原始方法(父类方法)。
1 2 3 4 5 6 7 8 9 10 11 12 public Object invokeSuper (Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
生成 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 private void init () { if (fastClassInfo == null ) { synchronized (initLock) { if (fastClassInfo == null ) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo (); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; createInfo = null ; } } } }
FastClass 信息
f1 是被代理类的 FastClass 对象,i1 是 CGLIB$sayHello$0
方法在生成的 FastClass 中的索引。
f2 是代理类的 FastClass 对象,i2 是 sayHello
方法在生成的 FastClass 中的索引。
invoke 方法根据传入的方法索引,快速定位要调用对象 obj 的哪个方法。
CGLib 完全有能力获得 CGLIB$sayHello$0
的 Method 对象,通过反射实现调用,这样处理逻辑更加清楚。但是早期 Java 反射的性能并不好,通过 FastClass 机制避免使用反射从而提升了性能。
1 2 3 4 5 6 7 private static class FastClassInfo { FastClass f1; FastClass f2; int i1; int i2; }
FastClass 的 invoke 方法 以代理类的 FastClass HelloServiceImpl$$EnhancerByCGLIB$$c51b2c31$$FastClassByCGLIB$$c068b511
为例,当传入的方法索引为 16 时,就会调用 CGLIB$sayHello$0
方法。
获取代理对象
根据传入的方法索引,调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Object invoke (int var1, Object var2, Object[] var3) throws InvocationTargetException { HelloServiceImpl..EnhancerByCGLIB..c51b2c31 var10000 = (HelloServiceImpl..EnhancerByCGLIB..c51b2c31)var2; int var10001 = var1; try { switch (var10001) { case 0 : return new Boolean (var10000.equals(var3[0 ])); case 16 : var10000.CGLIB$sayHello$0 ((String)var3[0 ]); return null ; } } catch (Throwable var4) { throw new InvocationTargetException (var4); } throw new IllegalArgumentException ("Cannot find matching method/constructor" ); }
获取方法索引 怎么知道方法的索引呢?在初始化 FastClass 信息时,不仅生成了 FastClass,还通过 getIndex 获取方法的索引。
在 JDK 7 之后,switch 不仅可以支持 int、enum,还能支持 String,CGLib 这样实现是出于兼容性的考虑还是说有什么性能提升?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public int getIndex (Signature var1) { String var10000 = var1.toString(); switch (var10000.hashCode()) { case -1721191351 : if (var10000.equals("CGLIB$sayHello$0(Ljava/lang/String;)V" )) { return 16 ; } break ; } return -1 ; }
总结和思考 两者在使用上是相仿的。
对于两者的源码,读得不多。有时候会感慨,看这么多年前的代码,还是感觉吃力。有时候想,如果不好好看源码,心里不踏实;如果花很多时间理清楚了,但是发现更多只是知道了一些细节,于整体理解的提升不大,又会感觉不值得。
但也提醒自己,不要太在意,用得本就不多,涉及源码的机会更是没有,如果方方面面都要细究,人生太短,智商不够,等涉足相关问题再回头研究。
基础的用法和概念应该了解,不然看到 Spring AOP 源码时,分不清 Spring 封装的边界在哪里。
借着梳理 Spring 的机会回头再看,又感觉轻松不少。