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.println(hello.sayHelloTo(theName)); } . . |
在上面的代码中,invoke()方法将被调用两次,每次调用sayHelloTo()方法时执行一次。现在我们来看看invoke()方法。简而言之,invoke()方法的工作正是第二篇文章中每一个客户程序必须手工完成的工作,其中包括:用合适的调用参数设置一个Call对象,定制的调用参数所需要的类型映射。由于SOAP代理中的invoke()方法担负了所有这些任务,客户程序释放了这份负担。
在invoke()方法接收到的三个参数中,我们只对后面两个感兴趣。第二个参数,即Method对象,给出了被调用方法的名字。记住,被调用方法的名字对应着一个SOAP服务导出的已知方法。服务的对象ID作为参数传递给newInstance()方法,所以invoke()方法已经拥有该对象ID。invoke()方法利用这些信息,按照如下方式设置Call对象:
Call call = new Call(); call.setTargetObjectURI(urn); call.setMethodName(m.getName()); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); |
现在要做的是为远程服务调用设置参数。为此,我们要用到invoke()方法的第三个参数:传入动态代理上被调用方法的一个参数数组。数组中索引为0的参数是方法调用中最左边的参数,索引为1的参数是方法的第二个参数,依此类推。举例来说,如果客户程序调用了sayHelloTo(String name)方法,那么参数数组就是包含一个字符串的数组。invoke()方法处理该数组的每一个元素,创建一个由Parameter对象构成的向量(Vector)(正如第二篇文章中客户程序所做的那样):
java.util.Vector params = new java.util.Vector(); for( int i=0; i<args.length; i++ ) { if( isSimple(args[i]) || isSimpleArray(args[i]) ) { params.add(new Parameter(_paramName+(i+1), args[i].getClass(),args[i],null)); } else if( isVector(args[i]) ) { addMapping((java.util.Vector)args[i]); params.add(new Parameter(_paramName+(i+1),args[i].getClass(),args[i],null)); } // 如果这个数组的元素不属于Java基本数据类型 // 则假定这是一个JavaBean的数组 else if( isArray(args[i]) ) { if( smr == null ) smr = new SOAPMappingRegistry(); if( beanSer == null ) beanSer = new BeanSerializer();
ArraySerializer arraySer = new ArraySerializer(); smr.mapTypes(Constants.NS_URI_SOAP_ENC, null, null, beanSer, beanSer); smr.mapTypes(Constants.NS_URI_SOAP_ENC, null,args[i].getClass(), arraySer, arraySer); params.add(new Parameter(_paramName+(i+1), args[i].getClass(),args[i],null)); } // 假定这是一个Bean else { if( smr == null ) smr = new SOAPMappingRegistry(); if( beanSer == null ) beanSer = new BeanSerializer(); String qnamePart = args[i].getClass().getName(); smr.mapTypes(Constants.NS_URI_SOAP_ENC, new QName(urn, qnamePart),args[i].getClass(), beanSer, beanSer); params.add(new Parameter(_paramName+(i+1),args[i].getClass(),args[i],null)); } }
|
invoke()方法用到了许多私有的辅助方法,比如用isSimple()来确定参数的类型。如果参数是一个JavaBean或者一个数组,那么,程序必须设置一个定制的SOAP映射注册项,并通过setSOAPMappingRegistry()方法对Call对象作相应的设置(参见第二篇文章)。SOAP代理假定,当出现JavaBean时,SOAP服务用到的所有JavaBean按照如下方式映射:NameSpace URI设置成对象ID,Local Part设置成JavaBean完整的类名。我们部署HelloWorld服务时正是按照这个要求进行,所以一切都不存在问题。
invoke()方法的剩余部分相当简单:设置Call对象参数,设置定制SOAP映射注册项(如果有必要的话),发出调用,接收方法调用的返回值。如下所示:
if( params.size() != 0 ) call.setParams(params); if( smr != null ) call.setSOAPMappingRegistry(smr); // 发出调用 Response resp = call.invoke(serverURL, ""); if( !resp.generatedFault() ) { Parameter ret = resp.getReturnValue(); return(ret.getValue()); } else { Fault fault = resp.getFault(); throw new SOAPException(fault.getFaultCode(),fault.getFaultString()); }
|
二、HelloWorld服务 下面是HelloWorld服务的完整代码。有似曾相识的感觉吗?
package hello; public class HelloServer { public String sayHelloTo(String name) { System.out.println("sayHelloTo(String name)"); return "Hello " + name + ", How are you doing?"; } public String sayHelloTo(Name theName) { System.out.println("sayHelloTo(Name theName)"); return "Hello " + theName.getName() + ", How are you doing?"; } }
|
回忆一下,Name是一个简单的JavaBean,代码如下:
package hello;
public class Name { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
|
事实上,这里服务的代码与第二篇文章中的服务程序代码完全一样。对于服务开发者来说,唯一增加的工作是创建Java接口。部署服务的方法也和第二篇文章中讨论的完全一样,所以这里我不再介绍。相同的地方还不止如此,编译和运行客户程序的方法也和第二篇文章介绍的一样。为什么有这么多相同之处呢?因为我们创建的代理是一个非插入式的框架,它不会修改和干涉任何Apache SOAP部件的内部工作——无论是客户端还是服务端。
三、其他说明 本文讨论的SOAP代理(可以从文章后面下载)支持以下参数类型:
⑴ 下面的Java基本数据类型及其对应的对象形式。
boolean, Boolean, double, Double, float, Float, long, Long, int, Integer, short, Short, byte, Byte
|
注:服务器端总是接收基本数据类型。
⑵ 任何JavaBean
注:
该JavaBean不能包含其他JavaBean。
如果数组或向量包含除字符串或1列出数据类型之外的类型,则JavaBean不能包含这类数组或向量。
⑶ 下面的类:String, Vector
注:
Vector可以包含1、2列出的所有类型和字符串。
服务器端把Vector作为一个对象的数组接收。
⑷ 数组。数组元素可以是在1、2中列出的所有类型和字符串(上面已注明的除外)。
■ 结束语
在这个四篇文章构成的系列中,我不仅介绍了SOAP的基础知识,而且介绍了SOAP 1.1标准的一个优秀的实现:Apache SOAP。在本文中,我提供了一个以动态代理类为基础的框架,这个框架极大地简化了使用Apache SOAP的客户程序开发者的工作。
我深切地感到SOAP有着美好的前景,至少有两个理由使我这么认为:首先,SOAP以一些开放的标准为基础,比如XML。这使得无论是Microsoft,还是反Microsoft的企业,都广泛地接受了SOAP。对于开发者来说,这无疑是一个天大的好消息。第二,SOAP正在成为其他许多标准的基础,比如UDDI(Universal Description,Discovery,and Integration)。许多人认为,Web服务代表着下一代的Web应用开发,而SOAP和UDDI都是Web服务的关键组成部分。
■ 参考资源
下载本文的完整代码:JavaAndSOAP4_code.zip
W3C的SOAP 1.1规范:
http://www.w3.org/TR/SOAP/
有关动态代理类的更多信息:
http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html
关于IBM SOAP工程的更多信息:
http://www.alphaworks.ibm.com/tech/soap4j
下载Apache SOAP:
http://xml.apache.org/dist/soap/
相关推荐
### SOAP:XML跨平台Web Service开发技术 #### 1. 简介 SOAP(Simple Object Access Protocol),即简单对象访问协议,是一种用于交换结构化信息的标准协议。它是Web服务中用于远程过程调用的主要协议之一,特别是...
XML标签`<soap:Envelope>`用于声明SOAP Envelope的开始,而`</soap:Envelope>`标记其结束。 2. **SOAP Header**:可选部分,用于提供元数据,如安全性、路由信息或交易上下文。Header中的元素是可扩展的,可以包含...
**SOAP协议规范详解** SOAP(Simple Object Access Protocol),即简单对象访问协议,是一种基于XML(Extensible Markup Language)的协议,用于在Web服务中传递结构化的和格式化的信息。SOAP允许应用程序通过HTTP...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,它的主要目的是让应用程序通过HTTP协议来交换信息。SOAP在不同操作系统和编程语言的应用程序间提供了一种标准化的通信方式,解决了传统远程过程调用...
#### 四、SOAP错误处理 在使用SOAP进行通信时,可能出现各种类型的错误。为了处理这些问题,SOAP定义了一套错误处理机制。当服务端遇到问题时,可以通过特定的SOAP错误消息来通知客户端。 ##### 示例:错误响应 ...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,主要用于在分散或分布式的网络环境中交换结构化和类型化的信息。SOAP协议的设计目标是简单性和可扩展性,因此它并不包含传统消息系统和分布式对象系统的...
SOAP(Simple Object Access Protocol)是一种基于XML的网络通信协议,用于在Web服务中交换结构化和类型化的信息。它允许应用程序通过HTTP等简单协议来发送和接收数据,从而实现跨平台、跨语言的通信。在“SOAP数据...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,主要用于在分散或分布的环境中交换结构化和类型化的信息。它的设计目标是简单性和可扩展性,因此它不包含一些传统消息系统和分布对象系统中的特性,如...
#### 四、SOAP的符号约定与名域 在SOAP规范中,“MUST”、“SHALL NOT”、“SHOULD”等关键词遵循RFC-2119的规定,用于指示协议的强制性要求和建议。同时,SOAP使用了一系列的名域前缀来关联特定的URI,例如“SOAP-...
SOAP(Simple Object Access Protocol)是一种基于XML(Extensible Markup Language)的协议,用于在Web服务中传递结构化和类型化的信息。它允许不同系统之间通过网络进行通信,尤其是在分布式计算环境中。SOAP协议...
<soap:Header/> <soap:Body> <Add xmlns="http://tempuri.org/"> <intA>1 <intB>2 </soap:Body> </soap:Envelope> ``` 在这个例子中,Body内的`Add`操作表示一个加法请求,`intA`和`intB`是参数。 **iOS中...
它通常包含`<soap:Envelope>`标签。 - **Header**:可选部分,用于传递与消息处理相关的附加信息,如认证、路由指令等。它由`<soap:Header>`标签表示。 - **Body**:必需部分,包含了SOAP消息的主要内容,即实际的...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在Web服务中交换结构化和类型化信息。它允许不同系统间进行远程过程调用,即使它们运行在不同的操作系统或使用不同的编程语言。SOAP消息通常通过...
### SOAP协议规范详解 #### 一、概述 SOAP(Simple Object Access Protocol)是一种基于XML的信息交换协议,旨在为分布式环境中不同应用之间的通信提供一种标准化的方法。该协议规范由多个部分组成,包括SOAP封装...
调用pb开发的webserver(SOAP 1.1) /* POST /webservice/n_webservice.asmx HTTP/1.1 Host: localhost Content-Type: application/soap+xml; charset=utf-8 Content-Length: length <soap12:Envelope xmlns:xsi=...
SOAP,全称Simple Object Access Protocol,是一种基于XML的通信协议,用于在分布式环境中交换结构化和类型化的信息。中文SOAP协议规范主要关注如何在不同系统间有效地传递数据,同时保持了简单性和可扩展性,使其...
SOAP(Simple Object Access Protocol)是一种基于XML的网络通信协议,用于在不同的应用程序之间交换结构化信息。它允许服务提供者和消费者通过HTTP等传输协议进行通信,实现了远程过程调用的功能。C语言版的SOAP...
SOAP(Simple Object Access Protocol)是一种基于XML的协议,它的全称是简单对象访问协议。SOAP设计的主要目的是在分散或分布式环境中提供一种轻量级、结构化的信息交换机制。SOAP协议不涉及具体的编程模型或特定...
SOAP(Simple Object Access Protocol)协议是一种基于XML的通信协议,用于在分布式或分散环境中交换结构化和类型化的信息。它的设计目标是简单性和可扩展性,因此它不包含某些传统消息系统和分布式对象系统的特性,...
SOAP(Simple Object Access Protocol)是一种基于 XML 的轻量级协议,用于在网络上进行数据交换。SOAP 1.1 和 SOAP 1.2 是两个不同的版本,它们之间存在一些关键的区别。 首先,从报头信息开始,SOAP 1.1 和 SOAP ...