Java 2平台1.3版本为Java映像API(Reflection API)增加了一个极其实用的扩展:动态代理类。一个动态代理类就是一个实现了一系列运行时指定的接口的类。这个代理可以象它真正实现了这些接口一样使用。换句话说,可以直接在代理对象上调用任意接口的任意方法——当然,必须先进行必要的类型定型(casting)。由此,我们可以用动态代理类为一组接口创建一个类型安全的代理对象,且不必象使用编译时工具一样预先生成代理(有关动态代理类更详细的说明,请参见本文最后的参考资源)。
接下来我将介绍一个以动态代理类为基础的框架,这个框架使得SOAP(简单对象访问协议)客户程序的创建更加简单和直观。SOAP是一种用XML编码数据的有线协议。在本系列文章的第二篇、第三篇构造SOAP服务的过程中,我们发现客户程序的开发者必须多做许多原来不必做的工作。为帮助回忆,你可以看一下第二篇文章中的SOAP服务代码,看看和客户程序代码相比较时,服务程序的SOAP代码是多么微不足道。本系列文章前几篇所创建的简单SOAP服务显示出,基于SOAP的服务只包含无论用不用SOAP都必须提供的代码。服务程序的开发者要编写的额外代码很少,而客户程序开发者却有许多额外工作要做。本文介绍的类将把这些额外工作减到最少。
一、介绍SOAP代理类 首先,我要给出如果客户程序使用了本文创建的框架,它将变成什么样子:
package hello;
import soapproxy.*;
public class Client
{
public static void main(String[] args)
{
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 调用sayHelloTo方法
// 这个sayHelloTo方法需要一个字符串参数
System.out.println(hello.sayHelloTo("John"));
// 调用sayHelloTo方法
// 这个sayHelloTo方法需要一个Name JavaBean参数
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
也许是出于我的个人爱好,我认为上面的客户代码比第二篇和第三篇文章中的客户代码更好。如果你现在不能理解上面的代码,这很正常,但我想待到本文结束时你会理解的。
要理解客户程序的代码,你必须深入了解SOAP Proxy类,它在soapproxy包内,可以在Proxy.java内找到(参见本文最后的参考资源)。Proxy类有一个私有的构造函数,它意味着Proxy实例不能从Proxy之外创建;新建Proxy实例的唯一方法是通过静态的newInstance()方法。newInstance()方法有两个参数:SOAP服务的对象ID,以及一个数组,数组中包含一组该代理要实现的接口的名字。对象ID很简单,但这些接口名字是什么?从哪里去得到这些接口的名字?SOAP服务的开发者直接把服务上所有可被客户程序调用的方法堆在一起得到一个接口。相当简单,不是吗?
现在我们为HelloWorld服务定义一个接口。第二篇文章中,这个服务的最终版本有sayHelloTo()方法的两个重载版本:一个版本的参数是一个字符串,另一个版本的参数是一个Name JavaBean。这两个方法就可以构成一个接口,称为Hello,如下所示:
package hello;
public interface Hello
{
public String sayHelloTo(String name);
public String sayHelloTo(Name name);
}
服务开发者决定要创建多少接口,以及为这些接口取什么样的名字。例如,你可以为HelloWorld服务创建两个接口,每一个接口包含一个方法。一般地,你应该避免创建方法数量大于七个的接口。另外,注意只把那些看来有必要放在一起的方法用一个接口组织起来。例如,如果HelloWorld服务还有一个返回定制的Good-Bye消息给调用者的sayByeTo()方法,设计两个独立的接口也许比较明智:一个接口用于sayHelloTo()方法,一个接口用于sayByeTo()方法。
现在我们有了定义HelloWorld服务和客户程序之间契约的接口,下面返回来看newInstance()方法。如前所述,newInstance()方法创建Proxy类的一个新实例。newInstance()方法可以创建新实例是因为它属于Proxy类,能够访问私有的构造函数。newInstance()方法为新创建的实例调用initialize()方法。initialize()值得关注,因为动态代理就是在这里创建和返回。initialize()的代码如下所示:
private Object initialize(Class[] interfaces)
{
return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()
,interfaces,this));
}
注意newProxyInstance()方法的应用。创建动态代理类实例的唯一办法是调用该类(即java.lang.reflect.Proxy类)静态的newProxyInstance()方法。java.lang.reflect.Proxy类为创建动态代理类提供了静态方法,而且它还是所有由这些方法创建的动态代理类的超类。换句话说,它不仅是一个创建动态代理类的工厂,而且它本身也是一个动态代理类!因此,在我们的例子中,SOAP代理不是动态代理;相反,这个动态代理实际上是newProxyInstance静态方法返回的java.lang.reflect.Proxy类的一个实例。从本文后面可以看到,这个动态代理实际上通过SOAP代理实现的invoke()方法完成它的所有工作。那么,这个动态代理如何建立和SOAP代理的联系呢?因为有一个对SOAP代理的引用传递给了newProxyInstance()方法。也许现在这听起来有点费解,但只要你分析一下invoke()方法,这一切就很明白了。
java.lang.reflect.Proxy类构造函数的第一个参数是一个类装载器实例,第二个参数是需要动态实现的接口的数组(它就是客户程序传递给newInstance()的数组),第三个参数是一个实现了java.lang.reflect.InvocationHandler接口的类的实例。因为SOAP Proxy类实现了InvocationHandler接口,所以第三个参数是代理实例本身(即this)。InvocationHandler接口有一个方法invoke()。当动态代理的动态实现的接口被调用时,Java运行时环境调用invoke()方法。因此,举例来说,当客户程序调用动态代理的Hello接口的sayHelloTo()方法时,Java运行时环境将调用SOAP代理的invoke()方法。
你可能已经发现,SOAP代理的newInstance()方法不返回SOAP代理的实例;相反,它返回newInsance()刚刚创建的动态代理,而动态代理动态地实现客户程序传入的接口数组。客户程序可以将这个返回的动态代理定型为传入newInstance()的任意接口类型,在动态代理上调用接口所定义的各个方法,就象动态代理真地实现了那些接口一样。
.
.
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 调用参数为字符串的sayHelloTo方法
System.out.println(hello.sayHelloTo("John"));
// 调用参数为Name JavaBean的sayHelloTo方法
Name theName = new Name();
theName.setName("Mala");
System.out.pr