基于JDK动态代理原理解析
1、回顾一下JDK动态代理的核心参数
如果我们要为target类创建一个【JDK动态代理对象】,那么我们必须要传入如下三个核心参数
- 加载target类的类加载器
- target类实现的接口
- InvocationHandler
为什么必须要这三个参数呢?之前使用动态代理的时候都是直接按接口要求传这三个参数,但从来没想过为什么?下面仔细去探究一下
2、实现一个简单的动态代理
【JDK动态代理】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去创建的动态代理对象,我们这里也使用这个方法去创建一个简单的【动态代理对象】以便于理解他的核心原理。
①、定义接口Subject
1 2 3 4 5 6 7 8 9 10 11 12 13 | public interface Subject { /** * 接口方法 */ void doSomething(); /** * sayHello * * @param name name * @return string */ String sayHello(String name); } |
②、定义接口的实现类RealSubject
实现Subject接口,并实现接口相关方法
1 2 3 4 5 6 7 8 9 10 11 | public class RealSubject implements Subject { @Override public void doSomething() { System.out.println( "RealSubject do something" ); } @Override public String sayHello(String name) { System.out.println( "RealSubject sayHello" ); return "hello-" + name; } } |
③、定义代理对象创建工厂
- 定义一个工厂类,该工厂类用于为target对象生产代理对象
- 该工厂类借助
Proxy.newProxyInstance
来为目标对象创建代理对象
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 class JdkDynamicProxyFactory { /** * 创建target类的代理对象 * 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法 * * @param <T> 泛型 * @return 代理对象 */ public static <T> T getProxy(Object target) { // 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】 Object proxyInstance = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @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; } }); // 返回代理对象 return (T) proxyInstance; } } |
④、创建测试类,为target创建代理对象
该测试类会将内存中的动态代理对象保存到磁盘上,以便于我们后续分析生成的动态代理类的具体结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class Client { public static void main(String[] args) { // 保存生成的代理类的字节码文件 System.getProperties().put( "sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" ); // 目标对象 RealSubject target = new RealSubject(); // 使用JDK动态代理为【target对象】创建代理对象 Subject proxy = JdkDynamicProxyFactory.getProxy(target); // 调用代理对象的方法 proxy.doSomething(); System.out.println( "=====================华丽的分割线=====================" ); proxy.sayHello( "wenpan" ); } } |
3、分析生成的动态代理类的结构
在上面一步中我们使用
将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样
- 可以看到代理类【继承了Proxy类】,并且【实现了目标接口Subject】,覆写了接口中的【每一个方法】
- 这也说明了为什么JDK代理需要实现接口,因为Java是单继承的,既然代理类继承了Proxy类那么就无法再继承其他类了
- 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来(主要是为了方便后面的【反射调用】)
- 在调用动态代理对象的某个方法时(比如:调用doSomething方法),实质上是调用的【Proxy类】的【h属性】的invoke方法
- 所以我们要重点去看看这个【Proxy.h】到底是个什么鬼?其实他就是创建代理对象是我们传入的【InvocationHandler】
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | // 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject public final class $Proxy0 extends Proxy implements Subject { // 2、可以看到,在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来 private static Method m1; private static Method m3; private static Method m4; private static Method m2; private static Method m0; // 以静态代码块的形式为属性赋值 static { try { m1 = Class.forName( "java.lang.Object" ).getMethod( "equals" , Class.forName( "java.lang.Object" )); m3 = Class.forName( "com.stone.design.mode.proxy.jdk.Subject" ).getMethod( "doSomething" ); m4 = Class.forName( "com.stone.design.mode.proxy.jdk.Subject" ).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()); } } public $Proxy0(InvocationHandler var1) throws { super (var1); } // 3、object父类的equals方法 public final boolean equals(Object var1) throws { try { // 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行 // 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看 // 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法 return (Boolean) super .h.invoke( this , m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } // 4、目标接口的方法 public final void doSomething() throws { try { // 调用了【Proxy.h属性】的invoke方法 // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看 super .h.invoke( this , m3, (Object[]) null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // 5、目标接口的方法 public final String sayHello(String var1) throws { try { // 调用了【Proxy.h属性】的invoke方法 // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看 return (String) super .h.invoke( this , m4, 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); } } } |
4、Proxy.h是什么鬼
在上面的第三点中我们看到,执行代理对象的方法的时候其实质是调用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我们仔细看看【Proxy.h】到底是什么鬼
①、Proxy.newProxyInstance是如何创建代理对象的
下面的源代码我做了一些删减,只留下了最核心的部分
- 通过下面代码我们就明确了使用【newProxyInstance】方法创建代理对象时所做的几件事情
- 首先通过【目标接口】 + 【类加载器】创建一个Proxy类的【Class对象】
- 然后通过这个【Class对象】获取到他的有参构造器,并且传入我们自定义的【InvocationHandler】作为构造函数参数,并且通过反射的方式调用有参构造器创建一个【Proxy对象】
- 在【Proxy的有参构造器】中,会将传入的【InvocationHandler】保存到 【h属性】上(方便后面的supper.h.invoke调用)
- 代理对象创建完毕
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 | public class Proxy implements java.io.Serializable { // h属性,保存我们传递进来的InvocationHandler protected InvocationHandler h; // 【有参构造器】注意这里的参数 protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this .h = h; } // 生成代理对象的方法 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{ // 1、InvocationHandler强制不允许为空 Objects.requireNonNull(h); // 获取到目标接口 final Class<?>[] intfs = interfaces.clone(); /* * Look up or generate the designated proxy class. * 2、获取到代理类的Class对象(也就是Proxy) */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 通过反射执行 cl 的有参构造,也就是下面这个,可以看到通过反射执行Proxy有参构造, * 将InvocationHandler赋值到了h属性上 */ try { // 3、获取到有参构造器 final Constructor<?> cons = cl.getConstructor(constructorParams); // 4、通过构造器来创建一个代理对象并返回,这里传入的参数h 就是我们的【InvocationHandler】 return cons.newInstance( new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { // 省略.... } } } |
5、问题总结
现在再来看上面抛出的三个问题!
为什么创建代理对象时需要传入如下三个参数:
- 加载target类的类加载器
- target类实现的接口
- InvocationHandler
①、为什么要传入类加载器
因为动态代理类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而动态代理类则是在【运行过程中动态生成的类】。
那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。
所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】
②、为什么要传入target类实现的接口
为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行
- 从上面的【动态生成的代理类的结构】来看,代理类继承了Proxy类,由于【Java是单继承】的,所以无法再通过继承的方式来继承【目标类】了。
- 所以动态代理类需要【实现目标接口】,来重写接口的方法
③、为什么要传入InvocationHandler
- 从【动态生成的代理类的结构】可以看出,我们传入的
InvocationHandler
最终会被作为一个属性保存到Proxy对象的【h属性】上 - 并且【动态代理对象】会覆写【目标接口的所有方法】,在方法中会使用
supper.h.invoke
的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler
- 动态代理的每个方法调用都会先走
InvocationHandler.invoke()
方法
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
使用Mybatis Plus整合多数据源和读写分离的详细过程
这篇文章主要介绍了Mybatis Plus整合多数据源和读写分离的详细过程,mybatisplus可以整合阿里的分布式事务组件seata,本文通过示例代码给大家介绍的非常详细,需要的朋友参考下吧2021-09-09
最新评论