2003-01-08 10:00
EJB 2.1中实现Web Service
EJB2.1(Enterprise JavaBeans 2.1)正式推荐版本已经出台,它产生的动力主要是Enterprise JavaBeans对支持Web Service的需求,同时也是因为Microsoft .NET的发布,它对J2EE或者说对Java技术来说已经构成了巨大威胁。在新版本的EJB2.1中,主要的变化主要集中在基于SOAP和WSDL的Web Service上。EJB已经成为一种新的Web Service平台。它对Web Service的支持主要体现在三个新的Web Service API上:分别是JAX-RPC(Java API for XML-RPC,它基本上是通过SOAP实现的Java RMI,为RPC格式的SOAP消息提供远端接口)、SAAJ(SOAP API with Attachments for Java,它模仿SOAP消息的结构,同时也有功能有限的消息分发能力)和JAXM(Java API for XML Messaging,它类似于JMS,提供发送和接收SOAP消息的消息架构),利用它们可以实现与其它类型的Web Service进行通讯,而且还允许无序的会话Bean和消息驱动的Bean来作为Web Service使用,使它们能够被任何与 SOAP1.1兼容的客户端所访问。例如:使用SOAP,我们就可以从其它平台Web Service来调用无序的会话Bean的方法,象微软的.NET,Perl,Apache Axix和其他的语言和平台。EJB2.1中新的Web Service功能能够提供一种前所未有的跨平台互操作性,它主要是建立在两个崭新的J2EE SOAP工具包JAX-RPC和JAXM。
Web Service代表了分布式计算的最新潮流,可能是自1995年Java的出现和1998年XML出现以来最重要的技术了。其实,给Web Service下一个准确的定义是很难的,因为Web Service并不是任何特殊技术或者平台所特有的,Web Service是一种网络应用程序,以XML形式的文档,使用SOAP和WSDL进行信息交换。要更好地理解这句话的含义,你必须先理解SOAP和WSDL,下面是有关这方面的定义:
SOAP:简单对象访问协议(Simple Object Access Protocol),是在W3C的支持下,由Microsoft,IBM和其他公司开发的,基于XML格式的一种协议,它是可伸缩和可扩展的,不象以前的DCE RPC, CORBA IIOP, Java RMI-JRMP以及DCOM,它已经被几乎所有的开发厂商所认可和接受。
WSDL:Web服务描述语言(Web Service Description Language),也是在W3C的支持下,由Microsoft,IBM和其他公司开发的,XML格式的语言,用来对Web Service进行描述,包括期望的消息格式类型、所使用的Internet协议和Web Service的Internet地址。
其实,Web Service代表了一种新的分布式对象技术,它和CORBA IIOP和Java RMI很相似,但也有许多差异,最大的差异应该就是真正的平台无关性。尽管Java RMI和CORBA IIOP都声称自己是平台无关的,但实际上,它们都需要它们自己的平台。要使用Java RMI,你需要一个Java虚拟机和Java编程语言,对使用其它语言的开发者如Visual Basic或C++来说,Java RMI并不是平台无关的。CORBA IIOP也是有局限性的,IIOP协议通常需要一个特定的架构如CORBA ORB,也只有少数几个厂商支持CORBA。另一方面,Web Service着重描述信息交换的协议,而不是着重描述对这些协议的实现,换句话说,你可以用任何语言,在任何平台上,以任何你自己喜欢的方式来创建Web Service。
Web Service另外一个好处就是,不象其它的分布式对象体系,它建立在现有的技术架构的基础之上,因此大多说厂商很容易实现。SOAP和WSDL都是基于XML的,而XML已经被广泛支持,XML解析器在几乎每一种开发语言中都有,因此,处理SOAP消息和WSDL文档的基础已经存在了。此外,Web Service消息通常是通过TCP/IP进行交换的,也已经被几乎所有的平台和语言所支持。
JAX-RPC和EJB
JAX-RPC(Java API for XML-RPC)实质上就是通过SOAP访问的Java RMI。它和“本地的”Java RMI (Java RMI-JRMP)和Java RMI-IIOP很象,但是它是以SOAP作为通讯协议。要实现JAX-RPC,最低要求是必须对通过HTTP访问的SOAP支持RPC编码,但是,我们仍然可以提供对其他编码方式、消息格式和Internet协议的支持。JAX-RPC能够被用来从会话、实体和消息驱动的Bean来调用Web Service的操作。JAX-RPC能够用来访问其它平台的Web Service。例如:一个无序的会话Bean可能会使用JAX-RPC来调用.NET Web Service的方法。如下图所示:
每个EJB开发商都会提供自己对JAX-RPC的实现,但它们之间的差别是很小的,主要是因为所有的实现都必须遵照JAX-RPC规范,JAX-RPC能够当作客户端API来访问其它的Web Service,但是,它同时也是一个被称作“EndPoint接口”的新型企业Bean接口。当JAX-RPC当作客户端API的时候,会话、实体或者消息驱动的Bean能够使用它与其它平台的Web Service交换消息。JAX-RPC定义了三种编程模型:Generated Stub、Dynamic Proxy和DII(Dynamic Invocation Interface),我们在EJB环境中常用的Generated Stub模型。
如果使用JAX-RPC去访问Web Service,那么,这个Web Service必须发行一个WSDL文档,EJB开发商提供的JAX-RPC工具包产生Java RMI接口和实现WSDL文档所描述的Web Service操作的Stub,一旦Stub和接口创建出来之后,我们就可以把它们与企业Bean的JNDI ENC(Environment Naming Context)进行绑定,然后与 Web Service进行通信。
WSDL把访问Web Service的接口描述成“端口”,每个端口有一个或者多个“操作”,端口和操作的概念和Java的接口和方法类似。实际上,JAX-RPC定义了WSDL和Java RMI之间的映射关系,它产生来自端口的远端接口,并带有响应端口操作的方法。例如:一个WSDL文档可能描述一个被称作“BookPrice”、并带有单个操作getBoolPrice的端口,下面就是BookPrice WSDL文档的一些简单代码:
<?xml version="1.0"?>
<definitions name="BookPrice"
targetNamespace="http://lucky.myrice.com/GetBookPrice"
xmlns:tns="http://lucky.myrice.com/GetBookPrice"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<!-- 描述参数和返回值的message元素 -->
<message name="IsbnMessage">
<part name="isbn" type="xsd:string" />
</message>
<message name="PriceMessage">
<part name="price" type="xsd:float" />
</message>
<!-- 描述Web Service抽象接口的portType元素 -->
<portType name="BookPrice">
<operation name="getBookPrice">
<input name="isbn" message="tns:IsbnMessage"/>
<output name="price" message="tns:PriceMessage"/>
</operation>
</portType>
<!-- 在这里进行绑定 -->
<!-- service元素告诉我们Web Service的地址 -->
<service name="BookPriceService">
<port name="BookPrice" binding="tns:BookPrice_Binding">
<soap:address location="http://lucky.myrice.com/BookPrice" />
</port>
</service>
</definitions>
在部署的时候,JAX-RPC
Stub生成工具会把WSDL端口转换成远程接口和Stub,端口和服务Stub可能是下面的样子:
public Interface BookPriceService extends
javax.xml.rpc.Service{
public BookPrice getBookPrice( ) throws RemoteException;
}
public Interface BookPrice extends java.rmi.Remote
{
public float getBookPrice(String isbn)
throws RemoteException;
}
这里只是一个简单的例子,这个服务只有一个端口,而实际上一个服务会有多个端口,每个端口有相应的接口和Stub。一旦接口和Stub产生并被绑定到JNDI
ENC之后,它们就可以在运行期调用Web Service的“操作”了,在下面的无序会话Bean里,BookCatalog EJB利用JAX-RPC从.NET
Web Webvices查找一本书的批发价格。
public class BookCatalog implements javax.ejb.SessionBean
{
...
public float getWholeSalePrice(String isbn) {
try {
InitialContext jndiContext = new InitialContext (
);
BookPriceService service =
jndiContext.lookup("java:comp/env/service/BookPriceService");
BookPrice bookPrice_port =
service.getBookPrice();
float price = bookPrice_port.getBookPrice( isbn
);
return price;
catch(RemoteException re){
}catch(ServiceException se){
}catch(NamingException ne){
}
}
...
}
当调用getBookPrice()方法时,JAX-RPC Stub向.NET Web
Service发送SOAP信息,Stub产生的SOAP信息可能会是下面的样子:
<?xml version='1.0' ?>
<env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xyz='http://lucky.myrice.com/BookPrice"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<Body>
<xyz:getBookPrice>
<isbn xsi:type="string">1565928695
</xyz:getBookPrice>
</Body>
</env:Envelope>
.NET Erb
Services处理SOAP信息,并把结果返回到Stub,Stub分析结果,最后向客户端发送最终结果。
JAX-RPC Stub中的方法可以有参数,参数类型可以是基本数据类型,如int,long等;基本包装类型,如java.lang.Interger,java.lang.Long等;数组;Java标准类型,如String,Date等;也可以是自定义对象类型。自定义对象必须符合JAX-RPC规范的规则。
除了产生Stub外,JAX-RPC也支持动态代理服务,除了它的远程接口和Stub的实现是在运行时动态产生的之外,动态代理服务的作用和Stub一样。下面的例子就是JAX-RPC产生动态Stub的:
public class BookCatalog implements javax.ejb.SessionBean
{
...
public float getWholeSalePrice(String isbn) {
try {
InitialContext jndiContext = new InitialContext ( );
javax.xml.rpc.Service service =
jndiContext.lookup("java:comp/env/service/DynamicService");
BookPrice bookPrice_port = service.getPort(BookPrice.class);
float price = bookPrice_port.getBookPrice( isbn );
return price;
catch(RemoteException re){}
catch(ServiceException se){}
catch(NamingException ne){}
}
...
}
在运行时,getPort()方法自动把BookPrice接口映射到WSDL文档里定义的相应端口,然后产生Stub实现接口的工作。
JAX-RPC还支持名为DII(Dynamic Invocation Interface)的动态API,DII允许开发人员在运行时调用SOAP方法。如果你使用过CORBA Dynamic Invocation Interface的话,那你对JAX-RPC DII一定很容易理解。JAX-RPC DII类似于Java的反射(Reflection),它允许你以方法的形式得到一个代表Web Service操作的对象的参考,调用那个方法,就无需再访问Service Factory或者再使用Stub和远端接口。下面的例子就是企业Bean访问BookPrice端口的getBookPrice()操作:
public class BookCatalog implements javax.ejb.SessionBean
{
...
public float getWholeSalePrice(String isbn) {
try {
InitialContext jndiContext = new InitialContext ( );
javax.xml.rpc.Service service =
jndiContext.lookup("java:comp/env/service/DynamicService");
QName port = new QName("http://lucky.myrice.com/GetBookPrice ","BookPrice");
QName operation = new QName("http://lucky.myrice.com/GetBookPrice",
"getBookPrice");
Call callObject = service.createCall(port, operation);
Object [] args = new Object[1]; args[0] = isbn;
Float price = (Float) callObject.invoke( args );
return price.floatValue();
}
catch(JAXRPCException se){}
catch(NamingException ne){}
}
}
...
}
实际上,你可以在运行期配置参数、类型、编码等,你能用WSDL配置的所有信息都可以用DII动态配置。
JAX-RPC另外还是一个称为终端接口(Endpoint Interface)的新型组件,这个新接口允许我们把无序的会话Bean作为Web Service来实现,这个终端接口简化了javax.rmi.Remote接口的实现,并且遵守JAX-RPC规范中的规则。把一个无序的会话Bean作为Web Service来实现是非常简单的:只需定义Bean类和远端接口,然后使用开发商的提供的工具来实现。一旦建立了Web Service,它的方法就能够被任何SOAP兼容的、来自任何语言和平台的工具包来调用,比如:.NET,Perl,Apache Axis,C,C++等等。如下图所示:
由于JAX-RPC仅仅是Java RMI的另外一种形式,因此,利用它访问企业Bean是很自然的,我们以前已经利用RMI-IIOP和RMI访问过。利用JAX-RPC与EJB进行通信意味着EJB可以当作Web Service来使用,至少无序的Bean是可以的。EJB2.1允许我们利用JAX-RPC,但只能是应用于无序的Bean,这主要是因为SOAP是一种无序的消息协议,它没有对象识别的概念,因此它不能应用在有序的和实体的Bean中。
EJB2.1为无序的Bean定义了一个新的Web Service终端(EndPoint)接口,Web Service界的人使用“终端”来称呼发送和接收SOAP信息的任何东西。在EJB中,终端就是一个无序的会话Bean,它可以通过SOAP来访问,并且遵照JAX-RPC规范中定义的从Java-to-WSDL到Java-to-SOAP的映射规则。使用JAX-RPC最为EJB终端的基础是顺理成章的,因为JAX-RPC规范中定义了SOAP消息和Java方法调用以及从Java远程接口产生WSDL文档的详细规则。
不象EJB开发者已经很熟悉的远端和本地接口,终端接口并没有继承EJB对象类型,如EJBObject或EJBLocalObject。相反,终端接口直接继承了javax.ejb.Remote接口。例如:在上面的例子中的BookPrice的Web Service能够很轻易实现为EJB中的终端。下面的代码说明了一个BookPrice的终端接口,和实现为Web Service的无序会话Bean的部分列表。
public interface BookPrice extends javax.rmi.Remote
{
public String getBookPrice(String isbn) throws javax.rmi.RemoteException;
}
public class BookPriceWS implements BookPrice,
javax.ejb.SessionBean {
public float getBookPrice(String isbn){
Connection con = null;
Statement stmt = null;
ResultSet rs;
try {
DataSource ds = jdniEnc.lookup("java:comp/env/jdbc/DataSource");
con = ds.getConneciton();
stmt = con.createStatement();
rs = stmt.executeQuery("SELECT wholesale FROM CATALOG WHERE isbn = \'"
+isbn+"\'");
if(rs.next()){
float price = rs.getFloat("wholesale");
return price;
}else{
return 0;
}
}
catch (SQLException se) {
file://处理异常
}
}
...
}
终端接口比远程和本地接口一个很明显的好处就是它不会带来象EJBObject或者EJBLocalObect无用方法等形式的额外负担,此外,终端接口没有包括home接口,SOAP不支持按引用传值。因此,你不能要求一个Web
Services接口(home 接口)按引用传递到另外一个远端接口,更进一步讲,你不能创建或移除一个Web Service。
当我们把一个无序会话 Bean开发成Web Service时,首先定义一个终端接口,然后利用它产生JAX-RPC的客户端Stub和WSDL文档或者如果你产生JAX-RPC客户端Stub,无需做任何改变,你可以把它包装成J2EE客户端JAR,利用它去访问无序会话Bean,利用SOAP做通讯协议。如果你从终端接口产生WSDL文档,其它的SOAP工具包也能够使用这个文档去访问你的无序Bean。WSDL和SOAP是Web Service的基础,因此,为EJB Web Services发布WSDL可以实现与其它平台的交互。
SAAJ
SAAJ(SOAP with Attachments API for Java)是一个基于API的SOAP工具包,它定义了SOAP Messages with Attachments (SwA)和SOAP用的MIME信息格式。Java开发人员能够利用SAAJ来创建、读取或者修改SOAP信息。这个API包含许多类和接口,用来定义SOAP元素(Envelope, Body, Header, Fault等),XML名称空间,属性,文字节点以及MIME附件。你可以使用SAAJ操作简单的、没有附件的XML格式的SOAP信息,也可以操作更加复杂的、带Mime附件的SOAP信息。SAAJ可以与JAX-RPC结合使用,但也可以单独使用,它有自己的、通过HTTP1.1实现的请求/应答方式的消息机制。
SAAJ是基于Abstract Factory模式的。SAAJ是类型的抽象集合,每一种类型的对象都是由SAAJ集合中另外的对象产生的。在Abstract Factory的SAAJ实现中,MessageFactory类是根,它负责创建自己的实例,反过来创建SOAPMessage,SOAPMessage包含SOAPPart,它代表SOAP文档、0个和多个AttachmentPart(代表附件的对象,如GIF,PDF等)。SOAPPart包含SOAPEnvelope、SOAPBody、SOAPHeader和其它类型的对象。
要迅速了解SAAJ,我们先用XML格式建立一个简单的SOAP信息,如下所示:
<?xml version='1.0' ?>
<env:Envelope
xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:book='http://lucky.myrice.com/BookPrice'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
<env:Body>
<book:getBookPrice>
<isbn xsi:type='string'>1565928695
</book:getBookPrice>
</env:Body>
</env:Envelope>
而下面是用SAAJ创建的例子:
import javax.xml.soap.*;
public class Example_1 {
public static void main(String [] args) throws SOAPException{
MessageFactory msgFactory =
MessageFactory.newInstance();
SOAPMessage message = msgFactory.createMessage();
SOAPPart soap = message.getSOAPPart();
SOAPEnvelope envelope = soap.getEnvelope();
envelope.getHeader().detachNode();
SOAPBody body = envelope.getBody();
SOAPElement getBookPrice = body.addChildElement(
"getBookPrice","book",
"http://lucky.myrice.com/BookPrice");
getBookPrice.setEncodingStyle(SOAPConstants.URI_NS_SOAP_ENCODING);
SOAPElement isbn = getBookPrice.addChildElement("isbn");
isbn.addNamespaceDeclaration("xsi",
"http://www.w3.org/2001/XMLSchema-instance");
Name xsiType = envelope.createName("type","xsi",
"http://www.w3.org/2001/XMLSchema-instance");
isbn.addAttribute(xsiType,"string");
isbn.addTextNode("1565928695");
message.writeTo(System.out);
}
}
如果我们对比上面的例子,就会发现SAAJ定义了SwA消息格式的准确结构,SOAP消息的属性和SAAJ类型之间可以发现是一一对应的关系:Envelope对应SOAPEnvelope;Body对应SOAPBody;getBoolPrice对应SOAPBodyElement;isbn对应SOAPElement(Text)等。
当然,上面的例子是很简单的,它并没有包含任何附件,SwA中的附件通常是二进制或者是其它的非XML格式的文件,它通常被SOAP文档引用。SAAJ靠JAF( Java Activation Framework)添加附件,并处理对象和数据流之间的相互转换。下面的这段示例代码利用SAAJ向SOAP附加一个PDF文件。
MessageFactory msgFactory =
MessageFactory.newInstance();
SOAPMessage message = msgFactory.createMessage();
AttachmentPart pdfAttach = message.createAttachmentPart();
FileDataSource file = new FileDataSource("TestSAAJAttachment.pdf");
DataHandler pdfDH = new DataHandler(file);
pdfAttach.setDataHandler(pdfDH);
要成功处理附件,我们必须先理解JAF,JAF是处理MIME数据的极好框架,但它也有自己的局限性,大部分局限性与使用DataContentHandler类型有关。藏在JAF的局限性后面的细节问题是很复杂的,但我们可以使用JAF
DataSource类型来克服大部分的局限性,就象例子中的javax.activation.FileDataSource那样。
SAAJ是一个用途广泛的API,可以让我们在几乎任何的应用中创建SwA消息,在某些情况下,我们可以与JAX-RPC一起在文字消息和SOAP消息中使用SAAJ,但也可以与其它的API如JavaMail,JMS,JAXM一起使用。如果Java应用已经创建了SwA消息,它仍可以使用SAAJ把这个消息转换成二进制流,然后与使用其它API的Web Services进行交换信息。
JAXM 和 EJB
JAXM (Java API for XML Messaging)是和(Java Message Service)类似的SOAP消息API,就象JMS是一种通过面向消息的中间件来发送和接收消息的API一样,JAXM是一种通过Web Service来发送和接收消息的API。
JAXM是面向文档的,它把SOAP消息以XML格式的文档来进行传送的。JAXM客户端都是使用SAAJ(SOAP with Attachments API for Java)来装配、接收和使用SOAP消息的,SAAJ模仿SOAP消息的实际XML结构,这种机制与JAX-RPC是有很大区别的,JAX-RPC使用方法调用的语法,把SOAP消息隐藏到了Java RMI代理的后面。使用JAX-RPC,你 所看到的是仅仅由方法、参数、返回值组成的远端接口,而使用JAXM,你直接处理SOAP协议。象JAX-RPC一样,JAXM必须与SOAP兼容的Web Service交换SOAP信息。例如:一个企业Bean可能使用JAXM与用Perl写的Web Service交换SOAP信息。EJB2.1开发者能够以JAXM作为一种新的消息驱动Bean的基础,我们称它为基于JAXM的消息驱动Bean(JAXM-MDB)。JAXM-MDB使用的是SOAP消息,但扮演的是Web Service的角色。JAXM-MDB能够实现单向的异步接口,就象基于JMS的消息驱动的Bean,也可以实现请求/应答式的同步接口。
JAX-RPC和JAXM都允许企业Bean访问不同平台上的Web Service,也可以作为用无序和消息驱动Bean建立的Web Service的基础。但是,由于缺乏厂商的支持,JAXM的使用前景并不是很乐观。