JDK中的动态代理机制

栏目: IT资讯 · 发布时间: 5年前

内容简介:小明是一个程序员,在公司负责项目的研发工作。有一天,客户打电话进来,沟通之后,原来客户是有个模块需求要变动一下。小明却没有应允,而是让客户去找产品经理老王沟通。是小明偷懒不想干活吗?显然不是。我们把这个事例对应到上面的定义上,程序员小明可以映射为其他对象,产品经理老王是小明的代理。它来控制小明这个对象的访问。我们看上面的类图,可以简单归纳以下角色。

小明是一个程序员,在公司负责项目的研发工作。有一天,客户打电话进来,沟通之后,原来客户是有个模块需求要变动一下。小明却没有应允,而是让客户去找产品经理老王沟通。

是小明偷懒不想干活吗?显然不是。我们把这个事例对应到上面的定义上,程序员小明可以映射为其他对象,产品经理老王是小明的代理。它来控制小明这个对象的访问。

JDK中的动态代理机制

我们看上面的类图,可以简单归纳以下角色。

  • Subject 某一类主题。 比如工程师
  • Proxy 代理对象。 比如产品经理老王
  • RealSubject 真实对象。 比如 程序员 小明

静态代理

我们通过代码来重现上面的场景。

首先定义一个 工程师的接口,它有一个编码的方法。

public interface Engineer {
	
	void coding();
}
复制代码

小明是个 Java 码农,啊呸。是个Java工程师,要实现工程师这个接口。

public class JavaEngineer implements Engineer{

	private String name;
	
	public JavaEngineer(String name){
		this.name = name;
	}
	
	public void coding() {
		
		System.out.println(name+" :正在努力的coding...");
		System.out.println(name+" :终于改完了!");
	}
}
复制代码

产品经理老王也让他实现工程师的接口。他代理了公司里的Java工程师对象,当客户有需求提出来,他要整理评估一下,当需要coding的时候,他直接转交给具体的工程师去处理。

public class ProductManager implements Engineer{

	private String name;
	private JavaEngineer engineer;
	
	public ProductManager(JavaEngineer engineer,String name){
		this.engineer = engineer;
		this.name = name;
	}
	
	public void coding() {
		arrange();
		engineer.coding();
		appease();
	}
	
	
	private void arrange(){
		System.out.println(this.name+":整理客户需求中...");
		System.out.println(this.name+":输出需求文档,交给 码农 去完成!");
	}

	private void appease(){
		System.out.println(this.name+":哎,需求变好多次了.得安抚一下这个码农才好!");
	}
}
复制代码

我们来重现一下这个场景。

//有一个美丽的Java工程师,他的名字叫小明。
JavaEngineer engineer = new JavaEngineer("XiaoMing");

//同样,还有一个猥琐的老王。
ProductManager manager = new ProductManager(engineer,"老王");

//有新需求的时候,老王负责去沟通搞定。
manager.coding();

System.out.println("------------------输出结果-----------------------");
//老王:整理客户需求中...
//老王:输出需求文档,交给码农去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :终于改完了!
//老王:哎,需求变好多次了.得安抚一下这个码农才好!
复制代码

动态代理

JDK通过反射机制给我们提供了动态代理的实现,允许开发人员在运行时刻动态的创建出代理类及其对象。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者。这种做法实际上相当于对方法调用进行了拦截。

关键有两个类,Proxy和InvocationHandler 。

  • Proxy 用于生成代理类
  • InvocationHandler 用于调用目标类的方法,并且允许在调用前后插入其他的逻辑

上面的事例我们改成动态代理方式来看一下。先定义一个调用处理程序

public class ProxyHandler implements InvocationHandler{

	private Object target;
	private String name;
	
