全面解析动态代理 JDK Proxy 及 CGLib 原理

1 什么是代理

代理的目标是为某一个对象提供一个代理,并由代理对象来控制对原对象的访问。

依据代理类的创建时间,分为静态代理和动态代理。

静态代理是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前已经确定好。 动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。在程序运行时,通过反射机制动态创建而成。

简单地说,代理是用来在被代理对象基础上增加一些额外处理的机制,比如权限校验,日志处理等。

2 如何实现动态代理

动态代理的实现方式有两种:JDK Proxy 和 CGLib 代理方式。

动态代理的常见使用场景有 RPC 框架的封装、Spring AOP(面向切面编程)的实现、JDBC 的连接等。

2.1 JDK Proxy 动态代理

JDK Proxy 动态代理的实现无需引用第三方类,其动态代理类位于 java.lang.reflect 包中。

2.2.1 具体实现

实操撸一把代码:

package com.coding.wbp; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxyDemo { // 需要动态代理的接口 static interface Sister{ void sing(); } // 需要动态代理的实际对象 static class Wan implements Sister{ @Override public void sing() { System.out.println("I am wanqian."); } } // 需要动态代理的实际对象 static class Jin implements Sister{ @Override public void sing() { System.out.println("I am Jinsha."); } } //JDK proxy static class ProxyCheng implements InvocationHandler{ //要代理的真实对象 private Object target; //构造方法,给要代理的真实对象赋初值 ProxyCheng (Object obj){ this.target = obj; } public Object getInstance(){ Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); return o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("introduce yourself..."); // 执行调用方法(此方法执行前后,可以进行相关业务处理) Object result = method.invoke(target,args); System.out.println("score..."); return result; } } public static void main(String[] args) { //创建被代理类的对象 Sister wanqian = new Wan(); //创建代理类的对象 ProxyCheng proxyCheng = new ProxyCheng(wanqian); //获取动态代理类实例 Sister proxySister = (Sister) proxyCheng.getInstance(); System.out.println("JDK Dynamic object name: " + proxySister.getClass().getName()); proxySister.sing(); } }

以上程序运行结果:

JDK Dynamic object name: com.coding.wbp.$Proxy0 introduce yourself... I am wanqian. score... 结果分析:当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的 handler 对象的 invoke 方法来进行调用;即当执行 proxySister.sing();会自动调用 ProxyCheng 的 invoke 方法。

2.2.2 JDK proxy 底层原理

关键在于生成代理对象是用的 Proxy 类的静态方 newProxyInstance 获得代理对象。查看 JDK1.8 Proxy 类源码发现最终生成代理类字节码,部分关键代码:

//生成字节码 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

使用反编译工具将生成的字节码反编译,部分代码如下:

public final class ProxySister extends Proxy implements Sixter{ private static Method m1; public final void sing(){ try{ return this.h.invoke(this, m1, null); }catch (Error|RuntimeException localError){ throw localError; }catch (Throwable localThrowable){ throw new UndeclaredThrowableException(localThrowable); } } //...其他 }

这个代理类继承了 Proxy 类,并实现了之前定义的 Sister 接口。通过带有 InvocationHandler 参数的构造方法来创建实例,并把 InvocationHandler 传递到了 Proxy 类中,它覆写了接口中所有的方法,同时覆写了 Object 类的 equals、hashCode、toString 方法。到此就明白来龙去脉了。

2.2.3 JDK proxy 动态代理步骤

创建一个实现接口 InvocationHandler 的类,它必须实现 invoke 方法;创建被代理的类以及接口;通过 Proxy 的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理;通过代理调用要执行的方法。

2.3 CGLib 动态代理

JDK 动态代理机制只能代理实现接口的类,一般没有实现接口的类不能进行代理。

使用 CGLib 实现动态代理,完全不受代理类必须实现接口的限制。

CGLib 的原理是对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

2.3.1 具体实现

使用 CGLib 实现,需要第三方库,通过 pom.xml 引入:

<!----> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.0</version> </dependency>

实操撸一把代码:

package com.coding.wbp; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLibDemo { // 需要动态代理的实际对象 static class Sister{ public void sing() { System.out.println("I am Jinsha, a little sister."); } } static class CGLibProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target){ this.target = target; Enhancer enhancer = new Enhancer(); // 设置父类为实例类 enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("introduce yourself..."); Object result = methodProxy.invokeSuper(o,objects); System.out.println("score..."); return result; } } public static void main(String[] args) { CGLibProxy cgLibProxy = new CGLibProxy(); //获取动态代理类实例 Sister proxySister = (Sister) cgLibProxy.getInstance(new Sister()); System.out.println("CGLib Dynamic object name: " + proxySister.getClass().getName()); proxySister.sing(); } }

以上程序运行结果:

CGLib Dynamic object name: com.coding.wbp.CGLibDemo$Sister$$EnhancerByCGLIB$$d49b0c3e introduce yourself... I am Jinsha, a little sister. score... 结果分析:CGLib 的调用流程就是通过调用拦截器的 intercept 方法来实现对被代理类的调用。而拦截逻辑可以写在 intercept 方法的 invokeSuper(o, objects);的前后实现拦截。

2.3.2 CGLib 底层原理

通过查看 Enhancer 类源码,最终也是生成动态代理类的字节码,动态代理类继承要被代理的类,然后实现其方法。 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。

2.3.3 CGLib 实现步骤

创建被代理的类及方法;创建一个实现接口 MethodInterceptor 的代理类,重写 intercept 方法;创建获取被代理类的方法 getInstance(Object target);获取代理类,通过代理调用方法。

3 两者区别

JDK Proxy 和 CGLib 的区别主要体现在以下方面:

JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;JDK Proxy 是通过拦截器加反射的方式实现的;JDK Proxy 只能代理实现接口的类;JDK Proxy 实现和调用起来比较简单;CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。

4 常见面试问题

4.1 CGlib 是否一定快

使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。 对 JDK 动态代理与 CGlib 动态代理的代码进行测试,1W 次执行下,JDK 1.8 的动态代理性能比 CGlib 要好 20%左右。

4.1 聊聊 Spring AOP

1. AOP 思想

基于代理思想,对原来目标对象创建代理对象,在不修改原对象代码情况下,通过代理对象调用增强功能的代码,从而对原有业务方法进行增强。

2. AOP 的作用

在不修改源代码的情况下,可以实现功能的增强。

3. AOP 使用场景

记录日志(调用方法后记录日志)监控性能(统计方法运行时间)权限控制(调用方法前校验是否有权限)事务管理(调用方法前开启事务,调用方法后提交关闭事务 )缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )

4. AOP 的实现原理

Spring 中 AOP 的有两种实现方式:JDK proxy 和 CGLib 动态代理 当 Bean 实现接口时,Spring 就会用 JDK 的动态代理。 当 Bean 没有实现接口时,Spring 使用 CGlib 实现。 可以强制使用 CGlib 代理(在 spring 配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)

欢迎关注:Coding我不配 | 获取更多干货,一起每天进步一点点