JDK动态代理学习

在之前的博文《simpleRpc解析-客户端》中,提到了客户端通过JDK动态代理获取Service的代理类,然后通过代理类去执行Service中定义的方法。在动态代理类创建的过程中,使用的是InvocationHandler的匿名类。

一开始,我只是简单的从代码角度去查看,以为在执行Proxy.newProxyInstance()方法的时候会一并执行此匿名类(当然也包括它的invok()方法),然后直接返回代理类,代理类就是一个Service的实现。想法倒也不错,但后来仔细一看,发现匿名类中的invoke(Object object, Method method, Object[] args)方法,怎么看也看不出这几个参数是如何传递进去的!我再想,在创建代理的时候,代码怎么知道我会调用哪个方法呢?肯定不知道啊,那它怎么处理这个Method参数呢?费解

再往后看,看到代理类调用service中的方法,又想到之前的invoke()方法已经执行了,这里再调用一次吗?不应该是这样的。后来,我debug此代理类时,看不到什么有效信息,有点迷惑,所以打算再温习下JDK动态代理

下面就开始吧

提供一个接口定义:

1package com.paic.gof.proxy; 2 3public interface Subject { 4 5 public void rent(); 6 7 public String hello(String str); 8}

直接上测试类(使用匿名类方式)

1package com.paic.gof.proxy; 2 3import java.lang.reflect.InvocationHandler; 4import java.lang.reflect.Method; 5import java.lang.reflect.Proxy; 6import java.util.Iterator; 7import java.util.Properties; 8import java.util.Set; 9 10public class ProxyTest2 { 11 public static void main(String[] args) { 12 /* 13 * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 14 * handler.getClass().getClassLoader():这里使用handler这个类的ClassLoader对象来加载我们的代理对象 15 * realSubject.getClass().getInterfaces():这里为代理对象提供的接口是真实对象所实现的接口, 16 * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 17 * handler:这里将这个代理对象关联到了上方的InvocationHandler 这个对象上 18 */ 19 //下面一句可得到生成的代理类$Proxy0的.class文件,可以反编译查看其内容 20 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 21 System.out.println("-----------111---------"); 22 Subject subject = (Subject) Proxy.newProxyInstance( Subject.class.getClassLoader(), 23 new Class<?>[] {Subject.class}, 24 new InvocationHandler() { 25 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 26 System.out.println("-----------333---------"); 27 System.out.println("method : " + method.getName() + " / args = " + args); 28 return "【invoke-->" + method.getName() + "】"; 29 } 30 }); 31 System.out.println("-----------222---------"); 32 System.out.println(subject.hello("world")); 33 34 System.out.println("-----------444---------"); 35 System.out.println(subject.toString()); 36 System.out.println("-----------555---------"); 37 38 System.out.println("class : " + subject.getClass().getName()); 39 System.out.println("super class : " + subject.getClass().getSuperclass()); 40 Class<?>[] interfacess = subject.getClass().getInterfaces(); 41 for(Class ccc : interfacess) { 42 System.out.println("super interface : " + ccc.getName()); 43 } 44 } 45}

执行结果如下:

1-----------111--------- 2-----------222--------- 3-----------333--------- 4method : hello / args = [Ljava.lang.Object;@7d4991ad 5【invoke-->hello】 6-----------444--------- 7-----------333--------- 8method : toString / args = null 9【invoke-->toString】 10-----------555--------- 11class : com.sun.proxy.$Proxy0 12super class : class java.lang.reflect.Proxy 13super interface : com.paic.gof.proxy.Subject

可以从执行结果结合代码来分析:

  • 执行顺序

通过打印出来的数字可以了解代码的执行顺序,可以看到在生成代理类的时候并未执行invoke()方法, 而在代理类对象执行Subject接口定义的方法时,才开始真正的执行!

  • 代理对象

根据打印的日志知道,class : com.sun.proxy.$Proxy0,这就是代理对象subject,下面是它的debug信息:

信息比较少

再看看它的父类:super class : class java.lang.reflect.Proxy

实现的接口:super interface : com.paic.gof.proxy.Subject

可以了解到,它是一个JDK反射生成的继承了Proxy类并实现给定接口Subject的类

在代码中,通过System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");可以得到它的class信息,生成的.class文件路径如下:

经过反编译得到:

1package com.sun.proxy; 2 3import com.paic.gof.proxy.Subject; 4import java.lang.reflect.InvocationHandler; 5import java.lang.reflect.Method; 6import java.lang.reflect.Proxy; 7import java.lang.reflect.UndeclaredThrowableException; 8 9public final class $Proxy0 10 extends Proxy 11 implements Subject 12{ 13 private static Method m1; 14 private static Method m4; 15 private static Method m2; 16 private static Method m3; 17 private static Method m0; 18 19 public $Proxy0(InvocationHandler paramInvocationHandler) 20 throws 21 { 22 super(paramInvocationHandler); 23 } 24 25 public final boolean equals(Object paramObject) 26 throws 27 { 28 try 29 { 30 return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); 31 } 32 catch (Error|RuntimeException localError) 33 { 34 throw localError; 35 } 36 catch (Throwable localThrowable) 37 { 38 throw new UndeclaredThrowableException(localThrowable); 39 } 40 } 41 42 public final void rent() 43 throws 44 { 45 try 46 { 47 this.h.invoke(this, m4, null); 48 return; 49 } 50 catch (Error|RuntimeException localError) 51 { 52 throw localError; 53 } 54 catch (Throwable localThrowable) 55 { 56 throw new UndeclaredThrowableException(localThrowable); 57 } 58 } 59 60 public final String toString() 61 throws 62 { 63 try 64 { 65 return (String)this.h.invoke(this, m2, null); 66 } 67 catch (Error|RuntimeException localError) 68 { 69 throw localError; 70 } 71 catch (Throwable localThrowable) 72 { 73 throw new UndeclaredThrowableException(localThrowable); 74 } 75 } 76 77 public final String hello(String paramString) 78 throws 79 { 80 try 81 { 82 return (String)this.h.invoke(this, m3, new Object[] { paramString }); 83 } 84 catch (Error|RuntimeException localError) 85 { 86 throw localError; 87 } 88 catch (Throwable localThrowable) 89 { 90 throw new UndeclaredThrowableException(localThrowable); 91 } 92 } 93 94 public final int hashCode() 95 throws 96 { 97 try 98 { 99 return ((Integer)this.h.invoke(this, m0, null)).intValue(); 100 } 101 catch (Error|RuntimeException localError) 102 { 103 throw localError; 104 } 105 catch (Throwable localThrowable) 106 { 107 throw new UndeclaredThrowableException(localThrowable); 108 } 109 } 110 111 static 112 { 113 try 114 { 115 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); 116 m4 = Class.forName("com.paic.gof.proxy.Subject").getMethod("rent", new Class[0]); 117 m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 118 m3 = Class.forName("com.paic.gof.proxy.Subject").getMethod("hello", new Class[] { Class.forName("java.lang.String") }); 119 m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 120 return; 121 } 122 catch (NoSuchMethodException localNoSuchMethodException) 123 { 124 throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); 125 } 126 catch (ClassNotFoundException localClassNotFoundException) 127 { 128 throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); 129 } 130 } 131}

