例子与实验过程许方
2001 年 7 月
在本系列的上一篇文章中,我们架设了了于"三八线"的环境--Microsoft环境的客户端和Linux与开放技术环境的服务器端,下面我们来看在这个架构下一个具体的例子。
应用例子的服务器端
这里假设了这样一个WEB Service,接受一个整数作为学号输入,返回这个学生的成绩记录(对象)。学生的成绩用一个叫Score的Java类来描述。而实际提供WebService功能的是另一个叫QueryScore的类,这两个类很简单,完整的代码在这里下载。
先来介绍QueryScore.java,它是为我们Web Service的提供者(Provider)类,它很简单,看上去也没有什么特别,也并不因为要提供WebService而多声明什么(这就是Java的魅力之一),如清单1所示:
1 package bearsoaptest;2 public class QueryScore {3 public Score getScore(int StudentNo) {4 Score s = new Score();5 s.name = "Tiger";6 s.physics = 5;7 s.mathematics = 4;8 System.out.println("got score" + s ); //For Debug at Server Console9 return s;10 }11 }
|
清单1QueryScore类只有第3行中定义的一个方法。由于这里只想对计算架构作说明,因此并没有实际到数据库中去取学生的成绩,无论学号是什么,都返回同样值的Score对象,相信在这里加上从数据库中取记录的代码对大家不是什么问题。
这个Web Service将返回自定义的Score类型的对象,因此将用到Java对象的序列化(Serialization),序列化器的功能是由ApacheSOAP 2.1包中的org.apache.soap.encoding.soapenc.BeanSerializer来提供的,序列化器的作用就是把一个Java对象表示成XML。实际上,这个序列化器还提供反向功能(Deserialization),就是把一个XML表示还原成一个Java对象。由于这个WebService接收的参数是一个基本类型,因此不需要Deserializer,请注意区别XML Parser和序列化器是两回事。Parser仅仅是把XML建立成一个可以检索的内存结构模型――例如DOM。
对于Score.java,由于它需要被序列化后以便在网络上传输,基于Apache SOAP 2.1使用的序列化器的要求,Score.java要严格地写成一个JavaBean,否则无法正确地序列化。写成Bean无非是实现各个属性的setXXX和getXXX,在下面的清单2中列出开始的一部分代码,完整的代码请在参考资料7处下载。
1 package bearsoaptest;2 public class Score {34 String name;56 int physics = 0;7 int mathematics = 0;89 public String toString(10 {11 return "Score of " + name;12 }13 public void setName(String name) //出于Bean的需要14 {15 this.name = name;16 return;17 }18 public String getName() //出于Bean的需要19 {20 return this.name;21 }22 ......
|
清单2其实象清单2中的13行之后的代码,很多集成环境已经是可以自动生成的了,只要你告诉它你在写一个Java Bean。在Score.java中,我们实际上只是把要传送的数据放入到第4-7行的三个属性中。
这两个类在第1行都使用了package,为的是在部署的时候在同一个类目录下名字不冲突。
服务器端的代码就这么多,现在在服务器端只剩下最后一件事,部署(Deploy)这个Web Service。所谓部署,在Apache SOAP中,就是填一张表,告诉SOAP的rpcrouter如何激活这个服务的class。用我们的客户端机器打开浏览器,在地址栏输入:
http://192.168.0.10:8080/soap
进入Admin和Deploy填写下列内容,在这里没有提到的就空着或者用默认值。
| ID: | urn:bearsoaptest:score |
| Scope: | Request |
| Methods: | getScore |
| Provider Type: | Java |
| Provider Class: | bearsoaptest.QueryScore |
| Static: | No |
| Number of Mappings: | 1 |
| Encoding Style: | SOAP |
| Namespace URI: | urn:bearsoap |
| Local Part: | ScoreRecord |
| Java Type: | bearsoaptest.Score |
| Java To XMLSerializer: | org.apache.soap.encoding.soapenc.BeanSerializer |
| XML To JavaDeserializer: | org.apache.soap.encoding.soapenc.BeanSerializer |
ID是在同一个服务器环境下用来区分每个Web Service的,在技术上可以是任意的字符串,不过W3C还是规定了它的命名规范。
Scope是指这个Web Service的生存期,Request表示就是一次调用,读者一定猜到还有Session和Application可选,一点儿都没错,ApacheSOAP的状态管理与Apache Tomcat是一致的,与在JSP中调用Bean时的选项含义相同。
Provider Type是指Web Service由什么方法提供,我们这里显然是Java。
Provider Class指向提供Web Service的类。
Static指Provider是否是静态,如果是静态,多个QueryScore类就可以共享实例了。这个参数可能与运行效率和内存耗用有关,但我没有仔细测试过它。
Number of Mappings是指后面的表中有几行有效,在我们这个例子中只有一行,就是接下来几项,Encoding Style、NanespaceURI、Local Part三项是用来定义自定义的类bearsoaptest.Score序列化时在XML中的类型映射,在本文后面的XML清单中,你会看到刚才填写的这些值。最后两项指定序列化器和反序列化器,它们是纯Java的。
关于如何使用这个Deploy工具更详细的信息,请参阅参考资料1的Part 2,GrahamGlass给出了每一步完整的屏幕截图。
ScoreRecord这个Localpart的名字我是故意让它和原始的Score类不同名,以清楚地展现SOAP和语言及平台的无关性,说得更简单一点,一个JavaClass(Score)被序列化成一个SOAP XML节点后(ScoreRecord)就和Java无关了,收到这个SOAP节点的一端只需想办法理解ScoreRecord就可以了,你可以把ScoreRecord这个节点Deserializes成任何一种语言的对象,
当成功地Deploy了这个服务之后,还有一个小问题别忘了,编译这两个java程序,我们在/home/tuppin目录中编译它们,因为/home/tuppin已经被加入到了TOMCAT的CLASSPATH中,ApacheSOAP的rpcrouter可以找到它们。
javac -d . Score.java
javac -d . QueryScore.java
应用例子的客户端
因为已经有了Graham Glass在参考资料1中的例子,我就不想再编一个传递简单类型的例子了来浪费大家的时间了,既然是SOAP,我们就应该试一下Object而且是不同平台、不同语言的Object才够刺激:)。
在客户端,我建立一个名字叫vbclient.vbp的程序,完整的代码请在参考资料7一节中下载,下面的清单3是最主要的一段代码的逐行注释:
Private Sub Command1_Click()'这是文章《SOAP--新一轮竞争的三八线》中的例子程序'作者:TuppinDim Serializer As New SoapSerializer'请注意这里的Serializer和Java对象的Serializer不是一回事Dim Reader As SoapReaderDim Connector As New HttpConnector '使用http协议Command1.Enabled = False '防止手哆嗦产生代码重入'以下指定URL到服务器192.168.0.10 Port=8080Connector.Property("EndPointURL") = "http://192.168.0.10:8080/soap/servlet/rpcrouter"Connector.Property("SoapAction") = "" 'Apache SOAP 2.1不需要指定Soap ActionConnector.Connect '连接Connector.BeginMessage '准备发送SOAP请求Serializer.Init Connector.InputStream '通过MS SOAP Toolkit内置的序列化器开始发送请求Serializer.startEnvelope '生成标准的Envelope头'为Apache SOAP 2.1指定两个Namespace,事实上这是XMLSchema所规定的,' MS SOAP在这一版不自动支持,我们只好填上下面两行Serializer.SoapNamespace "xsi", "http://www.w3.org/1999/XMLSchema-instance"Serializer.SoapNamespace "xsd", "http://www.w3.org/1999/XMLSchema"' 开始正文Serializer.startBody "http://schemas.xmlsoap.org/soap/encoding/"'下面这一行应该面熟吧,我们在服务器端Deploy QueryScore那个Java Class时填写的'Methods、ID等等,后面一个是NamespaceSerializer.startElement "getScore", "urn:bearsoaptest:score", , "ns1"'这个服务仅仅要求一个参数:学号,类型是xsd:int Java的int刚好和xsd:int相对应'我们在这里把学号定为常数100Serializer.writeXML "<StudentNo xsi:type=""xsd:int"">100</StudentNo>"Serializer.endElementSerializer.endBodySerializer.endEnvelope '生成Envelope尾,到此发送完成Connector.EndMessage '结束发送,并取会响应结果Set Reader = New SoapReader '准备处理回应Dim s As New VBScore '创建VBScore对象,准备装传回的对象Dim f As New SoapTypeMapperFactoryDim m As ISoapTypeMapperReader.Load Connector.OutputStreamIf Not Reader.Fault Is Nothing Then '如果有错就显示错误信息strRs = Reader.Fault.TextMsgBox Trim(Replace(strRs, Chr(10), vbCrLf)) '把Unix换行改为MS换行ElseSet m = f.getMapper(enXSDstring, Nothing)'read的最后一个参数是MS保留的,现在没用s.name = m.read(Reader.RPCResult.selectSingleNode("name"),_"", enRPCEncoded, 0) '取name的值Set m = f.getMapper(enXSDint, Nothing)s.physics = m.read(Reader.RPCResult.selectSingleNode("physics"),_"", enRPCEncoded, 0) '取physics的值s.mathematics = m.read(Reader.RPCResult.selectSingleNode("mathematics"),_"", enRPCEncoded, 0) '取mathematics的值'简单地现实VBScore对象各个属性的值,现在是强类型的了MsgBox s.name + " " & Str(s.physics) + " " & Str(s.mathematics)End IfCommand1.Enabled = True '恢复按钮允许再次执行End Sub
|
清单3运行这个程序,按Command1按钮,至此,我们看到了我们所期待的结果,服务器端的Java Score 对象中的内容被传递到客户断VB Score对象中。但是有些需要注意的问题是,目前的VB与C++中的所有数据类型与XML Schema中的数据类型不完全兼容,在开发过程中需要对这些类型作一些些特殊处理,例如,目前在VB6中int仍然是16bit的,而在XMLSchema和Java中都是32bit的。不过,在Microsoft将来的C#和VB.net中,基本的或者称原生(Primitive)的类型将与XMLSchema和Java的类型完全对应,这是一个好消息,这样我们可以更平滑地在这两个世界中交换数据。
目前的Microsoft SOAP Toolkit 2无法自动地Serializes和Deserializes一个用户自定义的类,无论是C++的还是VB的、也无论是它自己产生的还是第三方系统的,每一个需要用SOAP来传递的VB或C++对象必须手工地去实现一个被称为ISoapTypeMapper的接口。而对于其它系统产生的对象,除非利用WSDL(WebService Description Language),我们可以使用ISoapTypeMapper,目前只能象我们程序中那样处理(自己去Deserializes)。我希望在下一篇文章中讨论WSDL在这种架构下的应用。关于这一点确实远远没有Java方便(见参考资料1中的Part3),在Java中只需要把通过SOAP RPC传递的类写成一个标准的Bean就可以了,这个限制的很大原因归于C++和VB以及COM的语言特性和体系结构,在将来的.net中,这些限制都将成为历史,这也是.net被Microsoft力推的一个重要技术原因。
过程回顾
其实SOAP RPC的工作原理并不是很复杂,在我们的这个实验中,当你完成一次SOAP RPC时,整个系统内部经过了这样一系列主要动作:
- 客户端用DOM接口把RPC请求按照SOAP规范打包成XML,底层是用Microsoft 的 XML Parser中的DOM接口来实现。
- 建立到服务器端的HTTP连接,使用HTTP的post方法发送这个SOAP请求(1中生成的XML表示)到 http://192.168.0.10:8080/soap/servlet/rpcrouter
- 一个通用的Java Servlet--被称为SOAP RPC侦听器的rpcrouter收到这个请求后用XML4J(xcerces.jar)中的DOM接口Parse这个请求,然后使用urn:bearsopetest:score这个ID在自己的deploy的list中查找,检查这个服务是否存在,方法、参数等是什么?
- 如果找到,并且参数的个数、类型都正确,就调用这个服务(在我们的例子中就是那个QueryScore.class),调用后取得结果--Score的实例。
- 先将Score的实例Serialize成XML,再次用XML4J(xcerces.jar)中的DOM接口打包结果成SOAP回应(也是XML),然后通过http的response返回给客户端。
- 客户端收到这个回应后,用MS-XML DOM parse这个回应,然后显示结果。
根据上述过程,相信读者可以很容易地把我们用到的部件在本文第一部分的图1中对号入座。
我们利用一个Microsoft提供的监视工具(Apache SOAP中也有一个类似的工具,在参考资料1的Part3中有完整的屏幕截图和用法),截获了请求和应答的XML(不要因此而担心SOAP的安全问题,如果不希望传送明文,可以使用HTTPS)在下面的清单4和清单5中,可供大家回忆前面的程序注释和各种设置。
<?xml version="1.0" encoding="UTF-8" standalone="no"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"xmlns:xsd="http://www.w3.org/1999/XMLSchema"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><ns1:getScore xmlns:ns1="urn:bearsoaptest:score"><StudentNo xsi:type="xsd:int">100</StudentNo></ns1:getScore></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
清单4,请求的XML<?xml version='1.0' encoding='UTF-8'?><SOAP-ENV:Envelopexmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"xmlns:xsd="http://www.w3.org/1999/XMLSchema"xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><ns1:getScoreResponse xmlns:ns1="urn:bearsoaptest:score"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><return xmlns:ns2="urn:bearsoap" xsi:type="ns2:ScoreRecord"><physics xsi:type="xsd:int">5</physics><name xsi:type="xsd:string">Tiger</name><mathematics xsi:type="xsd:int">4</mathematics></return></ns1:getScoreResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
清单5 应答的XML至此,我们完成了这个能粗略覆盖整个架构的例子实践,在本系列的下一篇文章中,我们将比较深入地探讨这个架构的特点和优劣,以及作者对于SOAP的一些观点。
参考资料
Graham Glass的《Web services (r)evolution》分为四个部分,全面地介绍了Web Service和SOAPRPC的原理和实际应用,这篇文章是促使我撰写本文的一个直接动因。它的Part 1写得棒极了,对Web Service作了全面、系统且具前瞻性分析和介绍,同时客观地指出了目前WebService急需解决的一些技术问题,可以作为SOAP RPC应用研究的Roadmap。从下面地址可以获得DW美国站英文原文。
http://www-106.ibm.com/developerWorks/xml/library/ws-peer1.html?dwzone=xml
DW目前只翻译了Part 1和Part 2,在这里可以获得中文版:
http://www.cn.ibm.com/developerWorks/components/ws-peer1/index.shtml
http://www.cn.ibm.com/developerWorks/components/ws-peer2/index.shtmlwww.xmethods.com 的资源指南中有很多Web Service、SOAP、XML相关的技术文章和产品。http://www.xml.org.cn:8188一个非常不错的中文XML站点,翻译了很多有价值的标准文档,同时还有一些很简明的例子。就在本文写到一半的时候,W3C于5月2日确定了XML Schema(Part 0,1,2)的正式标准,这将使SOAP在异种系统间传送不依赖语言的复杂数据结构变得更方便和统一。从这里可以得到标准的全文:http://www.w3.org/TR/2001/REC-xmlschema-0-20010502W3C的SOAP 1.1标准:http://www.w3.org/TR/SOAP/本文中用到的软件可从下列地址得到:
ApacheTomcat 3.2.1
Apache SOAP2.1
IBM XML4J 3.1.1
MicrosoftSOAP Toolkit 2.0
Sun JavaBeansActivation Framework 1.0.1
Sun JavaMail 1.2载本文中的所有例子源代码。关于"三八线":特指朝鲜半岛大韩民国与朝鲜民主主义共和国的分界线,在地理上刚好位于北纬38度,故此得名。50年代朝鲜半岛南北之间曾爆发战争,中、美、英、前苏联等国均直接或间接参战,停战后仍维持此分界线至今。另,在北纬38度线上,全球有很多风景奇丽的地方,例如:中国的九寨沟……作者简介
许方,1991年毕业于武汉测绘科技大学计算机科学与工程系,毕业后被分配到中国计算机软件与技术服务总公司,从事国产Unix系统开发和金融系统应用,三年后辞职创业,但至今未成大器。自中学时代开始接触AppleII,经历了中国软件业到目前为止的完整发展历程,在十多年的软件工作生涯中,主要从事银行、零售业系统的开发、体系架构设计和项目管理,曾经成功地主导开发和实施过多个大中型POS系统项目、银行业务系统和企业MIS系统,现为自由职业者,主要为若干固定客户从事软件项目的技术咨询工作,同时作为自由撰稿人为一些网站和电子媒体介绍一些新的企业计算技术。此外,自己建立了一个小型的个人新技术实验室,有若干台装有不同平台的电脑并且连成一个小型的Intranet。最大的爱好就是不断地去弄明白层出不穷的软件技术和概念。