首页 > 技术文章 > 细说java系统之动态代理

nuccch 2017-12-01 14:33 原文

代理模式

在深入学习动态代理之前,需要先掌握代理模式。只有深刻理解了代理模式的应用,才能充分理解Java动态代理带来的便利。
在生活中存在许多使用“代理模式”的场景,比如:村里的张三今年已经30岁了,但是还没结婚,可把他老妈给愁坏了,于是就拜托村东头的王媒婆给儿子找个媳妇。
在这里,要娶媳妇的人是张三,但是他不能直接跑到女方家把人家闺女直接带回来,需要中间人王媒婆上门说媒,在这里王媒婆就是一个代理。
另外,我们上大学的时候都知道,学校的机房都是通过一个代理服务器上网的,因为只有一个外网IP,不允许每一台局域网的机器都直连外网。
再者,我们通常为了保护应用程序不受外网攻击,通常将nginx部署在应用前端,作为反向代理服务器。
总之,我们总是会出于某些目的,或者因为某些限制而不得不使用代理模式。

OK,我们通过一个更加常见的例子来说明如何在通过抽象在Java世界使用代理模式。
我们都知道,iPhone已经成为当今非常流行的手机品牌,前些年甚至有人为了买iPhone手机而卖肾,可想其品牌早已深入人心。
最新发售的iPhoneX也是火爆得一塌糊涂,于是小明同学按耐不住激动的心情也想买一台来使用。
但是最先并不在大陆发售,但是小明又身在大陆不便于出国,那可怎么办呢?在电子商务早已风靡的今天,显然这个问题太容易解决了。
于是小明在某电商网站通过海外代购的方式就可以买到心仪的机器了。在这里,小明买手机这个事情就委托给了电商平台的某个商家,小明不用直接去购买,而是通过商家这个“代理”去买手机,最后再通过邮寄的方式把货物交给小明。
那么,如何用程序来实现小明使用海外代购这个方式购买到手机呢?
在此之前,我们需要明确一点,计算机程序的实现必然是对现实生活的一种抽象。所以,我们第一步先抽象出一个叫做“人”的类。

/**
 * 定义"人"类
 * @desc org.chench.test.java.Person
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class Person {
	/**
	 * 姓名
	 */
	private String name = "";
	
	/**
	 * 年龄
	 */
	private int age = 0;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

我们看到,在“人”这个类中定义基本的属性,如:姓名,年龄。
同时,我们还需要抽象出一个“购买者”接口,声明购买指定物品的方法,如下所示:

/**
 * 定义"购买者"接口,声明购买指定物品的方法.
 * @desc org.chench.test.java.Buyer
 * @date 2017年11月30日
 * @version 1.0.0
 */
public interface Buyer {
	/**
	 * 有人想要买iphonex
	 */
	public void buyIphoneX();
}

显然,做了这么多准备,目的就是要为小明买iPhoneX,所以“人”类还必须实现“购买者”接口,即最终的“人”类应该是这样子的:

/**
 * 定义"人"类
 * @desc org.chench.test.java.Person
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class Person implements Buyer {
	/**
	 * 姓名
	 */
	private String name = "";
	
	/**
	 * 年龄
	 */
	private int age = 0;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public void buyIphoneX() {
		System.out.println(getName() + " will buy iPhoneX");
	}
}

OK,到这里我们应该可以实例化一个叫“小明”的对象去购买iPhoneX了:

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
	public static void main(String[] args) {
		Person person = new Person();
		person.setName("小明");
		person.buyIphoneX();
	}
}

运行时输出:小明 will buy iPhoneX
但是问题来了,因为iPhoneX还未在大陆上市,同时小明又不能出国,他是没法直接买到iPhoneX了。
于是小明找了一家叫做“代购小王子”的商家,替他去海外买手机,然后再邮寄给小明。

