ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从 $Proxy0 到 $$EnhancerByCGLIB:动态代理底层揭秘

从 $Proxy0 到 $$EnhancerByCGLIB:动态代理底层揭秘 1.代理在调用目标类时先调用代理类然后通过代理类调用目标类也就是说从直接调用转换成间接调用。优势就是我们可以在调用目标类前后进行额外的功能添加。静态代理和动态代理静态代理硬编码方式,就是系统运行就生成了代理类的字节码文件动态代理不需要硬编码而是在系统运行期间通过反射自动生成代理对象方式一JDK 动态代理JDK 动态代理要求目标对象必须实现至少一个接口。运行时JDK 会根据接口规范动态生成代理类创建代理对象并在调用原始方法前后织入增强逻辑从而实现对目标对象功能的扩展。1. 定义接口public interface UserService { void save(); String getUserByName(String name); }2. 创建需要代理的对象public class UserServiceImpl implements UserService { Override public void save() { System.out.println(A_Thinks 执行 save()); } Override public String getUserByName(String name) { System.out.println(A_Thinks 执行 getUserByName()); return Hello name; } }3. 创建处理器import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private final Object target; public MyInvocationHandler(Object target) { this.target target; } Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { System.out.println(方法执行前); Object result method.invoke(target, args); System.out.println(方法执行后); return result; } }注意这里的private final Object target;就是UserServiceImpl4. 测试效果// 4. 测试类 public class ProxyDemo { public static void main(String[] args) { // 创建真实对象 UserService realService new UserServiceImpl(); // 创建代理对象动态代理 UserService proxyService (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 类加载器 realService.getClass().getInterfaces(), // 要代理的接口列表 new LogInvocationHandler(realService) // 处理器 ); // 通过代理对象调用方法实际会进入InvocationHandler.invoke proxyService.save(); } }5. 原理调用 Proxy.newProxyInstance(类加载器, 接口, 处理器) 创建代理对象时实际上会在运行时生成一个名为$ProxyX如 $Proxy0的代理类。该类继承自 java.lang.reflect.Proxy并实现了传入的所有接口因此它天然具备了这些接口定义的全部方法。当我们调用代理对象的任意方法时其内部本质上都会转发为调用 InvocationHandler.invoke(...)从而将方法执行统一交由处理器来调度实现动态代理的效果。由此可见JDK 动态代理必须依赖接口数组来生成字节码这也解释了为什么目标对象必须实现接口。从 newProxyInstance 的参数设计也可以看出生成代理类字节码仅需接口数组即可无需目标对象的其他信息。其实现原理也不难推测JDK 动态代理会遍历所有接口中的方法并为每个方法生成类似 return (String) super.h.invoke(this, m0, new Object[]{var1}); 的转发代码以此将调用统一路由到 InvocationHandler。手写$ProxyX现在尝试手写$ProxyX理解下JDK到底实现了个什么样的对象。真正的 JDK $Proxy0 是 JVM/JDK 动态生成的我们现在自己写一个简化版import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class $Proxy0 implements UserService { private final InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h h; } Override public void save() { try { Method method UserService.class.getMethod(save); h.invoke( this, method, null ); } catch (Throwable e) { throw new RuntimeException(e); } } Override public String getUserByName(String name) { try { Method method UserService.class.getMethod( getUserByName, String.class ); return (String) h.invoke( this, method, new Object[]{name} ); } catch (Throwable e) { throw new RuntimeException(e); } } }然后手动创建代理public class Test { public static void main(String[] args) { // 真实对象 UserService target new UserServiceImpl(); // InvocationHandler InvocationHandler handler new MyInvocationHandler(target); // 手动创建代理对象 UserService proxy new $Proxy0(handler); proxy.save(); String result proxy.getUserByName(Tom); System.out.println(result); } }方法执行前A_Thinks 执行 save()方法执行后方法执行前A_Thinks 执行 getUserByName()方法执行后Hello Tom现在把整个过程拆开你调用proxy.getUserByName(Tom);这里的Proxy实际上就是new $Proxy0(handler)所以实际上执行的是$Proxy0.getUserByName(Tom);进入Override public String getUserByName(String name) { Method method UserService.class.getMethod( getUserByName, String.class ); return (String) h.invoke( this, method, new Object[]{name} ); }最后method.invoke(target, args) ↓ UserServiceImpl.getUserByName(Tom)方式二CGLIB 动态代理CGLIB 动态代理不要求目标对象实现接口。它通过继承目标类的方式生成代理子类利用字节码技术在运行时动态创建代理对象。因此它可以为任何非 final 的类创建代理。但需要注意的是由于CGLIB基于继承机制CGLIB 无法代理被 final 修饰的方法。由于该技术是脱离JDK的所以我们需要引入依赖。dependency groupIdcglib/groupId artifactIdcglib-nodep/artifactId version3.3.0/version /dependency注意如果使用 Spring则无需单独引入。Spring 已经将 CGLIB 重新打包进 spring-core包名为 org.springframework.cglib下面的示例代码只需把 import 换成 org.springframework.cglib.proxy.* 即可用法完全一致。1. 创建待代理类该目标类不需要实现任何接口。public class UserServiceImpl { public void save() { System.out.println(A_Thinks 执行 save()); } public String getUserByName(String name) { System.out.println(A_Thinks 执行 getUserByName()); return Hello name; } }2. 创建方法拦截器JDK 动态代理的处理器是 InvocationHandlerCGLIB 中对应的是 MethodInterceptorimport net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyMethodInterceptor implements MethodInterceptor { Override public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(方法执行前); // 调用目标类的原始方法重点见下方说明 Object result proxy.invokeSuper(obj, args); System.out.println(方法执行后); return result; } }intercept 的四个参数obj生成的代理对象本身即目标类的子类实例method目标类中被拦截的方法args方法参数 proxyMethodProxy方法代理对象封装了方法的快速调用机制关于调用目标方法有一个非常容易踩的坑必须用 proxy.invokeSuper(obj, args)它表示调用父类目标类的原始方法。千万不能写成 proxy.invoke(obj, args)因为 obj 是代理子类对象invoke 会再次触发 intercept造成无限递归最终栈溢出。另一种写法是 method.invoke(target, args)即通过反射调用持有的目标对象也能工作但会走反射性能不如 invokeSuper 的 FastClass 机制。3. 测试效果import net.sf.cglib.proxy.Enhancer; public class CglibProxyDemo { public static void main(String[] args) { // 1. 创建增强器 Enhancer enhancer new Enhancer(); // 2. 设置父类也就是目标类CGLIB 会生成它的子类 enhancer.setSuperclass(UserServiceImpl.class); // 3. 设置回调方法拦截器 enhancer.setCallback(new MyMethodInterceptor()); // 4. 创建代理对象本质是目标类的子类实例 UserServiceImpl proxy (UserServiceImpl) enhancer.create(); // 通过代理对象调用方法实际会进入 MethodInterceptor.intercept proxy.save(); String result proxy.getUserByName(Tom); System.out.println(result); } } 输出 方法执行前 A_Thinks 执行 save() 方法执行后 方法执行前 A_Thinks 执行 getUserByName() 方法执行后 Hello Tom4. 原理调用 enhancer.create() 时CGLIB 会基于 ASM 在运行时生成一个目标类的子类字节码类名形如 UserServiceImpl$$EnhancerByCGLIB$$xxxx。这个子类重写了父类中所有非 final、非 private 的方法每个重写的方法内部都会转发到 MethodInterceptor.intercept(...)以此实现增强。 所以 CGLIB 代理对象本质上是目标类的子类实例这与 JDK 动态代理代理对象是接口的实现类、与目标类没有继承关系是根本区别。这也解释了 CGLIB 的两个限制 final 类不能被继承因此无法被代理 final 方法不能被子类重写因此不会被增强调用时直接执行原方法拦截器不生效。另一个值得注意的点是性能JDK 动态代理的调用链路最终走 method.invoke 反射而 CGLIB 除了生成代理子类外还会生成 FastClass。FastClass 会给目标类和代理类的每个方法分配一个数字索引调用时通过 switch(index) 直接定位并调用目标方法完全避开反射开销。MethodProxy.invokeSuper 走的就是这条路。这也是早期认为 CGLIB 调用性能优于 JDK 动态代理的原因JDK 8 之后 JDK 动态代理做了大量优化二者差距已经很小。5. 手写生成的代理子类2.参考前面手写 $Proxy0 的思路我们手写一个简化版的 CGLIB 生成子类看看 CGLIB 到底生成了什么import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class UserServiceImpl$$EnhancerByCGLIB$$xxxx extends UserServiceImpl { private MethodInterceptor interceptor; public void setInterceptor(MethodInterceptor interceptor) { this.interceptor interceptor; } Override public void save() { try { Method method UserServiceImpl.class.getMethod(save); MethodProxy proxy MethodProxy.create( UserServiceImpl.class, // 目标类 this.getClass(), // 代理子类 ()V, // 方法描述符 save, // 原方法名 CGLIB$save$0 // 子类中生成的桥接方法名 ); interceptor.intercept( this, method, null, proxy ); } catch (Throwable e) { throw new RuntimeException(e); } } Override public String getUserByName(String name) { try { Method method UserServiceImpl.class.getMethod( getUserByName, String.class ); MethodProxy proxy MethodProxy.create( UserServiceImpl.class, this.getClass(), (Ljava/lang/String;)Ljava/lang/String;, getUserByName, CGLIB$getUserByName$1 ); return (String) interceptor.intercept( this, method, new Object[]{name}, proxy ); } catch (Throwable e) { throw new RuntimeException(e); } } // CGLIB 还会在子类中生成对应的桥接方法 // invokeSuper 最终就是通过 FastClass 索引调用这里 // 再直接调 super.xxx()绕开反射 final void CGLIB$save$0() { super.save(); } final String CGLIB$getUserByName$1(String name) { return super.getUserByName(name); } }我让ai生成了一个流转示例如下参考2.区别对比维度JDK 动态代理CGLIB 动态代理生成方式代理类继承Proxy并实现目标对象的接口代理类继承目标类是目标类的子类目标对象要求必须实现接口无需实现接口限制只能代理接口中定义的方法目标类不能是finalfinal方法无法被重写增强核心 APIProxy.newProxyInstance()InvocationHandlerEnhancerMethodInterceptor方法拦截入口InvocationHandler.invoke()MethodInterceptor.intercept()目标方法调用通常通过Method.invoke()调用目标方法通常通过MethodProxy.invokeSuper()调用父类原始方法代理类类型$Proxy0、$Proxy1……XXX$$EnhancerByCGLIB$$xxxx类型关系代理对象是接口的实现类与目标类不存在继承关系代理对象是目标类的子类强转方式一般只能转成目标接口类型可以转成目标类类型典型场景面向接口编程、JDK 原生代理没有接口的普通类代理
返回列表