|
JAXB
Java Architecture for XML Binding
(JAXB)是一种Java技术,通过这种技术你可以从XML
模式生成Java类。作为这一过程的一部分,JAXB技术还提供将XML实例文档反编组为Java对象的一个内容树,然后再将内容树编组成一个XML文档。JAXB提供了一种快速和方便的方式来将一个XML模式绑定到Java代码的一个表示上,使得Java开发者能够更轻松地将XML数据和Java应用中的处理函数融合在一起,而不必对XML本身有很多的了解。
JAXB技术的一个好处就是它隐藏了细节,排除了SAX和DOM中的外部联系——生成的JAXB类仅仅描述了在源模式中实际定义了的关系。其结果就是产生了具有高度可移植性的XML数据,加入了具有高度可移植性的Java代码,这些Java代码可用于创建灵活的、轻量级的应用和Web服务。
关于对JAXB体系结构、功能和核心思想的介绍,参见第9章,同时,在第10章还提供了示例代码和手把手式的使用JAXB技术的步骤指南。
JAXB绑定过程
图1-1展示了JAXB数据绑定过程。
图1-1 数据绑定过程
JAXB数据绑定过程包括以下步骤:
1.
从一个源XML 模式生成类,并且编译生成的类。
2.
根据模式将XML文档反编组。反编组过程为模式派生(schema-drived)的JAXB类的实例对象生成一个内容树。该内容树代表源XML文档的结构和内容。
3.
在生成内容树之前,在反编组过程中还可以选择对源XML文档进行确认。如果你的应用修改了内容树,你就可以在将内容编组成XML文档之前使用确认操作来使修改生效。
4.
通过绑定编译器生成的接口,客户端应用可以修改由内容树表示的XML数据。
5.
经过处理的内容树被编组到一个或更多的XML输出文档。
确认
JAXB客户端可以执行的确认有两种:
· Unmarshal-Time
(反编组时)- 允许客户端应用接收关于在将XML数据反编组到内容树时检测到的确认错误和警告的信息,并且与其他类型的确认是完全无关的。
· On-Demand(基于命令的)
-允许客户端应用接收关于在内容树中检测到的确认错误和警告的信息。无论何时,客户端应用都可以在内容树(或内容树的任何子树)上调用Validator.validate方法。
表示XML内容
将XML内容表示成Java对象时需要进行两次映射:一次是将XML名称绑定到Java标识符,一次是将XML
schema表示成Java类的集合。
XMLSchema语言使用XML名称来标识schema组件,然而,这样的一套字符串比起合法的Java类、方法和常量标识符的集合来就大多了。为解决这一矛盾,JAXB技术使用了一些name-mapping(名称映射)算法。特别地,name-mapping算法按照一种遵从标准Java
API设计指定的方法,将XML名称映射到Java标识符, 生成与响应的映像有明显联系的标识符,并且不容易产生大量的冲突。
定制JAXB绑定
通过定制绑定声明,我们可以在全局范围内或按具体情况修改默认的JAXB绑定。通过绑定声明,就可以定制JAXB使用的默认绑定规则,绑定声明可以在XML
Schema的里面,也可以在其外面。定制JAXB绑定声明还可以使你超越XML 模式中的XML-specific(XML特性的)约束机制,将生成的JAXB类自定义为包含Java-specific(Java特性的)改进,例如类和包名的映射。
示例
下面的表中介绍了一些默认的XML
Schema-to-JAXB绑定:
|
表 1-1 JAXB绑定的模式
|
|
XML模式
|
Java类文件
|
|
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
|
<xsd:element name="purchaseOrder"
type="PurchaseOrderType"/>
|
PurchaseOrder.java
|
|
<xsd:element
name="comment" type="xsd:string"/>
|
Comment.java
|
|
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="orderDate"
type="xsd:date"/>
</xsd:complexType>
|
PurchaseOrderType.java
|
|
<xsd:complexType name="USAddress">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:decimal"/>
</xsd:sequence>
<xsd:attribute name="country"
type="xsd:NMTOKEN" fixed="US"/>
</xsd:complexType>
|
USAddress.java
|
|
</xsd:schema>
|
|
USAddress.java的模式派生类
为了简要起见,这里只显示了部分的模式派生(schema-derived)代码。下面的代码显示了用于模式的复合类型USAddress的模式派生类。
public interface USAddress {
String getName(); void setName(String);
String getStreet(); void setStreet(String) ;
String getCity(); void setCity(String);
String getState(); void setState(String);
int getZip(); void setZip(int);
static final String COUNTRY="USA";
反编组XML内容
为了将XML内容反编组成数据对象的一个内容树,首先要创建一个JAXBContext实例,用于处理模式派生类,接着创建一个Unmarshaller实例,最后反编组XML内容。举个例子,如果生成的类在一个名为primer.po的包中,并且XML内容是一个名为po.xml的文件:
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
Unmarshaller u = jc.createUnmarshaller();
(PurchaseOrder)u.unmarshal( new FileInputStream( "po.xml"
为了允许反编组时确认,你通常需要像上面显示的那样创建Unmarshaller实例,然后允许ValidationEventHandler:
默认的配置是,只要碰到确认错误,反编组操作就会失败。默认的确认事件处理程序将处理确认错误,生成送到system.out的输出,然后抛出一个异常:
} catch( UnmarshalException ue ) {
System.out.println( "Caught UnmarshalException" );
} catch( JAXBException je ) {
} catch( IOException ioe ) {
修改内容树
使用模式派生JavaBeans组件的set和get方法来操纵内容树上的数据。
USAddress address = po.getBillTo();
address.setName( "John Bob" );
address.setStreet( "242 Main Street" );
address.setCity( "Beverly Hills" );
address.setState( "CA" );
确认内容树
应用修改了内容树之后,就可以对内容树(或其子树)调用Validator.validate方法来确认内容树仍然是有效的。这种操作就叫做on-demand确认。
Validator v = jc.createValidator();
boolean valid = v.validateRoot( po );
} catch( ValidationException ue ) {
System.out.println( "Caught ValidationException" );
编组XML内容
最后,为了将内容树编组成XML格式,需要创建一个Marshaller实例,然后编组XML内容。
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
m.marshal( po, System.out );
JAX-RPC
Java API for XML-based RPC (JAX-RPC)是一种用于开发和使用Web服务的Java
API。要了解更多关于JAX-RPC的信息以及学习如何创建一个简单的Web服务和客户端,参见第11章。
Overview of JAX-RPC
概述
RPC-based服务是过程的集合体,这些过程可以被Internet上的远程客户端调用。例如,一个典型的RPC-basedWeb服务就是一种股票报价服务,这种服务接收关于某支特定股票报价的SOAP(简单对象访问协议)请求,再通过SOAP返回这支股票的报价。
注意:SOAP1.1规范说明书可以在http://www.w3.org/找到,它为XML文档交换定义了一个框架。其中指定了在SOAP消息中所需的东西及选择,以及如何将数据编码和传输。JAX-RPC
和JAXM 都是基于SOAP的。
Web服务,也是一个实现了可以被客户端调用的过程的服务器应用,被部署在一个服务器端容器中。该容器可以是一个servlet容器(例如Tomcat),也可以是Java2
Platform上的一个Web容器,Enterprise Edition (J2EE)服务器。
通过在一个Web服务描述语言(Web Services Description Language,WSDL)文档中对自身进行描述,Web服务就可以为潜在的客户端所用。WSDL描述是一种XML文档,它给出了某个Web服务的所有相关信息,包括名称、可以调用的操作,这些操作所带的参数,以及发送请求的时机。一个消费者(Web客户端)可以使用WSDL文档来查看该服务提供的内容以及如何访问这些内容。至于开发者在创建Web服务时如何使用WSDL文档,将放在以后讨论。
互操作性
或许对于一个Web服务来说最重要的需求就是:它可以在客户端和服务器之间具有互操作性。有了JAX-RPC,一个用Java之外的编程语言编写的客户端可以访问一个在Java平台上开发和部署的Web服务。反过来,一个用Java语言编写的客户端也可以与一个通过其他平台开发和部署的服务进行通信。
这种互操作性来自JAX-RPC为SOAP和WSDL提供的支持。SOAP定义了XML消息发送和数据类型映射的标准,从而使得遵从这些标准的应用可以相互通信。JAX-RPC遵从了SOAP标准,实际上,也就是基于SOAP的消息发送。这样的话,一个JAX-RPC远程过程调用将实现为一种request-response(请求-响应)式的SOAP消息。
另外一个实现互操作性的关键就在于JAX-RPC提供了对WSDL的支持。WSDL描述,是一种XML文档,它以一种标准的方式描述了
Web服务,使得这种描述具有可移植性。WSDL文档及其用途将在以后作更详细的讨论。
易用性
既然JAX-RPC是基于远程过程调用(RPC)机制的,因此它对于开发者来说相当友好。RPC需要大量复杂的基础设施,或者说“管件(plumbing)”,而JAX-RPC则使得底层的实现细节对客户端和服务器的开发者来说是透明的。例如,一个Web服务的客户端仅仅是进行Java方法调用,所有内部的编组、反编组和传送的细节问题都是自动管理的。在服务器端,Web服务仅仅是实现它所提供服务,像客户端一样,无需关心底层的实现机制。
JAX-RPC之所以成为客户端和服务器应用两者的主流Web服务,其易用性功不可没。JAX-RPC将重点放在点对点的消息发送上,这是Web服务的大多数客户端使用的一种基本的机制。虽然JAX-RPC可以提供异步的消息发送,并且可扩展到为之提供更高层次的支持,但是JAX-RPC重点关心的是如何为大多数普通任务提供易用性。这样一来,对于那些希望避免SOAP消息发送的复杂性的应用,以及那些发现使用RPC模型进行通信很合适的那些人来说,JAX-RPC都是一个不错的选择。至于SOAP消息机制的更负重任的替代者——Java
API for XML Messaging (JAXM),将在后面讨论。
高级特性
尽管JAX-RPC是基于RPC模型的,它还提供超越了基本RPC的一些特性。其一,它既允许发送完整的文档,也允许发送文档的一部分。另外,JAX-RPC支持SOAP消息处理,因而也就支持很多种类的消息。而且,
除了通常使用RPC来处理的request-response式的消息发送外,JAX-RPC还被扩展为可以处理单程消息发送。另一个高级特性就是可扩展的类型映射,这使得JAX-RPC在所发送的消息的类型方面具有更多的灵活性。
使用JAX-RPC
在一个典型的情景中,企业可能希望订购零件或货物。虽然他们寻找潜在的货物来源的方法没有什么限制,但是最好的方法就是通过一个商业注册中心和知识库服务,例如统一描述、发现和集成协议(Universal
Description,Discovery and Integration ,UDDI)注册。注意,Java API for XML Registries
(JAXR)提供了在商业注册中心和知识仓库中搜索Web服务的一种快捷方式,我们将在后面对JAXR进行讨论。Web服务通常将自己注册到一个商业注册中心,并且将相关的文档,包括它们的WSDL描述,存放到知识库中。
在商业注册中心内搜索完潜在的货物来源之后,这个企业就可以获得一些WSDL文档,这些文档都与符号搜索条件的那些Web服务一一对应。这个企业客户端就可以使用这些WSDL文档来查看这些服务所提供的服务内容以及如何与它们取得联系。
WSDL文档的另外一个用途就是作为创建stub的基础,stub是客户端用于和远程服务进行通信的低级类。在JAX-RPC的实现中,通过WSDL文档来生成stub的工具就叫做wscompile。
JAX-RPC的实现还有另外一种工具,即wsdeploy,这种工具可以用来创建tie,这是一种服务器用来和远程客户端进行通信的低级类。stub和tie执行类似的功能,stub在客户端,而tie在服务器端。除了生成tie之外,wsdeploy还可以用来创建WSDL文档。
JAX-RPX运行时系统,例如包含在JAX-RPC实现中的那个系统,便可以使用wscompile和wsdeploy在后台分别创建的stub和tie。首先,它将客户端的远程方法调用转换成一个SOAP消息,并将这个消息发送到服务器,作为一个HTTP请求。在服务器端,JAX-RPC运行时系统接收请求,并将SOAP消息转换成一个方法调用,然后调用这个方法。在Web服务处理完这个请求之后,运行时系统再经过一遍类似的步骤将结果返回给客户端。要记住的一点是,虽然客户端和服务器之间的通信的实现细节很复杂,但是这对于Web服务和它们的客户端来说都是透明的。
创建Web服务
使用JAX-RPC来开发Web服务简单得令人惊奇。服务的本身基本上就是两个文件,一个是声明了该服务的远程过程的接口,一个是实现了这些过程的类。除此之外,还有一些用于配置和部署服务的小文件,但是,首先还是让我们看看组成一个服务的两大主要部分,接口定义和接口的实现类。
下面的接口定义是一个简单的例子,它展示了批发商希望用来为预期客户服务的一些方法。注意,服务定义接口扩展了java.rmi.Remote,它的方法还抛出了一个java.rmi.RemoteException对象。
import java.rmi.RemoteException;
public interface CoffeeOrderIF extends Remote {
public Coffee [] getPriceList()
public String orderCoffee(String coffeeName, int quantity)
方法getPriceList返回一个Coffee对象数组,每个Coffee对象都含有一个name字段和一个price字段。Coffee对象都与批发商当前要出售的咖啡种类是一一对应的。方法orderCoffee返回一个String,用于确认订购或者表明需要延期交货。
下面的例子展示了实现的大致轮廓(省略了实现的细节)。大致是这样的,为了获取当前信息,方法getPriceList首先查询公司的数据库,并返回结果,即一个Coffee对象数组。第二个方法,orderCoffee,同样也需要查询这个数据库,以便查看被订
购的咖啡是否有足够的库存。如果有,该实现将开始执行内部的订单处理,并向客户发送回复信息,告诉他可以满足他的订购。如果没有足够的库存,该实现将发送自己的订单,以便补充库存,并通知客户咖啡需要延期交货。
public class CoffeeOrderImpl implements CoffeeOrderIF {
public Coffee [] getPriceList() throws RemoteException; {
public String orderCoffee(String coffeeName, int quantity)
throws RemoteException; {
编写了服务的接口和接口的实现类之后,开发者下一步要做的就是运行映射工具。这种工具可以用前面的编写的接口和类为基础,生成stub和tie类,还有其他必需的类。这里再次提醒一下,开发者可以使用这种工具来为该服务创建WSDL描述。
创建一个Web服务的最后一步就是打包和部署。要将一个Web服务定义打包,可以通过一个Web应用归档(Web
application archive ,WAR)来进行。WAR是一种为Web应用而设的JAR文件,它包含了Web服务所需的所有文件,这些文件都是以压缩的格式放在WAR文件中的。例如,CoffeeOrder服务可以打包成文件jaxrpc-coffees.war,有了这个打包文件,这个CoffeeOrder服务就可以方便地发布和安装了。
每个WAR文件中都必须有的一个文件就是一个被称为部署描述文件(deployment
descriptor)的XML文件。该文件名为web.xml,它包含了部署一个服务所需的信息。例如,如果服务要被部署到一个servlet引擎(如Tomcat)上,该部署描述文件就会包含这个servlet的名称及其描述、servlet类、初始的参数以及其他用于启动的信息。web.xml文件所引用到的文件之一就是一个配置文件,该文件由映射工具自动生成。在我们提到的这个例子中,该文件可能的名称为:
CoffeeOrder_Config.properties.
在一个Tomcat容器中部署例子中的CoffeeOrder Web服务,只需简单地将jaxrpc-coffees.war拷贝到Tomcat的webapps目录下即可。如果要在J2EE服务器上部署该服务,则可以利用应用服务器提供商提供的部署工具来进行。
编写客户端
要为Web服务编写客户端应用,只需编写一些调用所需方法的代码即可。当然,如果要建立远程方法调用并将其发送到Web服务,则需要做更多的工作。但是,这些工作都是后台完成的,对客户端来说是完全透明的。
接下来的类的定义就是一个Web服务客户端的例子。它创建了一个CoffeeOrderIf实例,并通过这个实例调用方法getPriceList。然后,这个实例访问方法getPriceList返回的Coffee对象数组中每个对象的name和price字段,并将其打印出来。
类CoffeeOrderServiceImpl也是由映射工具生成的一个类。它是一个stub工厂,仅有的一个方法就是getCoffeeOrderIF,换句话说,它惟一的目的就是创建CoffeeOrderIF实例。由CoffeeOrderServiceImpl
创建的CoffeeOrderIF实例都是客户端的stub,可用于调用接口CoffeeOrderIF中定义的方法。这样一来,变量coffeeOrder就代表了一个客户端stub,通过它就可以调用在CoffeeOrderIF
中定义的getPriceList方法。
getPriceList方法在收到一个响应并将其返回之前,都是被阻塞的。因为WSDL文档正在被使用,JAX-RPC运行时将从中获取服务端点。这时,客户端类无需为远程过程调用指定目的地。当服务器端点真正被需要的时候,它就可以作为参数在命令行中给出。一个客户端类可以是这样的:
public class CoffeeClient {
public static void main(String[] args) {
CoffeeOrderIF coffeeOrder = new
CoffeeOrderServiceImpl().getCoffeeOrderIF();
coffeeOrder.getPriceList():
for (int i = 0; i < priceList.length; i++) {
System.out.print(priceList[i].getName() + " ");
System.out.println(priceList[i].getPrice());
调用远程方法
一旦客户端发现了一个Web服务,它就可以调用这个服务中的某个方法。下面的例子就调用了一个名为getPriceList的方法,该方法没有带任何参数。这里再次提醒一下,
JAX-RPC运行时可以测定来自WSDL描述的CoffeeOrder服务的端点(它的URI)。如果没有使用WSDL文档,你就必须以命令行参数的形式给出该服务的URI。在编译了CoffeeClient.java文件之后,要调用CoffeeClient.java类的getPriceList方法,你需要在命令行键入以下内容:
java coffees.CoffeeClient
上面的命令行执行的远程过程调用是一种静态的方法调用。换句话说,RPC是在编译时决定的。这里要注意的是,使用JAX-RPC,还可以在运行时动态调用远程方法。这可以通过使用动态调用接口(Dynamic
Invocation Interface ,DII)或一个动态代理(dynamic proxy)做到。
|