	public ProxyHandler(Object target,String name){
		this.target = target;
		this.name = name;
	}
	
	
	public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
		arrange();
		method.invoke(target, args);
		appease();
		return null;
	}

	
	private void arrange(){
		System.out.println(this.name+":整理客户需求中...");
		System.out.println(this.name+":输出需求文档,交给工程师去完成!");
	}

	private void appease(){
		System.out.println(this.name+":哎,需求变好多次了.得安抚一下这个码农才好!");
	}
}
复制代码

然后我们来测试一下

//有一个美丽的Java工程师,他的名字叫小明。
Engineer engineer = new JavaEngineer("XiaoMing");

//代理实例的调用处理程序
ProxyHandler handler = new ProxyHandler(engineer, "老王");

//返回指定接口的代理类实例,该接口可以将方法指派到指定的handler
Engineer proxy = (Engineer) Proxy.newProxyInstance(Engineer.class.getClassLoader(), 
			new Class[]{Engineer.class}, handler);

proxy.coding();

System.out.println("------------------输出结果-----------------------");
//老王:整理客户需求中...
//老王:输出需求文档,交给码农去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :终于改完了!
//老王:哎,需求变好多次了.得安抚一下这个码农才好!
复制代码

内存中的代理类实例长啥样?

我们看到,Proxy类通过静态方法newProxyInstance就生成了一个代理类的实例。先不管它是怎么样生成的,但是我想关心它到底长什么样子呢?把它拿出来看看。 JDK生成的代理类以 $Porxy 开头,后面跟一个从0开始的自增长数字。比如, $Proxy0 ,通过下面这段代码,可以将代理类实例输出到$Proxy0.class文件中。

byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[] {Engineer.class});	
FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
fileOutputStream.write(data);
fileOutputStream.close();
复制代码

通过反编译class文件,得到代理类删减整理如下:

public class $Proxy0 extends Proxy implements Engineer {

	private static final long serialVersionUID = 1L;
	private static Method m3;

	protected $Proxy0(InvocationHandler h) {
		//通过构造方法 把代理实例的调用处理程序传进来
		super(h);
	}
	public final void coding() {
		try {
			//此处的h是父类Proxy的属性,对应的就是ProxyHandler
			//invoke就相当于ProxyHandler.invoke(this, m3, null);
			this.h.invoke(this, m3, null);
			return;
		} catch (RuntimeException localRuntimeException) {
			throw localRuntimeException;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	static {
		try {
			//通过反射拿到指定接口的方法
			m3 = Class.forName("proxy.proxy2.Engineer").getMethod("coding",
					new Class[0]);
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw new NoClassDefFoundError(
					localClassNotFoundException.getMessage());
		}
	}
}
复制代码

如果我们把通过反编译得到的class文件写成一个Java类,调用它同样可以实现代理功能。

Engineer engineer = new JavaEngineer("XiaoMing");
ProxyHandler handler = new ProxyHandler(engineer, "老王");
$Proxy0 p0 = new $Proxy0(handler);
p0.coding();
System.out.println("------------------输出结果-----------------------");
//老王:整理客户需求中...
//老王:输出需求文档,交给码农去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :终于改完了!
//老王:哎,需求变好多次了.得安抚一下这个码农才好!
复制代码

总结

关于动态代理创建对象的过程,我们大概可以这样总结一下。

  • 1、通过实现InvocationHandler接口创建自己的调用处理器
  • 2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
  • 3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
  • 4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入

Proxy类的newProxyInstance方法封装了2-4,只需2步就完成了代理对象的创建。 生成的代理对象集成Proxy,实现被代理对象接口。被代理对象接口的方法实际调用处理器的invoke方法,而处理器的invoke方法利用反射调用的是被代理对象的的方法method.invoke(target,args)。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning ASP.NET 4 in C# and Vb

Beginning ASP.NET 4 in C# and Vb

Imar Spaanjaars / Wrox / 2010-3-19 / GBP 29.99

This book is for anyone who wants to learn how to build rich and interactive web sites that run on the Microsoft platform. With the knowledge you gain from this book, you create a great foundation to ......一起来看看 《Beginning ASP.NET 4 in C# and Vb》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具