从反编译信息中看到它的类定义确实和日志记录是一致的,并且在初始化块中初始化了可被调用的方法m0-m4

至于这个代理对象如何生成的,后续再分析,暂略过

再看下源码的hello()方法,这个方法是Subject接口中定义的:

1 public final String hello(String paramString) 2 throws 3 { 4 try 5 { 6 return (String)this.h.invoke(this, m3, new Object[] { paramString }); 7 } 8 catch (Error|RuntimeException localError) 9 { 10 throw localError; 11 } 12 catch (Throwable localThrowable) 13 { 14 throw new UndeclaredThrowableException(localThrowable); 15 } 16 }

分析下这个方法

在代码中执行System.out.println(subject.hello("world"));

代理类对象subject调用hello()方法,由于subject的定义是接口定义:Subject subject,那么它运行的时候实际是调用其实现类去处理的,这里的实现类就是动态生成的代理类:com.sun.proxy.$Proxy0,而该方法实际调用方式为:

this.h.invoke(this, m3, new Object[] { paramString })

this就代表它本身

m3可以从其初始化块中知道代表hello()

h代表InvocationHandler,从$Proxy0的构造方法可以看到它调用了父类的构造方法(它的父类就是Proxy):super(paramInvocationHandler);

而Proxy中的定义:protected InvocationHandler h;

