级别: 初级 Russell Butek (butek@us.ibm.com), 软件工程师, IBM
2004 年 3 月 01 日 JAX-RPC 支持带附件的 SOAP。本技巧描述了您可以如何使用 JAX-RPC API 来发送 MIME 附件。
SOAP 消息传递协议使您能够通过 SOAP 消息来发送 MIME 附件。WSDL
提供了对这些附件的一种描述。JAX-RPC 提供了附件的 WSDL 描述到 Java
构件的映射。本技巧描述了如何使用这些 JAX-RPC 映射来发送 SOAP
消息中的附件。
Web 服务描述语言(WSDL)
遗憾的是,附件的 WSDL
描述并不是特别直接的。看一下
清单 1 中的 WSDL
描述,尤其是绑定中突出显示的元素。
清单 1. 带附件的 WSDL
<?xml version="1.0" encoding="utf-8"?>
<definitions
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
targetNamespace="urn:attachment.tip"
xmlns:tns="urn:attachment.tip">
<message name="empty"/>
<message name="imageMsg">
<part name="image" type="xsd:hexBinary"/>
</message>
<message name="octetMsg">
<part name="octet" type="xsd:hexBinary"/>
</message>
<portType name="AttachmentTip">
<operation name="sendImage">
<input message="tns:imageMsg"/>
<output message="tns:empty"/>
</operation>
<operation name="sendOctet">
<input message="tns:octetMsg"/>
<output message="tns:empty"/>
</operation>
</portType>
<binding name="AttachmentBinding" type="tns:AttachmentTip">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="sendImage">
<soap:operation soapAction=""/>
<input>
<mime:multipartRelated>
<mime:part>
<soap:body use="literal"/>
</mime:part>
<mime:part>
<mime:content part="image" type="image/jpeg"/>
</mime:part>
</mime:multipartRelated>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="sendOctet">
<soap:operation soapAction=""/>
<input>
<mime:multipartRelated>
<mime:part>
<soap:body use="literal"/>
</mime:part>
<mime:part>
<mime:content part="octet" type="application/octet-stream"/>
</mime:part>
</mime:multipartRelated>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="AttachmentService">
<port name="AttachmentTip" binding="tns:AttachmentBinding">
<soap:address
location="http://localhost:9080/attachment/services/AttachmentTip"/>
</port>
</service>
</definitions> |
 |
