文档库 最新最全的文档下载
当前位置:文档库 › java_soap

java_soap

本文由院中的蔷薇贡献
pdf文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。
第五章
处理复杂数据类型
在前一章中,我们用 Java 创建了几个 RPC 形式的服务,这些服务仅仅处理了简 单数据类型.简单数据类型可能在有些场合很合适,但你总会遇到需要使用更复 杂的数据类型的时候,就像平时在 Java 编程中经常遇到的一样.在这一章中,我 们来看看如何使用以数组和 Java bean 作为参数和返回值的方法来建立服务.由 于所有的自定义数据类型都可能用到本章中讨论的复杂数据类型, 因此一直到下 一章结束,我们都将涉及到用户自定义的数据类型.
传递数组参数
我们来看看数组,它们也许是在程序设计中使用得最普遍的复杂数据类型了.它 们无处不在,当然在 SOAP 中也是如此.我们在第三章中曾使用过 SOAP 数组, 因此你可能对数组如何编码的细节已有所了解. 那么现在就让我们用数组来编写 Java 的服务吧. 本章中我们继续使用前面使用过的股票市场的例子. 一个能够返回股票信息的服 务会是很有用的,它需要返回当天股票的交易量,这些股票的平均交易价格,当 天价格上涨的股票数等等.可能还有其他各种情况.我们以一个返回当日股票总 交易量的服务作为开始. 我们将这个服务叫做 urn:BasicTradingService, 它有 一个名为 getTotalVolume 的方法.下面是实现这个服务的 Java 类:
106
处理复杂数据类型
107
package javasoap.book.ch5; public class BasicTradingService { public BasicTradingService() {} public int getTotalVolume(String[] stocks) { // 从某个数据来源得到各股票的交易量 // 并返回其总数 int total = 345000; return total; } }
BasicTradingService类有一个 getTotalVolume()方法,该方法返回交易的总 数.由于我们不打算真正从数据提供者处取得输入数据,因此我们只返回一个虚 构的值. 这个方法接受一个名为 stocks的字符串数组并返回一个整数. 这个字符 串数组中的值代表股票代码,在实际应用中,我们需要从数据提供者处分别取得 字符串数组中各个股票的交易数量,然后返回一个总数. Apache SOAP已经为数组提供了内置的支持, 因此你不需要在服务器端做什么特 别的事情.这也意味着在部署描述文件中不用添加新的内容,和前几章中介绍的 一样即可.下面是服务 urn:BasicTradingService 的部署描述文件:
org.apache.soap.server.DOMFaultListener
你可以使用上面这个部署描述文件来部署你的服务, 或者在浏览器中使用Apache SOAP 的 Admin 工具来部署它.
108
第五章
现在我们要写一个访问该服务的客户应用程序.这与前几章里的很相似,不同的 仅仅是传入的参数类型,这

里我们用字符串数组代替了前面的简单类型.这是它 的代码:
package javasoap.book.ch5; import https://www.wendangku.net/doc/3b1504556.html,.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; public class VolumeClient { public static void main(String[] args) throws Exception { URL url = new URL("http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call(); call.setTargetObjectURI("urn:BasicTradingService"); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); String[] stocks = { "MINDSTRM", "MSFT", "SUN" }; Vector params = new Vector(); params.addElement(new Parameter("stocks", String[].class, stocks, null)); call.setParams(params); try { call.setMethodName("getTotalVolume"); Response resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue(); Object value = ret.getValue(); System.out.println("Total Volume is " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); } } }
我们向Parameter构造函数传入的第二个参数是String[].class, 这说明stocks 参数的类型是字符串数组.好了,到现在为止我们已经完成了所有的工作.如果 你运行这个例子,其输出应该是这样的:
Total Volume is 345000
处理复杂数据类型
109
我们现在来看看为了调用服务的 getTotalVolume 方法而向服务器传送的 SOAP 封套:
MINDSTRM MSFT SUN
stocks 元素表示我们传给服务方法的字符串数组参数.通过把 xsi:type属性设 置为 ns2:Array 来指定数组类型. ns2 名称空间标识符在前面的行中定义.接下来,为 ns2:arrayType属性分配一 个值xsd:string[3]. 这表示该数组的大小为3, 数组的每个元素都是xsd:string 类型.stocks 元素有三个子元素,每一个的名称都是 item.记住,数组的子元 素使用什么名称并不重要,因为不同的 SOAP 实现使用不同的模式来为这些元素 命名. 本例通过将 xsi:type设为 xsd:string显式地指定了数组中每个元素的类 型. 这个例子使用的是同构数组(homogeneous array) ,也就是说,数组中所有的元 素都是同种类型的.你或许还会遇到要使用异构数组(heterogeneous array)的 情况,让我们来看看这种可能性.在 Java 语言中,方法常常使用数组作为参数, 而在其他语言里可能使用一个变长的参数列表.举个例子, 语言中的 printf() C 函数的参数个数就是不固定的.尽管 Java 不支持这种方式,但你可以通过在数组
110
第五章
中传递参数值来模拟这种功能.一个数组可以是任何长度的,而且其中的元素并 不一定要是同一类型的. 现在我们向 urn:BasicTradingService 服务中添加一个用异构数组作为参数的 方法 executeTrade. 它的参数是一个数组, 数组的

元素包含股票代码, 需要交易 的数量和一个表示是买还是卖的标志(true 表示买) .返回值是一个描述交易的 字符串(注 1) .这里是改动后的 BasicTradingService 类:
package javasoap.book.ch5; public class BasicTradingService { public BasicTradingService() { } public int getTotalVolume(String[] stocks) { // 从某个数据来源得到各股票的交易量 // 并返回其总数 int total = 345000; return total; } public String executeTrade(Object[] params) { String result; try { String stock = (String)params[0]; Integer numShares = (Integer)params[1]; Boolean buy = (Boolean)params[2]; String orderType = "Buy"; if (false == buy.booleanValue()) { orderType = "Sell"; } result = (orderType + " " + numShares + " of " + stock); } catch (ClassCastException e) { result = "Bad Parameter Type Encountered"; } return result; } }
注 1:
当然还有其他方式来设计这样的方法接口, 而且这也不是我所选择的设计. 这种情况 可能会要求使用自定义类或 Java bean.但本例中所使用的方法演示了同构数组的使 用.
处理复杂数据类型
111
executeTrade()方法只有一个参数:一个叫 params 的 Object[]数组.这个数 组中的对象必须转换成相应的类型:String,Integer 和Boolean.我喜欢将类 型转换放在一个 try/catch块中以防调用者出错.在这种方式下,我可以在方法 调用错误时做一些适当的处理,尽管有时候只是简单地返回错误描述.在第七章 里,我们将介绍在这种情况下如何生成 SOAP 错误.executeTrade()方法将数 组传递的信息生成一个描述输入参数的字符串, 该字符串保存在result变量中并 返回给调用者. 现在我们来修改客户应用程序,使它能够向服务的 executeTrade 方法传递一个 适当的 Object[]类型的参数.multiParams变量被声明为 Object[]类型,它由 一个字符串 MINDSTRM,一个值为 100 的整数和一个值为 true 的布尔变量组成. 既然我们使用的是 Java 的 Object 数组,我们就不再使用 Java 的简单类型作为 数组的元素. 取而代之, 我们将这些简单值包装到等价的 Java对象里. Parameter 构造函数的第二个参数是 Object[].class,它是一个对象数组的类.
package javasoap.book.ch5; import https://www.wendangku.net/doc/3b1504556.html,.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; public class TradingClient { public static void main(String[] args) throws Exception { URL url = new URL( "http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call(); call.setTargetObjectURI("urn:BasicTradingService"); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); Object[] multiParams = { "MINDSTRM", new Integer(100), new Boolean(true) }; Vector params = new Vector(); params.addElement(new Parameter("params", Object[].class, multiParams, null)); call.setParams(params);
112
第五章
try { call.setMethodName("executeTrade"); Response resp = call

.invoke(url, ""); Parameter ret = resp.getReturnValue(); Object value = ret.getValue(); System.out.println("Trade Description: " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); } } }
如果一切正常,运行 executeTrade()服务方法的结果将是:
Trade Description: Buy 100 of MINDSTRM
我们可以试试改变 multiParams数组中参数的顺序.在这种情况下, 我们将得到 一个类转换异常,而且该方法会返回一个错误字符串. 这里是正确调用 executeTrade()服务方法的 SOAP 封套:

MINDSTRM 100 true

处理复杂数据类型
113
通过将 xsi:type属性的值设置为 ns2:Array来设定 params元素的类型.它与同 构的情况相比惟一不同的是, 数组中各个元素的类型由不同的 ns2:arrayType属 性值来设定.值 xsd:anyType[3]指定数组包含 3 个元素,每一个都可能是任意 的有效数据类型(注 2) . 现在让我们看看在 GLUE 中传递数组参数的方法.BasicTradingService 类在 GLUE 中可以不做修改就部署. 我们将使用一个简单的 Java 应用程序来启动这个 服务:
package javasoap.book.ch5; import electric.util.Context; import electric.registry.Registry; import electric.server.http.HTTP; public class BasicTradingApp { public static void main( String[] args ) throws Exception { HTTP.startup("http://georgetown:8004/glue"); Context context = new Context(); context.addProperty("activation", "application"); context.addProperty("namespace", "urn:BasicTradingService"); Registry.publish("urn:BasicTradingService", javasoap.book.ch5.BasicTradingService.class, context ); } }
编译并运行这个应用程序,该服务将被部署.现在让我们使用 GLUE API 写一个 简单的例子来访问这个服务. 首先我们来看服务的接口IBasicTradingService:
package javasoap.book.ch5; public interface IBasicTradingService { int getTotalVolume(String[] symbols); String executeTrade(Object[] params); }
注 2:
在第三章里我们提到了用 ur-type 来表示任何可能的数据类型.在这里我们使用了 anyType 来达到同样的目的.2001 年 5 月发布的 XML Schema 建议性标准的第 0 部 分在第 2.5.4 节中做出了下面的解释: "anyType 表示一个叫做 ur-type 的抽象,urtype是派生所有的简单类型和复杂类型的基础类型. anyType类型不以任何形式约束 它的内容. "
114
第五章
现在我们可以编写一个绑定到该服务并调用它的两个方法的应用程序:
package javasoap.book.ch5; import electric.registry.RegistryException; import electric.registry.Registry; public class BasicTradingClient { public static void main(String[] args) throws Exception { try { IBasicTradingService srv = (IBa

sicTradingService)Registry.bind( "http://georgetown:8004/glue/urn:BasicTradingService.wsdl", IBasicTradingService.class); String[] stocks = { "MINDSTRM", "MSFT", "SUN" }; int total = srv.getTotalVolume(stocks); System.out.println("Total Volume is " + total); Object[] multiParams = { "MINDSTRM", new Integer(100), new Boolean(true) }; String desc = srv.executeTrade(multiParams); System.out.println("Trade Description: " + desc); } catch (RegistryException e) { System.out.println(e); } } }
正如我们以前看到的,GLUE 允许我们使用熟悉的 Java 编程语法,而不必考虑底 层的 SOAP 结构.这对传递数组参数同样适用.在将接口绑定到服务之后,所有 的事情都已经被我们处理得非常好了. 在本例所产生的 S O A P 请求封套中, 你会看到一些有趣的事情.这就是 getTotalVolume()方法调用的 SOAP 封套:
处理复杂数据类型
119
SOAP-ENV:encodingStyle= "https://www.wendangku.net/doc/3b1504556.html,/soap/encoding/"> ABC DEF GHI JKL

为了在 GLUE 中部署 BasicTradingService 类的这一个版本,可以使用我们原 来的 BasicTradingApp类.我们可以修改 Java 接口 IBasicTradingService,使 它包括新的方法:
package javasoap.book.ch5; public interface IBasicTradingService { int getTotalVolume(String[] symbols); String executeTrade(Object[] params); String[] getMostActive(); }
现在我们要修改应用程序BasicTradingClient来加入一个对getMostActive() 方法的调用,然后在数组中迭代各个值并打印它们.在使用 GLUE 时我们不需要 将返回值的类型转换为 String[],这是因为,与 Apache SOAP 的例子不同,这 个接口的 getMostActive()方法已经被定义为返回正确的类型了. 这里是修改后 的代码:
package javasoap.book.ch5; import electric.registry.RegistryException; import electric.registry.Registry; public class BasicTradingClient { public static void main(String[] args) throws Exception { try { IBasicTradingService srv = (IBasicTradingService)Registry.bind( "http://georgetown:8004/glue/urn:BasicTradingService.wsdl", IBasicTradingService.class);
120
第五章
String[] stocks = { "MINDSTRM", "MSFT", "SUN" }; int total = srv.getTotalVolume(stocks); System.out.println("Total Volume is " + total); Object[] multiParams = { "MINDSTRM", new Integer(100), new Boolean(true) }; String desc = srv.executeTrade(multiParams); System.out.println("Trade Description: " + desc); String[] actives = srv.getMostActive(); int cnt = actives.length; for (int i = 0; i < cnt; i++) { System.out.println(actives[i]); } } catch (RegistryException e) { System.out.println(e); } } }
运行这个例子会得到如下输出:
Total Volume is 345000 Trade Description: Buy 100 of MINDSTRM ABC D

EF GHI JKL
GLUE 对作为返回值的数组使用了与前面作为参数的数组相同的串行化技术;它 为实际的返回值建立了一个独立串行化的数组, 然后又引用了这个实际的数组值. 调用 getMostActive()方法所返回的 SOAP 封套是:

我们来建立一个名为 StockTrade_ClientSide 的自定义 Java 类,它描述了一次 股票交易. 一般情况下我会将这个类命名为 StockTrade, 但是现在我想要表明我 将在客户端使用这个类.我们会为服务器端的使用编写一个类似的类,它将使用 一个对应的名字.我这样做是要说明你不必在消息事务的两端使用同一个 Java 类.事实上,也许使用同一个类是没有意义的,甚至常常是不可能的. StockTrade_ClientSide 有三个可读写的属性:Symbol,Buy 和 NumShares,它 们分别表示股票代码,买卖标志和交易数量.
注 3: JavaBean 提供了其他办法来完成这个工作,但这超出了本书的研究范围.
处理复杂数据类型
123
package javasoap.book.ch5; public class StockTrade_ClientSide { String _symbol; boolean _buy; int _numShares; public StockTrade_ClientSide() { } public StockTrade_ClientSide(String symbol, boolean buy, int numShares) { _symbol = symbol; _buy = buy; _numShares = numShares; } public String getSymbol() { return _symbol; } public void setSymbol(String symbol) { _symbol = symbol; } public boolean isBuy() { return _buy; } public void setBuy(boolean buy) { _buy = buy; } public int getNumShares() { return _numShares; } public void setNumShares(int numShares) { _numShares = numShares; } }
现在让我们创建一个StockTrade_ServerSide类来表示服务器端的股票交易. 只 是为了使这个类与它在客户端的副本之间显得不同, 我们删掉了带参数的构造函 数.而且为了对应,我们需要改变类变量的名称并调整它们出现的顺序.
package javasoap.book.ch5; public class StockTrade_ServerSide { int _shares; boolean _buyOrder; String _stock; public StockTrade_ServerSide() { } public String getSymbol() { return _stock;
124
第五章
} public void setSymbol(String stock) { _stock = stock; } public boolean isBuy() { return _buyOrder; } public void setBuy(boolean buyOrder) { _buyOrder = buyOrder; } public int getNumShares() { return _shares; } public void setNumShares(int shares) { _shares = shares; } }
我们可以向urn:BasicTradingService服务中加入一个新的方法executeStockTrade(), 它将股票交易作为参数. 这个方法返回的是一个描述交易的字符串. 下 面是被修改后的 B a s i c T r a d i n g S e r v i c e 类.我们能利用这个类中已有的 executeTrade()方法.在新方法 executeStockTrade()中,我们用 trade 参数 的三个属性创建了一

个 Object 数组,并将这个数组传递给 executeTrade()方 法.
package javasoap.book.ch5; public class BasicTradingService { public BasicTradingService() { } public String executeStockTrade(StockTrade_ServerSide trade) { Object[] params = new Object[3]; params[0] = trade.getSymbol(); params[1] = new Integer(trade.getNumShares()); params[2] = new Boolean(trade.isBuy()); return executeTrade(params); } public String[] getMostActive() { // 取得交易最活跃的股票 String[] actives = { "ABC", "DEF", "GHI", "JKL" }; return actives; } public int getTotalVolume(String[] stocks) {
处理复杂数据类型
// 从某个数据来源得到各股票的交易量 // 并返回其总数 int total = 345000; return total; } public String executeTrade(Object[] params) { String result; try { String stock = (String)params[0]; Integer numShares = (Integer)params[1]; Boolean buy = (Boolean)params[2]; String orderType = "Buy"; if (false == buy.booleanValue()) { orderType = "Sell"; } result = (orderType + " " + numShares + " of " + stock); } catch (ClassCastException e) { result = "Bad Parameter Type Encountered"; } return result; } }
125
为部署这个服务,我们需要把自定义类型映射到实现该类型的 Java 类.我们还需 要给自定义类型一个名称并用一个适当的名称空间限定它. 这有点像我们前面声 明一个服务的步骤.映射发生在部署描述文件的 isd:sections部分中.这就是 我们将用来在 Apache SOAP 中部署该服务的部署描述文件:
org.apache.soap.server.DOMFaultListener
让我们在继续介绍之前对映射做一个总结,这很重要.从自定义类型到 Java 类之 间的映射与一种编码形式和一个完全限定的类型名称有关. 一个 Java 类实现对自 定义类型的描述,而工具类则用来完成串行化和反串行化工作.现在让我们来看 例子中的细节. 部署描述文件中的大部分内容看起来很熟悉;它与第四章给出的很类似.不同的 是我们现在的 isd:mappings 部分中多了一项.isd:map 项描述了从股票交易类 型到 StockTrade_ServerSide类的映射. isd:map元素中没有数据; 所有的信息 都以属性形式提供. 第一个属性encodingStyle指定了与串行化该类型相关的编 码形式.下一个是名称空间标识符 x,其值被设为 urn:BasicTradingService. 我使用了服务的名称作为自定义类型的名称空间限定符;然而,你不一定要这样 做,而且事实上在某些情况下你也不可能这么做.举例来说,如果你的自定义类 型被多个服务所使用,或者你使用的是一个由第三方定义的自定义类型,那么你 肯定想要为自定义类型使用一个适当的名称空间.接下来的属性 qname指定了该 类型被映射到的完全限定符名.给它设定的值是 x:StockTrade,这与用服务名 限定的名称空间 StockTrade是同一个名称空间. javaType属性指明了用于实现 这个类型的服

务器-本地 Java 类;在服务器端我们使用了 javasoap.book. ch5.StockTrade_ServerSide.最后两个属性 java2XMLClassName 和 xml2JavaClassName分别告诉 Apache SOAP 使用哪个本地 Java 类来完成串行化 和反串行化工作.Apache SOAP 自带了一个自定义串行化 / 反串行化类,它可以 实现自定义 XML 类型与遵循 JavaBeans 属性存取器模式的 Java 类之间的转换. 它 能完成串行化和反串行化两种工作,这是我们为两个属性都使用了它的原因.在 下一章中我们将看到如何创建自定义类型串行化器. 既然已经有了一个部署描述文件,我们就可以马上部署 urn:BasicTradingService.一旦这样做了,我们的服务就已准备就绪,可以接受对 execute-
处理复杂数据类型
127
StockTrade方法的调用了. 然而, 我们还需要在客户应用程序中也做一些安装工 作.让我们看看客户应用程序:
package javasoap.book.ch5; import https://www.wendangku.net/doc/3b1504556.html,.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; import org.apache.soap.encoding.*; import org.apache.soap.encoding.soapenc.*; import org.apache.soap.util.xml.*; public class StockTradeClient { public static void main(String[] args) throws Exception { URL url = new URL("http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call(); SOAPMappingRegistry smr = new SOAPMappingRegistry(); call.setSOAPMappingRegistry(smr); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); call.setTargetObjectURI("urn:BasicTradingService"); call.setMethodName("executeStockTrade"); BeanSerializer beanSer = new BeanSerializer(); // 映射 Stock Trade 类型 smr.mapTypes(Constants.NS_URI_SOAP_ENC, new QName("urn:BasicTradingService", "StockTrade"), StockTrade_ClientSide.class, beanSer, beanSer); // 建立一个股票交易的实例 StockTrade_ClientSide trade = new StockTrade_ClientSide("XYZ", false, 350); Vector params = new Vector(); params.addElement(new Parameter("trade", StockTrade_ClientSide.class, trade, null)); call.setParams(params); Response resp; try { resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue(); Object desc = ret.getValue(); System.out.println("Trade Description: " + desc); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); }
128
第五章
} }
在建立了 Call 对象之后,我们又创建了 org.apache.soap.encoding.SOAPMappingRegistry的一个实例smr. 这个类包含了从自定义类型到Java类的映射, 并将它通过 Call 对象的 setSOAPMappingRegistry()方法传给这个 Call 对象. 我们在前面的例子中不需要这样做, 这是因为在没有任何参数传递的情况下Call 对象会为自己建立一个映射注册表. SOAPMappingRegistry()中包含了所有预先 定义的映射,例如我们用于数组的那些映射.我们不久将会把我们的映射添加进 去. 接下来我们调用了 call.setEncodingSty

leURI()方法. 这个方法指定了我们为 自定义类型所使用的总体编码形式.来自类 org.apache.soap.Constants 的常 量 NS_URI_SOAP_ENC 表示我们使用的 https://www.wendangku.net/doc/3b1504556.html,/soap/ encoding/名称空间.这个编码形式的名称空间应该与我们在部署描述文件中为 StockTrade类型指定的编码形式使用的名称空间相同. setTargetObjectURI() 和setMethodName()与它们在前面例子中的使用方法是一样的. 下一步是要创建 org.apache.soap.encoding.soapenc.BeanSerializer类的一个实例; 我们可 以使用这个标准的串行化器是因为我们的自定义类型遵循了 JavaBeans 属性存取 器模式.现在我们可以通过调用 smr.mapTypes()来建立映射了.该方法的第一 个参数是 Constants.NS_URI_SOAP_ENC,指明映射要使用的编码形式是标准的 SOAP 编码形式.你可能会觉得很奇怪:我们已经在前面为整个调用指定过编码 形式了,在这里为什么还要这样做?理由很简单:这个参数给你一个机会来覆盖 这种特定映射的编码形式.不过,如果你使用 null作为参数值,那么映射将对编 码形式使用 null名称空间,这是不正确的.我们来看这个消息的 SOAP 封套,你 会看到尽管编码形式与总体调用的编码形式是一致的, 但是对于这个串行化参数, encodingStyle 属性并没有重复出现.如果某个参数的编码形式与 Call 使用的 不同,它就会作为该参数的属性出现. smr.mapTypes()的下一个参数是 org.apache.soap.util.xml.Qname的一个实 例, 它表示一个被完全限定的名称 (名称前面加上了一个名称空间限定符) 我们 . 使用 urn:BasicTradingService 作为名称空间,StockTrade作为名称.第三个
处理复杂数据类型
129
参数是 StockTrade_ClientSide.class,它是实现了自定义类型的 Java 类.接 下来的两个参数是用于这个类型的串行化器和反串行化器的实例.因为 org.apache.soap.encoding.soapenc.BeanSerializer类实现了串行化和反串 行化,所以在这里我们使用了前面为它们创建的 beanSer 对象. 剩下的部分相当简单. 我们创建了 StockTrade_ClientSide的一个实例, 并用带 参数的构造函数设定了它的属性值. 然后我们为参数建立了一个 Vector, 这与我 们在前面的例子中所做的是一样的.如果运行这个例子,你应该会看到如下的输 出:
Trade Description: Sell 350 of XYZ
让我们看看被传输的 SOAP 封套.封套的相关部分由 trade 元素开始,它表示要 传递给 executeStockTrade 服务方法的自定义参数.分配给 xsi:type 的值是 ns1:StockTrade.ns1 是一个在父元素 executeStockTrade 中被声明为 urn: BasicTradingService 的名称空间标识符.而且 StockTrade是自定义类型的名 称. trade元素有三个子元素, 每一个子元素对应于 StockTrade自定义类型的一 个属性. 每个元素的名称也与该属性完全对应.这是很

关键的,因为 A p a c h e SOAP 要通过 Java 映射来寻找与 Java 类相关联的 set 方法.因此,封套中的名称 一定要和类使用的名称相匹配.这些属性元素的类型都被显式地声明,而且这些 类型也必须与对应的属性类型一致.

350 false XYZ

130
第五章
现在让我们看看在GLUE中如何处理自定义类型, 我们仍不做修改地使用 BasicTradingService 类.我们需要在 IBasicTradingService 接口中加入 executeStockTrade()方法.我们可以用前面介绍的那些方法来部署这个服务. 这里是 IBasicTradingService 的新版本:
package javasoap.book.ch5; public interface IBasicTradingService { int getTotalVolume(String[] symbols); String executeTrade(Object[] params); String[] getMostActive(); String executeStockTrade(StockTrade_ClientSide trade); }
由于我们不需要直接处理 SOAP 结构,因此使用 GLUE API 来编写访问服务的应 用程序就变得很简单了.在这里我们也应该可以按同样的模式来做,但是有一个 问题.IBasicTradingInterface 接口的 executeStockTrade()方法的参数是 StockTrade_ClientSide 的一个实例.而用于实现服务的 BasicTradingService类的executeStockTrade方法使用StockTrade_ServerSide作为参数. 这是设计导致的一个失配.默认情况下,GLUE 会在客户端和服务器端寻找同一 个类.假如我们在客户应用程序中用 StockTrade_ServerSide类来取代 StockTrade_ClientSide 类,那么一切将会很完美.但我们不准备那样做,因此你只 要相信它会工作得很好就行了 (或者, 你自己尝试一下当然更好) 我们将采取另 . 外一种方式. 每当我开发分布式系统时,我会很高兴让客户端和服务器端代码共享 Java 接口. 但是,我不喜欢共享 Java 类,尤其是当那些类中包含了特定于客户或服务器的代 码的时候.对于这个例子,我们可以重写我们的股票交易类,删去那些客户和服 务器功能代码, 只剩下相关数据. 我们可以让客户和服务器使用同一个包里的类, 因此作为基本交易 Web 服务的实现者,我们应该分发这样一个包,让它包含用于 开发者客户应用程序的类.这样做是可行的,但通常不会这样做.回到共享 Java 接口而不是类的观念上来吧,我们可以为交易数据建立一个接口,然后让服务器 和客户类都实现那个接口. 这是不在服务器和客户之间共享任何实际的可执行代 码而是共享代码要素的一个漂亮而整洁的方法.再说一次,这个接口会存在于一
处理复杂数据类型
131
个对服务器和客户系统都可用的软件包中.GLUE 声称将在它的下一个版本中支 持这种机制(当读到这里时你可能已

经在使用这个新的版本了) . GLUE 支持的另外一种机制也可以让你达到这个目的.这种机制不需要你修改服 务器端,在客户端要做的工作也很简单.我们要为我们的客户例子创建一个新的 包,这是因为在接下来的工作中我们会创建与前面使用的文件名称相同的文件. 当在同一台计算机上运行客户和服务器时,新的软件包会防止我们覆盖文件.因 此要注意这个例子是一个叫做javasoap.book.ch5.client的新软件包的一部分, 而且我们将要创建的文件存在于这个软件包对应的目录中. 到目前为止我一直有意地避免讨论 WSDL, 尽管 GLUE 是完全建立在 WSDL 之上 的.然而,本过程的第一个步骤就使用了 GLUE 的 wsdl2java工具程序,它将生 成基于某个 WSDL 文件的 Java 代码.那么这个 WSDL 是从哪里来的呢?它是由 GLUE 服务器自动产生的.基于 GLUE API 的客户端应用程序自始至终都在使用 它.wsdl2java 会生成绑定到服务的 Java 接口,一个辅助类,一个描述我们使用 的自定义类型数据字段的 Java 数据结构类和一个映射文件. 映射文件本质上是一 个自定义类型的模式定义; 它告诉 GLUE 该如何将 Java 数据结构类的字段映射到 自定义类型的字段. GLUE使用了很多种机制来处理自定义类型映射; 在本例中, 映射文件显式地定义了映射,而不是依靠 Java 反射(reflection)来进行映射. 现在让我们来为客户应用程序生成这些文件.确认你的当前目录是 javasoap. book.ch5.client 软件包所在的目录,然后输入下面的命令:
wsdl2java http://georgetown:8004/glue/urn:BasicTradingService.wsdl -p javasoap.book.ch5.client
wsdl2java 工具程序的参数是服务 WSDL 的完整 URL,就像我们前面例子里在 bing()方法中使用的一样.-p 选项告诉工具程序生成代码所使用的本地软件包 的名称:javasoap.book.ch5.client.wsdl2java 的输出由四个文件组成.第 一个是 IBasicTradingService.java,包含用来绑定到服务的 Java 接口.在此之前 我们一直是用手工编写这个文件的; 现在让我们看看wsdl2java生成的这个文件:
// 由 GLUE 生成的 package javasoap.book.ch5.client;
132
第五章
public interface IBasicTradingService { String executeStockTrade( StockTrade_ServerSide arg0 ); String[] getMostActive(); int getTotalVolume( String[] arg0 ); String executeTrade( Object[] arg0 ); }
这个接口定义与我们自己编写的不同仅仅在于声明的包不同, 方法的参数名称不 同以及executeStockTrade使用了StockTrade_ServerSide而不是StockTrade_ ClientSide作为参数. 你应该还记得IBasicTradingService的早期版本不能工 作就是因为 G L U E 默认对客户和服务器都使用同一个类.乍一看,这里 StockTrade_ServerSide类的用法违背了我们原来说的要在客户代码与服务器代 码间完全解除耦合的那个原则.

但是记住软件包的声明是 javasoap.book.ch5. client, 所以这个Java接口并没有引用服务器所使用的那个相同的 StockTrade_ ServerSide 类,因为它们属于不同的包.让我们看看由 wsdl2java 生成的 StockTrade_ServerSide.java 文件中的那个 StockTrade_ServerSide 类:
// 由 GLUE 生成的 package javasoap.book.ch5.client; public class StockTrade_ServerSide { public int _shares; public boolean _buyOrder; public String _stock; }
这只是在服务器端所使用的 javasoap.book.ch5包中对应类的一个镜像.然而, 它的确正确地反映了数据值. 我们将在客户应用程序中创建这个类的一个实例并 传递给服务,因为这正是前面生成的那个 IBasicTradingService 接口的 executeStockTrade()方法所要求的类型. wsdl2java生成的第三个文件是BasicTradingService.map, 它包含了前面提到的 映射模式.你可以自己去看看这个映射文件的内容;它包含了定义客户端和服务 器端 Java 之间字段映射的 XML 项. 生成的最后一个文件是 BasicTradingServiceHelper.java,它包含了一个完成 bind()操作的辅助类.我不会用到那个辅助类, 因此在这里我们将不理会它.
处理复杂数据类型
133
讲述一个过程要比实现这个过程麻烦得多. 接下来让我们着手编写客户应用程序:
package javasoap.book.ch5.client; import electric.registry.RegistryException; import electric.registry.Registry; import electric.xml.io.Mappings; public class StockTradeClient2 { public static void main(String[] args) throws Exception { try { Mappings.readMappings("BasicTradingService.map"); IBasicTradingService srv = (IBasicTradingService)Registry.bind( "http://georgetown:8004/glue/urn:BasicTradingService.wsdl", IBasicTradingService.class); StockTrade_ServerSide trade = new StockTrade_ServerSide(); trade._stock = "MINDSTRM"; trade._buyOrder = true; trade._shares = 500; String desc = srv.executeStockTrade(trade); System.out.println("Trade Description is: " + desc); } catch (RegistryException e) { System.out.println(e); } } }
在完成绑定操作之前的关键一步是要读取映射文件.这个工作通过调用 electric.xml.io.Mappings类的readMappings()方法来完成, 这个类是 GLUE 的一部分.在调用了 bind()之后,我们简单地创建了 StockTrade_ServerSide 类的一个实例,为它的各个字段设置了值,并调用了 executeStockTrade方法. 运行这个应用程序的输出应该是:
Trade Description is: Buy 500 of MINDSTRM
现在让我们看看这个应用程序所产生的 S O A P 封套.和我们预期的一样, executeStockTrade 元素使用了服务名作为名称空间限定.其子元素 arg0 是对 一个名为 id0的独立串行化元素的引用,正如 GLUE 为一个数组参数所产生的一 样.这个例子向我们说明了 GLUE 为什么要产生一个名称空间限定符 ns2,而它
134
第五章
看起来是在引用服务器

端实现类所在的包.在这里该名称空间被用来限定 xsi:type属性的值, 这个值与实现类的名称 StockTrade_ServerSide对应. id0 元素的子元素中包含了自定义类型的数据字段.
org.apache.soap.server.DOMFaultListener
现在我们创建一个客户应用程序来调用服务方法getHighLow并取得返回的最高/ 最低价对象.对于 Apache SOAP 客户,我们要使用 HighLow_ClientSide 类来 表示返回值.下面就是这个新的应用程序:
package javasoap.book.ch5; import https://www.wendangku.net/doc/3b1504556.html,.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; import org.apache.soap.encoding.*; import org.apache.soap.encoding.soapenc.*; import org.apache.soap.util.xml.*; public class HighLowClient { public static void main(String[] args) throws Exception { URL url = new URL("http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call(); SOAPMappingRegistry smr = new SOAPMappingRegistry(); call.setTargetObjectURI("urn:BasicTradingService"); call.setMethodName("getHighLow"); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); call.setSOAPMappingRegistry(smr); BeanSerializer beanSer = new BeanSerializer(); // 映射 High/Low 类型 smr.mapTypes(Constants.NS_URI_SOAP_ENC, new QName("urn:BasicTradingService", "HighLow"), HighLow_ClientSide.class, beanSer, beanSer); String stock = "XYZ"; Vector params = new Vector(); params.addElement(new Parameter("stock", String.class, stock, null)); call.setParams(params); Response resp; try { resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue(); HighLow_ClientSide hilo = (HighLow_ClientSide)ret.getValue(); System.out.println(hilo); }
138
第五章
catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); } } }
smr.MapTypes()方法将 HighLow 自定义类型映射到 J a v a 类 HighLow_ ClientSide. 既然我们的类遵循了 JavaBeans属性存取器模式, 那么和以前一样, 我们可以使用 Apache 的 BeanSerializer来在 XML 与 Java 之间转换.我们建立 了一个叫做 stock 的字符串参数来向 getHighLow 方法传递值(尽管在服务器代 码中我们并没有真正用到它) 在调用了这个方法之后, . 我们将resp.getReturnValue()的返回值的类型转换成了 HighLow_ClientSide 的一个实例.然后我们 把返回的参数值 ret 传递给 System.out.println()方法来显示它.因为我们已 经在 HighLow_ClientSide 类中实现了 toString()方法,因此我们现在已完成 了所有工作.当你运行这个例子的时候,会得到下面这样的输出:
High: 110.375 Low: 109.5
下面是这个方法调用所返回的 SOAP 封套.return元素的类型被设为 HighLow, 它用 urnBasicTradingService 作为名称空间限定.HighLow 的属性被作为 return 元素的子元素,其类型被设为浮点数并包含了对应的值.