也就是说,这里实际调用的是Proxy中的变量InvocationHandler,也就是Proxy.newProxyInstance()中的第三个参数,即上面定义的匿名类

理一下:

接口定义的subject对象调用hello()方法,利用多态,实际调用的是代理类的$Proxy0的hello(),而这里的hello()又转而调用其父类的InvocationHandler实现类,也就是调用了匿名类的invoke()方法,在这个方法里实现我们需要的扩展逻辑!

关于这里的h,通过上面对于代理对象subject的截图中发现,有一个属性h=ProxyTest2$1

并非InvocationHandler,不知道这里的h是不是同一个对象

  • 非匿名类测试

再使用非匿名类来测试下:

接口实现类:

1package com.paic.gof.proxy; 2 3public class RealSubject implements Subject { 4 5 @Override 6 public void rent() { 7 System.out.println("I want to rent my house"); 8 } 9 10 @Override 11 public String hello(String str) { 12 System.out.println("hello: " + str); 13 return "hello " + str; 14 } 15}

InvocationHandler实现类(对比上面的匿名实现类):

1package com.paic.gof.proxy; 2 3import java.lang.reflect.InvocationHandler; 4import java.lang.reflect.Method; 5 6public class DynamicProxy implements InvocationHandler { 7 // 这个就是我们要代理的真实对象 8 private Object subject; 9 10 // 构造方法,给我们要代理的真实对象赋初值 11 public DynamicProxy(Object subject) { 12 this.subject = subject; 13 } 14 15 @Override 16 public Object invoke(Object object, Method method, Object[] args) throws Throwable { 17 // 在代理真实对象前我们可以添加一些自己的操作 18 System.out.println("before rent house"); 19 20 System.out.println("subject:" + subject.getClass().getName() + " / Method:" + method); 21 //可以主动调用subject 22// RealSubject rs = (RealSubject) subject; 23// rs.hello("hhhhhhh"); 24 25 // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 26 method.invoke(subject, args); 27 28 // 在代理真实对象后我们也可以添加一些自己的操作 29 System.out.println("after rent house"); 30 31 return null; 32 } 33 34}

测试类:

1package com.paic.gof.proxy; 2 3import java.lang.reflect.InvocationHandler; 4import java.lang.reflect.Proxy; 5 6public class ProxyTest { 7 public static void main(String[] args) { 8 // 我们要代理的真实对象 9 Subject realSubject = new RealSubject(); 10 11 // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 12 InvocationHandler handler = new DynamicProxy(realSubject); 13 14 System.out.println("-----------111111111---------"); 15 Subject subject = (Subject) Proxy.newProxyInstance( handler.getClass().getClassLoader(), 16 realSubject.getClass().getInterfaces(), 17 handler); 18 System.out.println("-----------333---------"); 19 System.out.println(subject.hello("world")); 20 System.out.println("-----------444---------"); 21 22 System.out.println(subject.getClass().getName()); 23 System.out.println(subject.getClass().getSuperclass()); 24 Class<?>[] interfacess = subject.getClass().getInterfaces(); 25 for(Class ccc : interfacess) { 26 System.out.println(ccc.getName()); 27 } 28 29 } 30}

debug查看代理类:

查看这里的h果然就是InvocationHandler实现类,这里尚不清楚为什么匿名类测试时显示的是测试类本身,看来要再温习下匿名类了--!

使用匿名类代码量少,简洁,但是不够直观,可读性不如独立实现类简单。。。

好了,啰嗦这么多,大概了解了动态代理的一些概况,后续有时间的话就了解下代理类的生成过程吧

先到这吧,上述阐述不一定正确,有问题请及时留言,一起分析进步,谢谢~!

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

CGLIB介绍与原理(通过继承的动态代理)

一、什么是CGLIB?CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。二、CGLIB原理CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的

Spring的两种代理JDK和CGLIB的区别浅谈

一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid

JDK动态代理学习 - HelloWorld