/**
 * @desc org.chench.test.java.BuyerProxyPrinceling
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuyerProxyPrinceling implements Buyer {
	private String name = "";
	private Person realBuyer = null;
	
	public BuyerProxyPrinceling(String name, Person realBuyer) {
		this.name = name;
		this.realBuyer = realBuyer;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public void buyIphoneX() {
		System.out.println(getName() + " buy iPhoneX");
		realBuyer.receiveIphoneX("iPhoneX");
	}
}

首先,“代购小王子”要购买iPhoneX,同样需要实现“购买者”接口。
其次,为了在购买到手机之后把机器寄给小明,在“人”类中还需要定义一个接收iPhoneX的方法,否则,代购者购买到手机之后没法给小明啊。
在“人”类中添加如下方法:

public void receiveIphoneX(String iPhoneX) {
	System.out.println(getName() + " received " + iPhoneX);
}

准备完毕,海外代购找好了,小明需要找“代购小王子”买iphonex啦__

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
	public static void main(String[] args) {
		Person person = new Person();
		person.setName("小明");
		// 小明无法直接购买手机
		// person.buyIphoneX();
		BuyerProxyPrinceling buyerProxy = new BuyerProxyPrinceling("代购小王子", person);
		// 小明通过代理购买手机
		buyerProxy.buyIphoneX();
	}
}

运行输出:

代购小王子 buy iPhoneX
小明 received iPhoneX

小明终于通过海外代购的方式买到了心仪的手机了,这下该好好出去炫耀了吧~
在这里,我们在软件实现中把小明称为目标对象,代购商家称为代理对象,通过代理对象完成目标对象不能完成的动作。

从这个很简单的例子可以看到,为了通过代理实现某个操作,必须先定义出业务接口,然后目标对象类和代理对象类都必须实现该业务接口。
同时,代理对象必须要持有目标对象的引用,便于代理对象执行某个操作之后与目标对象进行联系。
虽然达到了目的,但是实现起来却很繁琐。具体到编程实现,需要进行太多的硬编码,这对于我们一直追求简单灵活的目标来说实在是太不友好了。
实际上,实现这种代理模式的方式叫做静态代理。
Java自1.3版本就提出了对动态代理的支持,这个动态代理又能带来什么便利呢?相比起静态代理,动态代理有什么优势呢?使用动态代理又能实现什么高级的操作呢?

动态代理

动态代理类概述

关于动态代理类的官方定义是:

A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation 
through one of the interfaces on an instance of the class will be encoded and dispatched to another object through 
a uniform interface.

翻译过来的意思是:

动态代理类是一个实现在运行时指定接口列表的类,使得这些接口实现类的实例中的某个方法调用将被编码并通过统一的接口分派到另一个对象。

这个定义非常的生硬,但是从中可以得出这么几点信息:
(1)动态代理类需要实现一些指定接口,且是在运行时指定的,也就是说,动态代理类不需要在编码时明确实现这些接口。
(2)实现这些接口的类的实例的方法调用将会被编码,并且会通过一个统一的接口分派到另一个对象(其实就是被分派到代理对象)。

那么我们就需要进一步确定:
(1)这个动态代理类需要在运行时指定哪些接口?
(2)这些接口的实现类实例的方法调用将会被谁编码?会被编码为什么?
(3)分派这些接口的实现类实例的方法调用的“统一的接口”是什么接口?分派到的对象是什么?
(4)使用这个动态代理类到底能做什么?

带着这些问题,继续看官方对于动态代理类功能的描述:

  • 可以使用动态代理类为接口列表创建类型安全的代理对象,而不需要预先生成代理类,例如使用编译时工具。
  • 动态代理类的实例的方法调用被调度到该实例的调用处理程序中的单个方法,并且会被编码为一个标识方法被调用的java.lang.reflect.Method对象和一个包含方法参数的Object类型的数组。
  • 动态代理类对于需要呈现接口API的对象提供类型安全的调用分派的应用程序或库很有用。例如,应用程序可以使用动态代理类来创建一个对象,该对象实现多个任意事件侦听器接口(扩展java.util.EventListener--),以统一的方式处理各种不同类型的事件(例如通过记录所有这样的事件到一个文件)。

既然动态代理类这么牛叉,那么如何创建和使用它呢?

动态代理类实践

还是之前小明海外代购iPhoneX的例子,编写动态代理类。

/**
 * "海外代购"动态代理类
 * @desc org.chench.test.java.BuyerDynimicProxy
 * @date 2017年12月1日
 */
