4. 动态代理:
Oozinoz公司的工程师们偶尔会面临性能方面的问题。他们喜欢构建不会对设计产生较大变动的代码。
Java的一个特性可支持此方面考虑:动态代理。借助于动态代理,可以使用代理对象来包装其他对象,使用代理对象截获对被包装对象的调用请求,然后代理继续把这些请求转发给被包装对象。在执行被截获的调用之前或者之后,我们可以编写相关的加强代码。对动态代理加以限制可以防止包装其他任意的对象。在正常条件下,动态代理使你能够完全控制被包装对象的操作。
动态代理需要使用对象的类所实现的接口。代理对象可以截获的调用就是这些接口定义的调用。如果某个类可以实现要截获的接口,就可以使用动态代理来包装那个类的实例。
为创建动态代理,必须具有要截获的接口列表。幸运的是,使用如下代码询问希望包装的对象,就可以获取这个列表:
Class[] classes = obj.getClass().getInterfaces();
借助于上述代码,我们可以获取希望截获的方法列表,这些方法属于对象的类实现的接口。为构建动态代理,还需要两个因素:类加载器,以及包含当代理捕获某调用时你希望执行的行为的类。对于接口列表,通过使用与希望包装的对象相关的对象,就可以获取合适的类加载器:
ClassLoader loader = obj.getClass().getClassLoader();
最后一个需要的元素就是代理对象本身。这个对象必须是实现java.lang.reflect包中InvocationHandler接口的类实例。这个接口声明实现如下操作:
public Object invoke(Object proxy,Method m,Object[] args) throws Throwable;
在动态代理中包装某对象时,对被包装对象的调用会转发给动态代理对象的invoke()操作,invoke()方法会继续把这个调用转发给被包装对象。可以使用如下代码行来转发调用。
result = m.invoke(obj,args);
这行代码使用反射把调用继续转发给被包装对象。动态代理的好处体现在:可以在转发调用之前或者之后添加任何你期望的行为。
假设当方法执行时间很长时,你希望记录一个报警日志。可以使用如下代码创建一个子ImpatientProxy类:
package app.proxy.dynamic;
import java.lang.reflect.*;
public class ImpatientProxy implements InvocationHandler
{
private Object obj;
private ImpatientProxy(Object obj)
{
this.obj = obj;
}
public Object invoke(Object proxy,Method m,Object[] args) throws Throwable
{
Object result;
long t1 = System.currentTimeMillis();
result = m.invoke(obj,args);
long t2 = System.currentTimeMillis();
if(t2-t1>10)
{
Sytem.out.println("> It takes " + (t2-t1)
+" millis to invoke " + m.getName()
+"() with");
for(int i=0;i<args.length;i++)
System.out.println("> arg["+ i + "]:" + args[i]);
}
return result;
}
}
这个类实现invoke()方法,以检查被包装对象执行某调用操作所花费的时间。如果执行时间太长,ImpatientProxy类会显示报警消息。
为保证 ImpatientProxy对象有效,有必要使用java.lang.reflect包中的Proxy类。Proxy类需要接口列表、一个类加载器,以及ImpatientProxy的一个实例。为简化动态代理的创建过程,我们也许会向ImpatientProxy类中加入如下方法:
public static Object newInstance(Object obj)
{
ClassLoader loader = obj.getClass().getClassLoader();
Class[] classes = obj.getClass().getInterfaces();
return Proxy.newProxyInstance(loader,classes,new ImpatientProxy(obj));
}
这个静态方法会创建我们所需的动态代理。对于需要包装的特定类,newInstance()方法会展开这个对象的接口列表和类加载器。该方法会初始化ImpatientProxy类, 传递要包装的对象。接下来,所有这些要素会被传递给Proxy类的newProxyInstance()方法。
返回的对象将实现被包装对象实现的所有接口。我们可以把返回的对象投射到所有接口。
假设你管理一级对象,其中有些对象执行某些操作时速度太慢。为查找哪个对象行为异常,可以把这组对象包装在ImpatientProxy对象中。下面的代码是个范例:
package app.proxy.dynamic;
import java.util.HashSet;
import java.util.Set;
import com.oozinoz.firework.Firecracker;
import com.oozinoz.firework.Sparkler;
import com.oozinoz.utility.Dollars;
public class ShowDynamicProxy
{
public static void main(String[] args)
{
Set s = new HashSet();
s = (Set)ImpatientProxy.newInstance(s);
s.add(new Sparkler("Mr.Twinkle",new Dollars(0.05)));
s.add(new BadApple("Lemon");
s.add(new Firecracker("Mr. Boomy",new Dollars(0.25)));
System.out.println("The set contains "+s.size()+"things.");
}
}
上述代码会创建一个Set对象来保持部分项。然后代码会包装这个集合,方法是:使用ImpatientProxy对象,把newInstance()方法的结果投射回Set。结果是s对象的行为与Set对象的行为一样,唯一的例外是,如果某方法执行时间太长,ImpatientProxy对象会显示报警信息。比如,当程序调用对象集合的add()方法时,ImpatientProxy对象会截获这个调用。ImpatientProxy对象继续把这个调用转发给实际的对象集合,并记载每个调用执行花费的时间。
运行ShowDynamicProxy程序会产生如下输出:
>It takes 1204 millis to invoke add() with
> arg[0]: Lemon
The set contains 3 things.
ImpatientProxy对象代码可以帮助我们确定被包装对象集合中哪个对象执行调用花费很长时间。结果显示,BadApple类的"Lemon"实例花费时间最长。BadApple类代码如下所示:
package app.proxy.dynamic;
public class BadApple
{
public String name;
public BadApple(String name)
{
this.name = name;
}
public boolean equals(Object o)
{
if(!(o instanceof BadApple)
return false;
BadApple f = (BadApple)o;
return name.equals(f.name);
}
public int hashCode()
{
try
{
Thread.sleep(1200);
}
catch(InterruptedException ignored)
{
}
return name.hashCode();
}
public String toString()
{
return name;
}
}
ShowDynamicProxy程序代码使用ImpatientProxy对象来监控对对象集合的调用。对象集合和ImpatientProxy对象之间没有连接。一旦编写好动态代理类,就可以包装任何对象,只要该对象所属的类可以实现你希望截获的行为。
能够在被截获方法调用执行前和执行后创建其他有意义的操作是面向方面编程(AOP)的理念之一。在AOP中,方面(aspect)就是建议(advice)和切入点 (point cut,对插入代码执行点位置的定义)的组合。借助动态代理的范例,你可以体验一个如何给多个对象应用可复用操作。
在Java中,动态代理技术使你能够使用代理对象包装其他对象、截获对被包装对象的调用、在调用传递前和传递后增加其他操作等,这样可以比较随意地给任何对象增加可复用的行为,从这点来讲,与AOP非常类似。
5.小结:
Proxy模式的实现要求建立一个占位对象,用于控制对目标对象的访问。这样客户端就无须了解目标对象的状态变化。就像加载一个图像需要耗费一定时间时,我们可以使用Proxy模式改善用户体验。但Proxy模式本身存在代理对象与被代理对象之间耦合程序过紧的问题。在Java中,动态代理有时可以提供一种增加可复用功能的机制。如果某对象的类可实现要截获的接口,可以使用动态代理包装该对象,增加自己的处理逻辑,以增强或者替换被包装对象代码的功能。