在本技巧中,作者和开发人员 Nicholas Chase 向您展示了如何使用 SOAP with Attachments API for Java (SAAJ),从而简化创建和发送SOAP消息的过程。
Web 服务的基础是以标准格式发送和接收消息,这样所有系统都可以理解消息。通常情况下,这种标准格式是 SOAP。SOAP 消息可以手工生成和发送,但 SOAP with Attachments API for Java (SAAJ)—— Java API for XML Messaging (JAXM)的一个分支——能够使许多必需的步骤变得自动化,例如创建连接,或者创建和发送实际消息。本技巧介绍了同步 SOAP 消息的创建和发送。
该过程包括 5 个步骤:
1. 创建 SOAP 连接
2. 生成 SOAP 消息
3. 填充消息
4. 发送消息
5. 检索响应
SAAJ 是 Java Web Services Developer Pack 1.2 的一部分(参见 参考资料)。这个软件包还包含了 Tomcat Web 服务器(因此您就可以建立自己的服务)和例子应用程序。
安装 Java Web Services Developer Pack 1.2 是很容易的——只要通过包含的 Tomcat Web 服务器发送消息即可。要通过独立的应用程序发送消息,像我这里所做的一样,需要采取以下步骤:
- 从网址 http://java.sun.com/webservices/downloads/webservicespack.html下载 JWSDP1.2。
- 将软件安装到相应目录中。
- 如果您正使用 Java 1.4,需要用下载的软件覆盖相关的 XML 类。创建以下目录:
<JAVA_HOME>/jre/lib/endorsed
并复制目录<JWSDP_HOME>/jaxp/lib/endorsed
中的文件到该目录下。(JAVA_HOME和JWSDP_HOME 分别表示 Java 安装以及 JWSDP 安装的目录。) - 将下列文件加入 classpath 中:
- <JWSDP_HOME>/saaj/lib/saaj-api.jar
- <JWSDP_HOME>/saaj/lib/saaj-impl.jar
- <JWSDP_HOME>/jwsdp-shared/lib/commons-logging.jar
- <JWSDP_HOME>/jwsdp-shared/lib/mail.jar
- <JWSDP_HOME>/jwsdp-shared/lib/activation.jar
- <JWSDP_HOME>/jaxp/lib/endorsed/dom.jar
- <JWSDP_HOME>/jaxp/lib/endorsed/xercesImpl.jar
- <JWSDP_HOME>/jaxp/lib/endorsed/sax.jar
- <JWSDP_HOME>/jaxp/lib/endorsed/xalan.jar
现在您就可以使用独立的程序从自己系统的任意位置发送消息了。
首先来看消息自身的结构。一条基本的 SOAP 消息由带有两个主要部分的信封(envelope)构成:头部和主体。应用程序确定如何使用这些部分,但整个消息必须遵循特定的 XML 结构,例如:
清单1. 一条示例 SOAP 消息
<SOAP-ENV:Envelope xmlns: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:Header /> <SOAP-ENV:Body> <ns1:getPrice xmlns:ns1="urn:xmethods-BNPriceCheck" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <isbn xsi:type="xsd:string">0672324229</isbn> </ns1:getPrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
这里,头部是空的,而主体包含了有效信息,或要传递的消息。在本例中,它是请求某本书价格的消息。
注意消息的结构。Envelope 包含 Header 和 Body 元素,这三者都是http://schemas.xmlsoap.org/soap/envelope/ namespace的一部分。应用程序使用 SOAPConnection 来发送消息。
第一步是要创建总体类和连接:
清单2. 创建连接
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPConnection;
public class SOAPTip {
public static void main(String args[]) {
try {
//First create the connection
SOAPConnectionFactory soapConnFactory =
SOAPConnectionFactory.newInstance();
SOAPConnection connection =
soapConnFactory.createConnection();
//Close the connection
connection.close();
} catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
|
应用程序可以直接使用 SOAPConnnection 或间接使用消息提供者来发送 SOAP 消息,现在 SOAPConnection 已经是 SAAJ 包的一部分了,而消息提供者仍然属于 JAXM 包。在本例中,应用程序使用工厂创建 SOAPConnection 对象。
工厂还创建了消息自身:
清单3. 创建消息对象
import javax.xml.soap.SOAPConnectionFactory; 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 SOAPTip { public static void main(String args[]) { try { //First create the connection SOAPConnectionFactory soapConnFactory = SOAPConnectionFactory.newInstance(); SOAPConnection connection = soapConnFactory.createConnection(); //Next, create the actual message MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(); //Create objects for the message parts SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPBody body = envelope.getBody(); //Close the connection connection.close(); } catch(Exception e) { System.out.println(e.getMessage()); } } } |
首先,使用 MessageFactory 创建消息自身。这一消息已经包含了空的基本部分,比如 envelope
和 header
。SOAPPart 包含了 envelope
,而 envelope
又包含了主体。创建对所需对象(比如 SOAPBody)的引用。
接着,填充 SOAPBody:
清单4. 填充主体
... import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; public class SOAPTip { public static void main(String args[]) { try { ... //Create objects for the message parts SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPBody body = envelope.getBody(); //Populate the body //Create the main element and namespace SOAPElement bodyElement = body.addChildElement(envelope.createName("getPrice" , "ns1", "urn:xmethods-BNPriceCheck")); //Add content bodyElement.addChildElement("isbn").addTextNode("0672324229"); //Save the message message.saveChanges(); //Check the input System.out.println("//nREQUEST://n"); message.writeTo(System.out); System.out.println(); //Close the connection connection.close(); } catch(Exception e) { System.out.println(e.getMessage()); } } } |
SOAP 消息的主体就好像是另一个 XML 元素,可以在其中添加孩子元素,比如 getPrice。然后就可以像处理典型的 DOM 元素一样,为它添加 isbn 元素和文本节点。
使用 SAAJ 还有可能使用外部文件来直接创建消息的 SOAPPart。例如,第一个程序清单中,prepped.msg 文件包含了 XML 结构,在手工建立文档的地方可以调用该文件:
清单5. 从一个外部文件创建消息
... import javax.xml.soap.SOAPElement; import java.io.FileInputStream; import javax.xml.transform.stream.StreamSource; public class SOAPTip { public static void main(String args[]) { ... //Create objects for the message parts SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPBody body = envelope.getBody(); //Populate the Message StreamSource preppedMsgSrc = new StreamSource( new FileInputStream("prepped.msg")); soapPart.setContent(preppedMsgSrc); //Save the message message.saveChanges(); ... } } |
通常,StreamSource 类用作 XSL Transformation 的一部分,但在这儿可以简单地使用它来获得 FileInputStream。结果是一个 SOAP 消息已经就绪,可以发送了。
对于同步消息,发送 SOAP 消息,并接收在同一步中发生的响应:
清单6. 发送消息
...
public class SOAPTip {
public static void main(String args[]) {
...
//Check the input
System.out.println("//nREQUEST://n");
message.writeTo(System.out);
System.out.println();
//Send the message and get a reply
//Set the destination
String destination =
"http://services.xmethods.net:80/soap/servlet/rpcrouter";
//Send the message
SOAPMessage reply = connection.call(message, destination);
//Close the connection
connection.close();
...
}
}
|
实际的消息是使用 call()方法发送的,该方法接收消息本身和目的地作为参数,并返回第二个 SOAPMessage 作为响应。在早期版本的 JAXM 中,目的地必须是一个 Endpoint 对象或一个 URLEndpoint,但现在它只要是一个对象就可以了。这个例子使用了由 XMethods 支持的“书价检验程序” Web 服务,该服务返回请求中给定 ISBN 的书的价格。
call()方法将会阻塞,直到它接收到返回的 SOAPMessage 为止。
相应地,返回的 SOAPMessage 是一个与发送消息具有相同形式的 SOAP 消息,这样该消息操作时就可以像另一个 XML 消息一样。SOAP 允许直接使用 XSLT 来转换响应:
清单7.读取响应
... import javax.xml.transform.TransformerFactory; import javax.xml.transform.Transformer; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamResult; public class SOAPTip { public static void main(String args[]) { try { ... //Send the message SOAPMessage reply = connection.call(message, destination); //Check the output System.out.println("//nRESPONSE://n"); //Create the transformer TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); //Extract the content of the reply Source sourceContent = reply.getSOAPPart().getContent(); //Set the output for the transformation StreamResult result = new StreamResult(System.out); transformer.transform(sourceContent, result); System.out.println(); //Close the connection connection.close(); ... } } |
像在任意 XSLT 应用程序中一样,创建 Transformer 对象。在本例中,只需要输出内容,因此没有样式表。这里,内容本身就是消息的整个 SOAP 部分(与 SOAP 消息本身相反,它可能包含附件)。您也可以在处理前提取信封和主体。在本例中,结果就是 System.out,但一般情况下,可以是一次转换可用的任意选择。像平常一样地进行转换。
图1. SOAP 请求和响应
这个简单的应用程序仅仅输出所接收到的消息,但是您可以同样简单地从 XML 文档中提取信息。同样,虽然这一技巧仅演示了消息的同步发送和接收,但是 JAXM API 也允许用于同步传送的消息提供者的使用,只是通过使用 ProviderConnection 对象而不是 SOAPConnection 进行。提供者保持消息,直到该消息成功传送为止。
JAXM 也允许配置文件的使用,配置文件可以简化特定 SOAP 消息的创建,比如 SOAP-RP 或 ebXML 消息。