Web 服务的基础是以标准格式发送和接收消息,这样所有系统都可以理解消息。通常情况下,这种标准格式是 SOAP。SOAP 消息可以手工生成和发送,但是如果说我们按上篇blog那样: 在客户端,首先创建一个HttpConnector对象,负责HTTP连接。设定Connector的一些头部信息,比如EndPoinURL和SoapAction等。如果网络连接需要使用代理服务器,那也要在这里设定相关的信息。接着创建SoapSerializer对象,用于生成Soap消息。按照WSDL里定义,把所有参数按顺序序列化,得到一个完整的SOAP请求消息。该Soap消息,作为Payload通过HttpConnector被发送到服务端。最后,生成一个SoapReader对象,负责读取服务端返回的SOAP消息,取得其中的返回值。
按上面这种SOAP消息的构建方法的话就会变得复杂起来, 所以说有必要借助于一些API来简化我们的操作.有几个基于Java的API可以用来构建低层SOAP消息来访问Web服务。这些API包括SAAJ、Web服务调用框架(WSIF)、上篇blog里提到的Axis等。
Ø 选择SAAJ的理由
我们最终选择了SAAJ,因为基于简单及适用性来考虑.SAAJ无疑很适合基于文档的同步或者异步Web Service。SAAJ使用简单,有助于在Java环境中集成各种Web Service,它扩展了对文档风格的Web Service通信的自然支持(natural support)。SAAJ还支持基于标准接口上的XML消息传递,并且这一点得到了供应商的广泛支持。另外SOAP with Attachments API for Java (SAAJ)—— Java API for XML Messaging (JAXM)的一个分支——能够使许多必需的步骤变得自动化,例如创建上面所说的连接,或者创建和发送实际消息。
Ø 什么是SAAJ
SAAJ是在松散耦合软件系统中利用SOAP协议实现的基于XML消息传递的API规范。顾名思义,SAAJ支持带附件的SOAP消息。
对于Java API for XML Messaging (JAXM),JAXM 1.0的理念是通过提供消息传递和SOAP API,允许开发人员根据SOAP编写支持消息传递标准的业务应用程序。随着JAXM 1.1版的推出,SOAP API (javax.xml.soap)被分割成了SAAJ1.1规范和JAXM1.1,JAXM1.1只包含基于消息传递的API(javax.xml.messaging)。目前,正在使用的SAAJ版本是1.2。
Ø 如何使用
回想那篇blog, Web服务搜索与执行引擎(八)——WSDL解析精髓,提到了为了使用 SAAJ构建SOAP消息调用该服务,我们将需要从 WSDL 收集下列最基本的信息:
目标名称空间
服务名称
端口名称
操作名称
操作输入参数
SOAP文档结构简单,利用SAAJ构建起来也比较方便。但我们需要将用户从网页中输入的数据作为SOAP的有效负载发送至异构平台的服务,如何来构建这个有效负载呢?按照什么样的格式将用户输入的数据放入SOAP的有效负载中呢?格式可以从解析WSDL文档过程中知道,即上面说的那些基本信息,但怎么来匹配用户的输入数据呢?这时就需要以WSDL中解析出来的参数名称作为用户从网页中输入的文本框的名字,从而匹配了每一个子参数的值,然后即可构建SOAP消息的有效负载。然后将SOAP消息发送至远程平台。
调用后,将返回一个SOAP消息返回值,我们需要解析,并将结果在网页中呈现给客户,我们利用了JDOM技术,根据从WSDL中解析出的返回信息来提取SOAP中的返回值,最终呈现给用户。
具体过程包括 5 个步骤:
1. 创建 SOAP 连接
2. 生成 SOAP 消息
3. 填充消息
4. 发送消息
5. 检索响应
SOPA 消息的结构
大家也可以去看看我的那篇blog: Web服务搜索与执行引擎(七)——重温WSDL与SOAP, 再次复习下WSDL 跟SOAP的有关知识.首先来看看消息自身的结构。一条基本的 SOAP 消息由带有两个主要部分的信封(envelope)构成:头部和主体。应用程序确定如何使用这些部分,但整个消息必须遵循特定的 XML 结构,例如:
清单1. 一条示例 SOAP 消息
这里,头部是空的,而主体包含了有效信息,或要传递的消息。在本例中,它是请求某本书价格的消息。
< SOAP-ENV:Header />
< ns1:getPrice xmlns:ns1 ="urn:Book"
SOAP-ENV:encodingStyle ="http://schemas.xmlsoap.org/soap/encoding/" >
< isbn xsi:type ="xsd:string" > 123544111 </ isbn >
</ ns1:getPrice >
</ SOAP-ENV:Body >
</ SOAP-ENV:Envelope >
注意消息的结构。Envelope 包含 Header 和 Body 元素,这三者都是http://schemas.xmlsoap.org/soap/envelope/ namespace的一部分。应用程序使用 SOAPConnection 来发送消息。
清单2. 创建连接
importjavax.xml.soap.SOAPConnection;
public class DynamicInvokeInterce ...{
......
publicListinvokeOperation(Operationoperation)throwsException...{
try...{
SOAPConnectionFactorysoapConnFactory=
SOAPConnectionFactory.newInstance();
SOAPConnectionconnection=
soapConnFactory.createConnection();
connection.close();
}catch(Exceptione)...{
System.out.println(e.getMessage());
}
}
}
……
创建一个SOAP连接,如上清单2所示。SAAJ客户机可以利用SOAP Connection Factory,通过创建SOAPConnection来建立点到点的同步连接。该连接提供了同步调用服务的方法。
其次,工厂还创建了消息自身:
清单3. 创建消息对象
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPBody;
public class DynamicInvokeInterce ... {
……
publicListinvokeOperation(Operationoperation)throwsException
try...{
SOAPConnectionFactorysoapConnFactory=
SOAPConnectionFactory.newInstance();
SOAPConnectionconnection=
soapConnFactory.createConnection();
MessageFactorymessageFactory=MessageFactory.newInstance();
SOAPMessagemessage=messageFactory.createMessage();
SOAPPartsoapPart=message.getSOAPPart();
SOAPEnvelopeenvelope=soapPart.getEnvelope();
SOAPBodybody=envelope.getBody();
connection.close();
…
}
如清单3所示,使用 MessageFactory 创建消息自身。这一消息已经包含了空的基本部分,比如 envelope 和 header 。SOAPPart 包含了 envelope ,而 envelope 又包含了主体。从而创建了对所需对象(比如 SOAPBody)的引用。
接着,填充 SOAPBody:
清单4. 填充主体
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
public class DynamicInvokeInterce ... {
publicstaticfinalStringXSI_NAMESPACE_PREFIX="xsi";
publicstaticfinalStringXSI_NAMESPACE_URI="http://www.w3.org/2001/XMLSchema-instance";
publicstaticfinalStringXSD_NAMESPACE_PREFIX="xsd";
publicstaticfinalStringXSD_NAMESPACE_URI="http://www.w3.org/2001/XMLSchema";
publicListinvokeOperation(Operationoperation)throwsException
try...{
...
SOAPPartsoapPart=message.getSOAPPart();
SOAPEnvelopeenvelope=soapPart.getEnvelope();
booleanisRPC=operation.getStyle().equalsIgnoreCase("rpc");
if(isRPC)
...{
//为envelope增加命名空间声明如果是RPC/encoded型
envelope.addNamespaceDeclaration(XSI_NAMESPACE_PREFIX,XSI_NAMESPACE_URI);
envelope.addNamespaceDeclaration(XSD_NAMESPACE_PREFIX,XSD_NAMESPACE_URI);
}
SOAPHeaderheader=envelope.getHeader();
header.detachNode();
SOAPBodybody=envelope.getBody();
body.addNamespaceDeclaration("",operation.getNamespaceURI());
StringtargetObjectURI=operation.getTargetObjectURI();
if(targetObjectURI==null)
...{
targetObjectURI="";
}
NamesvcInfo=envelope.createName(operation.getTargetMethodName(),"",targetObjectURI);
SOAPElementsvcElem=body.addChildElement(svcInfo);
//复杂类型的名称,需要添加子元素对应这个名称
if(operation.getComplextypename()!=null)...{
Namesvc=envelope.createName(operation.getComplextypename());
svcElem=svcElem.addChildElement(svc);
}
if(isRPC)
//判断是encoding还是literal
...{
svcElem.setEncodingStyle(operation.getEncodingStyle());
}
//填充主体
Documentdoc=XMLSupport.readXML(operation.getInputMessageText());
if(doc.hasRootElement())
...{
buildSoapElement(envelope,svcElem,doc.getRootElement(),isRPC);
}
StringsoapActionURI=operation.getSoapActionURI();
if(soapActionURI!=null&&soapActionURI.length()>0)
...{
MimeHeadersmimeHeaders=msg.getMimeHeaders();
mimeHeaders.setHeader("SOAPAction","""+operation.getSoapActionURI()+""");
}
message.saveChanges();
}
}
需要说明的是,SAAJ顾名思义,SOAPMessag可以有用于二