NV:encodingStyle="https://www.wendangku.net/doc/3b1504556.html,/soap/encoding/"> 109.5 110.375
处理复杂数据类型
139
要使用 GLUE 来返回 HighLow,我们将会采取与传递自定义类型时相同的步骤. 我们需要再一次将客户端的程序单独地放到另一个包 javasoap.book.ch5. client 中去.重新启动 BasicTradingApp 应用程序以部署该服务.现在在 javasoap.book.ch5.client 包对应的目录下运行 wsdl2java 工具程序:
wsdl2java http://georgetown:8004/glue/urn: BasicTradingService.wsdl -p javasoap.book.ch5.client
下面的代码是 wsdl2java所产生的新的 IBasicTradingService接口. getHighLow()方法返回 HighLow_ServerSide 的一个实例;记住这个类是由 GLUE 作为 javasoap.book.ch5.client 包的一部分生成的, 而不是给我们的服务器类 BasicTradingApp 使用的.
// 由 GLUE 生成的 package javasoap.book.ch5.client; public interface IBasicTradingService { HighLow_ServerSide getHighLow( String arg0 ); String executeStockTrade( StockTradeServer arg0 ); String[] getMostActive(); int getTotalVolume( String[] arg0 ); String executeTrade( Object[] arg0 ); }
下一个类 HighLow_ServerSide是一个简单的 Java 数据结构,它反映了将要被映 射到 HighLow 自定义类型的数据字段.我手工添加了 toString()方法以使得显 示结果更容易.
package javasoap.book.ch5.client; public class HighLow_ServerSide { public float _high; public float _low; public String toString() { return "High: " + _high + " Low: " + _low; } }
现在让我们使用 GLUE 创建一个客户应用程序来调用 getHighLow 服务方法并显 示所产生的返回值的内容.不需要做很多的工作,真的;我们只需要读取映射文
140
第五章
件,完成绑定,然后在被绑定的接口中调用 getHighLow 方法.javasoap.book. ch5.client.HighLow_ServerSide 返回的实例被传给 System.out.println() 来显示.正如在 Apache SOAP 例子中一样,该类的 toString()方法组装了要显 示的字符串.
package javasoap.book.ch5.client; import electric.registry.RegistryException; import electric.registry.Registry; import electric.xml.io.Mappings; public class HighLowClient2 { public static void main(String[] args) throws Exception { try { Mappings.readMappings("BasicTradingService.map"); IBasicTradingService srv = (IBasicTradingService)Registry.bind( "http://georgetown:8004/glue/urn:BasicTradingService.wsdl", IBasicTradingService.class); HighLow_ServerSide hilo = srv.getHighLow("ANY"); System.out.println(hilo); } catch (RegistryException e) { System.out.println(e); } } }
这里是从服务器返回的 SOAP 封套.你现在应该可以看得懂它.与返回数组时的 情况一样,GLUE 使用了对该自定义类型的一个独立串行化实例的引用.

'urn:BasicTradingService'> <_high xsi:type='xsd:float'>110.375
处理复杂数据类型
141
<_low xsi:type='xsd:float'>109.5
在本章中, 我们深入介绍了数组和自定义数据类型的使用,介绍了在 A p a c h e SOAP 和 GLUE 中如何支持这些结构.你可能会在它们或者别的 SOAP 实现中找 到其他有用的数据类型.这些类型可能包括 Java Vector 和 Hashtable类,Java 集合以及其他很多已经被普遍使用的 Java 类. 我们看到 Apache SOAP 和 GLUE 使用了不同的方式来处理复杂类型,而且它们 都工作得很不错.然而,仍然会有可能出现这样的情况:你需要使用一种自定义 类型,但使用你的 SOAP 实现所提供的办法不能够对它进行串行化.出现这种情 形多半仅仅是因为它不支持某个特定类型的数据 (比如多维的或稀疏的数组) 或 , 者是与你的应用程序有关的其他原因.我们将在下一章中解决这个问题.

相关文档