public class BuyerDynimicProxy implements InvocationHandler {
	private String name = "海外代购商家";
	private Object realBuyer = null;
	
	private BuyerDynimicProxy(Object obj) {
		this.realBuyer = obj;
	}
	
	public static Object newInstance(Object obj) {
		// 返回对象的动态代理对象,被代理对象的方法被调用时会被分派到动态代理对象的invoke方法
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
				obj.getClass().getInterfaces(), 
				new BuyerDynimicProxy(obj));
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 当小明要买手机时,会触发该动态代理去购买,买到了再邮寄给小明
		Object result =	method.invoke(realBuyer, args);
		this.doBuyIphoneX();
		if(realBuyer instanceof Person) {
			Person person = (Person)realBuyer;
			// 把手机邮寄给小明
			person.receiveIphoneX("iPhoneX");
		}
		return result;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 代理对象去买iphonex
	 */
	private void doBuyIphoneX() {
		System.out.println(getName() + " buy iPhoneX");
	}
}

通过动态代理对象代购手机:

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
	public static void main(String[] args) {
		Person person = new Person();
		person.setName("小明");
		
		// 通过动态代理给小明代购手机
		Buyer buyerDynimicProxy = (Buyer) BuyerDynimicProxy.newInstance(person);
		buyerDynimicProxy.buyIphoneX();
	}
}

输出结果:

小明 will buy iPhoneX
海外代购商家 buy iPhoneX
小明 received iPhoneX

总结

以小明代购手机这个使用了代理模式的例子我们可以看到,使用动态代理比使用静态代理带来了更多的灵活性和解耦,主要体现在:

动态代理类不需要在编码中明确实现业务接口(BuyerDynimicProxy并未实现Buyer接口),而是在运行时动态指定。
这样一个动态代理类就可以很方便地实现同时代理多种业务(比如BuyerDynimicProxy除了可以代购手机,还可以代理购房),而静态代理需要明确实现多个代理接口。

实际上,所谓的动态代理就是Java提供了一种机制,允许开发者在编程中灵活方便的实现代理模式的应用,而不需要实现大量重复的耦合性较大的硬编码。
动态代理是Spring AOP框架的基石,正是因为Java动态代理的出现,使得在Java中面向切面编程成为可能。
虽然具备了灵活性,但是动态代理同样需要遵循一定的约定:动态代理类必须实现java.lang.reflect.InvocationHandler接口,用于目标对象在执行业务接口方法时通知到代理对象。 动态代理与目标对象的关系可以使用下图形象表示:

如图所示,当动态代理对象调用它所实现的业务接口方法时,会触发InvocationHandler接口的invoke()方法,可以在这里获得对目标对象的引用,从而执行一些拦截处理。
另外,动态代理仅仅是Java为应用编程提供的一种灵活使用代理模式的手段,但不是必须的,如果处于某些考虑使用静态代理同样可以达到目的。

特别注意: 生成的动态代理对象只能代理目标对象类实现的业务接口方法,不能代理目标对象类中自己定义的方法。

/**
 * @desc org.chench.test.java.BuySomething
 * @date 2017年11月30日
 * @version 1.0.0
 */
public class BuySomething {
	public static void main(String[] args) {
		Person person = new Person();
		person.setName("小明");
		
		// 通过动态代理给小明代购手机
		//Buyer buyerDynimicProxy = (Buyer) BuyerDynimicProxy.newInstance(person);
		//buyerDynimicProxy.buyIphoneX();
		
		// 动态代理只能代理目标对象类所实现的业务接口方法,如下代码在运行时报错
		Person p = (Person) BuyerDynimicProxy.newInstance(person);
		p.doSomething();
	}
}

另外,在Java中使用动态代理技术除了可以通过JDK自带的InvocationHandler接口实现之外,还可以基于cglib库实现动态代理,详见:https://github.com/cglib/cglib。
关于代理模式的示例,详见:https://gitee.com/cchanghui/test-design-patterns.git

【参考】
http://blog.csdn.net/carson_ho/article/details/54910472 代理模式(Proxy Pattern)- 最易懂的设计模式解析

推荐阅读