WS-I 和附件
如左面的段落所描述的,MIME 类型不能通过 WSDL 的‘接口’所描述。WS-I
组织(请参阅
参考资料)正试图通过为所有的 MIME 类型引入一个新的 XML 类型: wsi:swaRef
来纠正(至少是部分地)这个缺点。但是在写这篇文章时,这个类型还在计划中,还没有标准的 Java 语言绑定用于它。
|
|
需要注意的第一件事就是在您为绑定做好了完全的准备之前是没有 MIME
信息的。如果您暂时忽略绑定,只考虑 WSDL 的‘接口’(portType 和消息),您将看到两个操作――
sendImage 和
sendOctet 。它们各自都有一个
hexBinary 输入。
hexBinary 映射到
byte[] 。仅仅使用这个信息,您就可以合理地猜测这个 portType 将映射到 Java 语言服务端点接口(Service Endpoint Interface,SEI),它将有两个方法,每个都有一个单独的
byte[]
参数。这是一个合理的猜测,对于所有的情况您都将是正确的(除了 MIME
类型之外)。但是对于 MIME
类型,您必须检查绑定来决定实参的类型。
在绑定中,您将看到用于操作的输入实际上是 MIME 内容类型――
image/jpeg 和
application/octet-stream ――而不是
hexBinary 。JAX-RPC
分别定义了这些类型来映射到
java.awt.Image 和
javax.activation.DataHandler 。
服务端点接口(Service Endpoint Interface)
清单 2
包含一个从
清单 1 中的 WSDL
生成的 Java 语言服务端点接口(SEI)。
清单 2. AttachmentTip SEI
package tip.attachment;
import java.awt.Image;
import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.activation.DataHandler;
public interface AttachmentTip extends Remote {
public void sendImage(Image image) throws RemoteException;
public void sendOctet(DataHandler octet) throws RemoteException;
} |
WSDL 的
sendImage 操作(由绑定显示它具有一个
image/jpeg 的 MIME 内容)成为一个带有
java.awt.Image 参数的 Java 应用程序的方法。非常直接。清楚得很。
表 1列出了所有很清楚的 MIME 类型到 Java 类型的映射。
表 1 - MIME 类型到 Java 类型的 JAX-RPC 映射
|
MIME 类型
|
Java 类型
| | image/gif, image/jpeg | java.awt.Image | | text/plain | java.lang.String | | multipart/* | javax.mail.internet.MimeMultipart | | text/xml, application/xml | javax.xml.transform.Source |
如果您定义了一个不在这个表格中的 MIME 类型,正如我在
sendOctet 操作中所作的,那么 JAX-RPC 实现将会把您的类型映射到
javax.activation.DataHandler 。
DataHandler
类型定义在 Java Activation Framework(JAF ―― 请查阅下面更多的详细资料,在
参考资料部分还有指向 JAF 页的一个链接)里。
客户端实现
从现在开始,我假设您已经使用了您所喜爱的 JAX-RPC 实现来从 AttachmentTip WSDL 生成所有的客户端映射。客户端实现将依赖于这些映射,尤其是来自
清单 2的 AttachmentTip SEI。
附件的客户端实现首先必须从一个服务中获得 SEI 的实现。您可以在
清单 3 中的
getTip
方法中看到这是怎样做的(关于
ServiceFactory 和
Service 的讨论超出了本技巧的范围,查阅
参考资料可以获得更多的信息)。
调用 sendImage 方法
一旦您有了 SEI 实现,您就可以和您的附件一起调用它的方法(在本例中它是在一个您提供了名称的文件中)。在
sendImage 情况下,那个附件就是
java.awt.Image 。对于它来说这就足够了。JAX-RPC 实现知道作为附件发送的所有映像,并且相应地构造 SOAP
消息。客户端的程序员甚至不必知道所包括的附件。查阅
清单 3
中突出显示的调用
sendImage 的代码。
清单 3. 调用 sendImage 的 AttachmentTip 客户端实现
package tip.attachment;
import java.awt.Image;
import java.awt.Toolkit;
import java.net.URL;
import java.rmi.RemoteException;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class AttachmentTipClient {
static AttachmentTip getTip() throws Exception {
QName serviceName = new QName(
"urn:attachment.tip",
"AttachmentService");
URL wsdlLocation = new URL(
"http://localhost:9080/attachment/services/AttachmentTip?wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(wsdlLocation, serviceName);
QName portName = new QName("", "AttachmentTip");
return (AttachmentTip) service.getPort(
portName,
AttachmentTip.class);
}
static void sendImage(AttachmentTip tip, String fileName)
throws RemoteException {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Image image = toolkit.createImage(fileName);
tip.sendImage(image);
}
public static void main(String[] args) {
try {
AttachmentTip tip = getTip();
sendImage(tip, args[0]);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
 |
关于映射非特定的 MIME 类型的警告
JAX-RPC 不需要一个实现来支持未在
表 1 中出现的 MIME 类型。实现不能将未指明的 MIME 类型映射到
DataHandler 。例如,WebSphere Web 服务直到 Application Server 的 6.0 版本才支持这种映射。
|
|
调用 sendOctet 方法
对于那些不多的已经由 JAX-RPC 为其定义了映射的 MIME 类型来说(查阅表 1),用起来是相当容易的。对于那些没有清楚的 JAX-RPC 映射的 MIME 类型,您可以使用
javax.activation.DataHandler 对象。这个类是 Java Activation Framework (JAF ――
请参见
参考资料)的一部分,所以您不得不了解 JAF,但是它不是太难。
DataHandler 包含一个
javax.activation.DataSource ,而它依次包含输入流和输出流。大部分 Java 编程类型能够相当容易地转换到流或者由流转换过来,所以这是相当直接的。而且,甚至激活框架本身都有些用处。如果您的附件数据是在一个文件中,正如这个例子,激活框架提供了
javax.activation.FileDataSource 类,所以您可以绕过流这个步骤。使用
清单 4是
清单 3 中的代码外加一个新的方法来调用
sendOctet 并传递一个
DataHandler 给它。
清单 4. 完整的 AttachmentTip 客户端实现
package tip.attachment;
import java.awt.Image;
import java.awt.Toolkit;
import java.net.URL;
import java.rmi.RemoteException;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class AttachmentTipClient {
static AttachmentTip getTip() throws Exception {
QName serviceName = new QName(
"urn:attachment.tip",
"AttachmentService");
URL wsdlLocation = new URL(
"http://localhost:9080/attachment/services/AttachmentTip?wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(wsdlLocation, serviceName);
QName portName = new QName("", "AttachmentTip");
return (AttachmentTip) service.getPort(
portName,
AttachmentTip.class);
}
static void sendImage(AttachmentTip tip, String fileName)
throws RemoteException {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Image image = toolkit.createImage(fileName);
tip.sendImage(image);
}
static void sendOctet(AttachmentTip tip, String fileName)
throws RemoteException {
FileDataSource fds = new FileDataSource(fileName);
DataHandler dh = new DataHandler(fds);
tip.sendOctet(dh);
}
public static void main(String[] args) {
try {
AttachmentTip tip = getTip();
sendImage(tip, args[0]);
sendOctet(tip, args[0]);
}
catch (Throwable t) {
t.printStackTrace();
}
}
} |
请注意,我使用同一个文件作为数据用于这两个附件操作,但是直到
sendOctet 被连接,文件的内容只不过是
application/octet-stream 。它并不知道,或者说不关心,它其实是一个映像。
application/octet-stream 是用于
FileDataSource 的缺省内容类型,这正是我使用它的原因。它为我做了一切。如果您没那么幸运,您的数据没有在文件中,您就不得不将它作为不同于
application/octet-stream
的某种形式来发送,那么您就不得不创建您自己的
DataSource 的实现,但是它是一个简单的接口,所以它不应该太费力。(这留给读者作为练习!)
总结
正如我已经展示的,通过 JAX-RPC 映射发送附件是一个相当简单的问题。应用程序的程序员根本就不必知道附件。在最坏的情况下,程序员必须知道
DataHandler 、
DataSource 和流(stream)。
参考资料
关于作者  | |  | Russell Butek 是 IBM WebSphere Web 服务引擎的开发人员之一。他也是 JAX-RPC Java Specification Request (JSR) 专家组的 IBM 代表。他从事 Apache 的 AXIS SOAP 引擎的实现方面的研究,推动了 AXIS 1.0 遵循 JAX-RPC 1.0。以前,他是 IBM CORBA ORB 的开发人员和许多 OMG 特别工作组的 IBM 代表:包括可移植拦截器特别工作组(他是这个特别工作组的主席)、核心特别工作组以及互操作性特别工作组。 |
对本文的评价
|