JDK动态代理学习

本文详细解析了JDK动态代理的工作原理,通过示例代码展示了如何利用Proxy.newProxyInstance()方法创建代理对象,并深入探讨了InvocationHandler接口及其实现类在代理机制中的作用。

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

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

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

下面就开始吧

提供一个接口定义:

package com.paic.gof.proxy;

public interface Subject {

	public void rent();

	public String hello(String str);
}

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

package com.paic.gof.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

public class ProxyTest2 {
	public static void main(String[] args) {
		/*
		 * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
		 * handler.getClass().getClassLoader():这里使用handler这个类的ClassLoader对象来加载我们的代理对象
		 * realSubject.getClass().getInterfaces():这里为代理对象提供的接口是真实对象所实现的接口,
		 * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
		 * handler:这里将这个代理对象关联到了上方的InvocationHandler 这个对象上
		 */
		//下面一句可得到生成的代理类$Proxy0的.class文件,可以反编译查看其内容
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
		System.out.println("-----------111---------");
		Subject subject = (Subject) Proxy.newProxyInstance(	Subject.class.getClassLoader(),
				new Class<?>[] {Subject.class}, 
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						System.out.println("-----------333---------");
						System.out.println("method : " + method.getName() + "  / args = " + args);
						return "【invoke-->" + method.getName() + "】";
					}
				});
		System.out.println("-----------222---------");
		System.out.println(subject.hello("world"));
		
		System.out.println("-----------444---------");
		System.out.println(subject.toString());
		System.out.println("-----------555---------");
		
		System.out.println("class : " + subject.getClass().getName());
		System.out.println("super class : " + subject.getClass().getSuperclass());
		Class<?>[] interfacess = subject.getClass().getInterfaces();
		for(Class ccc : interfacess) {
			System.out.println("super interface : " + ccc.getName());
		}
	}
}

执行结果如下:

-----------111---------
-----------222---------
-----------333---------
method : hello  / args = [Ljava.lang.Object;@7d4991ad
【invoke-->hello】
-----------444---------
-----------333---------
method : toString  / args = null
【invoke-->toString】
-----------555---------
class : com.sun.proxy.$Proxy0
super class : class java.lang.reflect.Proxy
super interface : com.paic.gof.proxy.Subject

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

  • 执行顺序

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

  • 代理对象

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

a394750ca7bce3b384778c64467cbe36a4c.jpg

信息比较少

再看看它的父类: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文件路径如下:

751887f5aaa6348ba3f8c94b2b6a030ee07.jpg

经过反编译得到:

package com.sun.proxy;

import com.paic.gof.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
  extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m4;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void rent()
    throws 
  {
    try
    {
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String hello(String paramString)
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m3, new Object[] { paramString });
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m4 = Class.forName("com.paic.gof.proxy.Subject").getMethod("rent", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.paic.gof.proxy.Subject").getMethod("hello", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

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

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

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

  public final String hello(String paramString)
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m3, new Object[] { paramString });
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

分析下这个方法

在代码中执行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

2b2e38cbe7b05bb7d0edbc464dd792c83f5.jpg

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

  • 非匿名类测试

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

接口实现类:

package com.paic.gof.proxy;

public class RealSubject implements Subject {
	
	@Override
	public void rent() {
		System.out.println("I want to rent my house");
	}
	
	@Override
	public String hello(String str) {
		System.out.println("hello: " + str);
		return "hello " + str;
	}
}

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

package com.paic.gof.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
	// 这个就是我们要代理的真实对象
	private Object subject;

	// 构造方法,给我们要代理的真实对象赋初值
	public DynamicProxy(Object subject) {
		this.subject = subject;
	}

	@Override
	public Object invoke(Object object, Method method, Object[] args) throws Throwable {
		// 在代理真实对象前我们可以添加一些自己的操作
		System.out.println("before rent house");

		System.out.println("subject:" + subject.getClass().getName() + " / Method:" + method);
		//可以主动调用subject
//		RealSubject rs = (RealSubject) subject;
//		rs.hello("hhhhhhh");

		// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
		method.invoke(subject, args);

		// 在代理真实对象后我们也可以添加一些自己的操作
		System.out.println("after rent house");

		return null;
	}

}

测试类:

package com.paic.gof.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {
	public static void main(String[] args) {
		// 我们要代理的真实对象
		Subject realSubject = new RealSubject();

		// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
		InvocationHandler handler = new DynamicProxy(realSubject);

		System.out.println("-----------111111111---------");
		Subject subject = (Subject) Proxy.newProxyInstance( handler.getClass().getClassLoader(),
															realSubject.getClass().getInterfaces(), 
															handler);
		System.out.println("-----------333---------");
		System.out.println(subject.hello("world"));
		System.out.println("-----------444---------");
		
		System.out.println(subject.getClass().getName());
		System.out.println(subject.getClass().getSuperclass());
		Class<?>[] interfacess = subject.getClass().getInterfaces();
		for(Class ccc : interfacess) {
			System.out.println(ccc.getName());
		}
		
	}
}

debug查看代理类:

c542d3bc8070314f2faa4647544b4def3ae.jpg

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

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

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

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

 

 

 

 

 

转载于:https://my.oschina.net/u/1474131/blog/1839266

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值