Java API使得手工生成和发送消息时许多必需的步骤变得自动化

: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息

级别: 中级

Nicholas Chase (mailto:nicholas@nicholaschase.com?subject=使用SAAJ发送和接收SOAP消息&cc=nicholas@nicholaschase.com), 总裁, Chase & Chase, Inc.

2003 年 9 月 01 日

在本技巧中,作者和开发人员 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 服务器发送消息即可。要通过独立的应用程序发送消息,像我这里所做的一样,需要采取以下步骤:

  1. 从网址 http://java.sun.com/webservices/downloads/webservicespack.html下载 JWSDP1.2。
  2. 将软件安装到相应目录中。
  3. 如果您正使用 Java 1.4,需要用下载的软件覆盖相关的 XML 类。创建以下目录:
    <JAVA_HOME>/jre/lib/endorsed
    并复制目录
    <JWSDP_HOME>/jaxp/lib/endorsed
    中的文件到该目录下。(JAVA_HOME和JWSDP_HOME 分别表示 Java 安装以及 JWSDP 安装的目录。)
  4. 将下列文件加入 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

现在您就可以使用独立的程序从自己系统的任意位置发送消息了。


: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
回页首


SOPA 消息的结构

首先来看消息自身的结构。一条基本的 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 来发送消息。


: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
回页首


创建连接和消息

第一步是要创建总体类和连接:


清单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 创建消息自身。这一消息已经包含了空的基本部分,比如 envelopeheader 。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 消息已经就绪,可以发送了。


: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收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 为止。


: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
回页首


响应

相应地,返回的 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 请求和响应
: 使用SAAJ发送和接收SOAP消息

: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
: 使用SAAJ发送和接收SOAP消息
回页首


接下来的步骤

这个简单的应用程序仅仅输出所接收到的消息,但是您可以同样简单地从 XML 文档中提取信息。同样,虽然这一技巧仅演示了消息的同步发送和接收,但是 JAXM API 也允许用于同步传送的消息提供者的使用,只是通过使用 ProviderConnection 对象而不是 SOAPConnection 进行。提供者保持消息,直到该消息成功传送为止。

JAXM 也允许配置文件的使用,配置文件可以简化特定 SOAP 消息的创建,比如 SOAP-RP 或 ebXML 消息。


有时候需要在soapheader中传输一些数据,代码如下:
: 使用SAAJ发送和接收SOAP消息 if(getHasSoapHeader())
            }


相应的.net 读取soap header 的代码,webservice代码:

相关文章: