JAXM
Java API for XML Messaging (JAXM)为从Java平台通过Internet发送XML文档提供了一种标准的方法。它基于SOAP1.1规范以及具有Attachments规范的SOAP,它为XML消息的交换定义了一个基本的框架。JAXM可以扩展到使用更高级的消息发送协议,例如在ebXML(electronic
business XML,电子事务XML)消息服务规范说明书中通过在SOAP之上添加协议的功能而定义的消息发送协议。
注意:ebXML Message Service Specification可以在http://www.oasis-open.org/committees/ebxml-msg/找到。与其他的一些东西一起,它可以提供比SOAP规范更安全的在Internet上发送业务消息的方式。
在第12章中,你可以看到关于如何使用JAXM API的介绍,并且运行与Java WSDP包含在一起的示例JAXM应用。
通常,企业使用一个messaging provider服务,该服务将在后台处理传送和路由消息所需的工作。如果使用了一个messaging
provider服务,那么所有的JAXM消息都需要通过它。因此,当一个企业发送一条消息时,这条消息首先被送到messaging provider服务中,然后再送到消息接收方的messaging
provider服务中,最后到达真正的消息接收方。在消息到达目的地之前,还可以将其路由到一个中间接收方。
由于消息都要通过messaging provider,所以它可以负责一些内务管理的细节,例如为消息指定标识符、存储消息,以及检查此前是否发送过同样的消息。messaging
provider还可以重发在第一次发送时没能达到目的地的消息。messaging provider的精彩之处就在于使用JAXM技术的客户端(“JAXM客户端”)对该messaging
provider在后台所做的一切都一无所知。JAXM客户端只是简单地执行Java方法调用, 而在消息发送基础设施中起连接作用的messaging provider则将所有消息发送的细节都隐藏在后台。
虽然在通常情况下企业都使用了一个messaging provider服务。实际上,在没有messaging
provider的情况下也可以进行JAXM消息发送。在这种情况下,JAXM客户端(也叫standalone客户端)将点到点(point-to-point)
的消息发送到那些为request-respond式的消息发送而特别实现的Web服务。Request-response式的消息发送是同步的,也就是说,发送请求和接收响应是在同样的操作中进行的。request-response消息是通过一个SOAPConnection
对象的SOAPConnection.call方法发送的,该方法发送完消息之后,便阻塞起来,直到收到响应为止。Standalone客户端只能以客户端的角色运行,也就是说,它只能发送请求,然后接收响应。相反,使用了messaging
provider 的JAXM客户端既可以充当客户端的角色,又可以充当服务器(服务)的角色。作为客户端,它可以发送请求;作为服务器,它可以接收请求,处理请求,最后返回响应。
虽然没有硬性的规定,但JAXM消息发送通常是在一个容器中进行的,例如一个servlet容器。使用了messaging
provider并且被部署到一个容器中的Web服务可以进行单程的消息发送,也就是说,它可以接收单程消息形式的请求,并且可以在一段时间之后以单程消息的形式返回响应。
由于messaging provider可以提供这样的一些特性,JAXM有时比JAX-RPC更适合于SOAP消息发送。下面的列表包含了JAXM可以提供的一些特性,而通常情况下RPC(包括JAX-RPC)是不能提供这些特性的:
· 单向(异步)消息发送
· 可以将消息路由到多个地方
· 可靠的消息发送,因为带有像有保证消息发送这样的一些特性
一个SOAPMessage对象代表了一个XML文档,该文档实际上就是一个SOAP消息。SOAPMessage对象总有一个SOAP部分,可能还会有一个或更多的附加部分。SOAP部分必须有一个SOAPEnvelope对象,这个对象又必须含有一个SOAPBody对象。SOAPEnvelope对象还可能含有一个SOAPHeader对象,SOAPHeader对象中还可以添加一个或更多的头部。
在发送消息的内容时,SOAPBody对象可以将XML段保留下来。如果你要发送的内容不是XML格式的,或者是一个完整的XML文档,那么除了SOAP部分外,你还需要在消息中包含一个附加部分。附加部分中所包含的内容没有限制,所以它可以包含图像或
者任何其他类型的内容,包括XML段和XML文档。
获得连接
JAXM客户端首先要做的事就是获得一个连接,或者是一个SOAPConnection对象,或者是一个ProviderConnection
对象。
获得一个点到点的连接
一个standalone客户端只能使用SOAPConnection对象,这种对象是一种点到点的连接,即直接从发送者到接收者。所有的JAXM连接都是由连接工厂创建的。对于SOAPConnection对象,连接工厂就是SOAPConnectionFactory对象。客户端通过调用下面的一行代码就可以获得SOAPConnectionFactory的默认的实现。
SOAPConnectionFactory factory =
SOAPConnectionFactory.newInstance();
客户端使用 factory创建一个 SOAPConnection 对象。
SOAPConnection con = factory.createConnection();
获得一个到Messaging Provider的连接
为了使用一个messaging provider,应用必须首先获得一个ProviderConnection对象,该对象是一个到messaging
provider的连接,而不是到某个特定接收方的连接。有两种方法可以获得ProviderConnection对象,第一种方法与standalone客户端获得SOAPConnection对象的方法类似。这种方法首先获得ProviderConnectionFactory的默认实现的一个实例,再用这个实例来创建连接。
ProviderConnectionFactory pcFactory =
ProviderConnectionFactory.newInstance();
ProviderConnection pcCon = pcFactory.createConnection();
变量pcCon代表一个到JAXM messaging provider的默认实现的连接。
第二种创建ProviderConnection对象的方法是先得到一个ProviderConnectionFactory对象,该对象的实现就是为了建立一个到某个特定messaging
provider的连接。下面的代码演示了获得那样的一个ProviderConnectionFactory对象并以之来建立一个连接。开始两行代码使用Java
Naming and Directory Interface (JNDI,Java命名和目录接口)
API来从命名服务那里获得适当的ProviderConnectionFactory对象,在命名服务那里ProviderConnectionFactory对象已经以名称“CoffeeBreakProvider”进行了注册。当这个逻辑名称用作一个参数时,lookup方法将返回与该逻辑名称绑在引起的CoffeeBreakProvider对象。返回的值是一个Java对象,该对象必须限制为一个CoffeeBreakProvider对象,以便用于创建一个连接。第3行使用一个JAXM方法来真正获得一个连接。
Context ctx = getInitialContext();
ProviderConnectionFactory pcFactory =
(ProviderConnectionFactory)ctx.lookup("CoffeeBreakProvider");
ProviderConnection con = pcFactory.createConnection();
ProviderConnection的实例con代表一个到名为Coffee Break的messaging
provider的连接。
创建消息
与连接一样,消息也是由一个工厂创建的。与连接工厂类似,MessageFactory对象也可以通过两种方法获得。第一种方法是获得一个MessageFactory类的默认实现的实例。这个实例可以用来创建一个基本的SOAPMesssage对象。
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage m = messageFactory.createMessage();
由messageFactory所创建的所有SOAPMessage对象,包括前面代码中的m,都是基本SOAP消息。这意味着它们没有预定义的头部。
JAXM所具有的灵活性之一就是它允许SOAP头部有特殊的用途。例如,像ebXML这样的一些协议可以建立在SOAP消息发送协议之
上,以便提供附加头部的实现,这样就获得了附加的功能。对于一个给定的标准或工业来说,SOAP的这种用途就叫做一个profile(配置文件)。(关于profile的更多的信息请参见JAXM
tutorial的Profiles一节,URL:http://java.sun.com/webservices/docs/1.1/tutorial/doc/JAXM3.html#wp63995)
在创建MessageFactory对象的第二种方法中,你可以使用ProviderConnection的createMessageFactory方法,并给它一个profile。由MessageFactory
对象生成的SOAPMessage对象可以支持这种profile。例如,在下面的代码段中,m2将支持提供给createMessageFactory的messaging
profile(代码段中的schemaURI是所需的profile的模式的URL)。
MessageFactory messageFactory2 =
con.createMessageFactory(<schemaURI>);
SOAPMessage m2 = messageFactory2.createMessage();
新的SOAPMessage对象m和m2都自动地包含了所需的元素SOAPPart、SOAPEnvelope和SOAPBody,还有可选的元素SOAPHeader(该元素是为了方便而包含进来的)。SOAPHeader和SOAPBody对象都初始为空,下一节将介绍为这两个对象添加内容的一些常用的方法。
填充消息
内容可以添加到SOAPPart对象中,可以添加到一个或多个AttachmentPart对象中,或者同时添加到消息的这两个对象中。
填充消息的SOAP部分
在前面已经说过,所有的消息都有一个SOAPPart对象,该对象有一个SOAPEnvelope对象,SOAPEnvelope对象又有一个SOAPHeader对象和一个SOAPBody对象。添加内容到消息的SOAP部分的一个方法就是创建一个SOAPHeaderElement对象或者一个
SOAPBodyElement对象,然后将其添加到body中。传给createName方法的参数是一个Name对象,用于识别要添加的SOAPBodyElement。最后一行通过addTextNode方法添加XML字符串。
SOAPPart sp = m.getSOAPPart();
SOAPEnvelope envelope = sp.getSOAPEnvelope();
SOAPBody body = envelope.getSOAPBody();
SOAPBodyElement bodyElement = body.addBodyElement(
envelope.createName("text", "hotitems",
"http://hotitems.com/products/gizmo");
bodyElement.addTextNode("some-xml-text");
另外一种方法是通过向SOAPPart对象传递一个javax.xml.transform.Source对象来为之添加内容,javax.xml.transform.Source对象可以是一个SAXSource对象,也可以是一个DOMSource对象或者StreamSource对象。Source对象包含了消息中的SOAP部分的内容,还包含了使其成为源输入所必需的一些信息。StreamSource对象包含了作为一个XML文档的内容;SAXSource或DOMSource对象则包含了将内容以及将这些内容传送到一个XML文档中所用的指令。
下面的代码段演示了添加DOMSource对象形式的内容。第一步是从SOAPMessage对象获得SOAPPart对象。接下来的代码使用JAXP
API中的方法创建要添加的XML文档。代码中使用了一个DocumentBuilderFactory对象来获得一个对象。然后分析给出的文件,产生用于初始化一个新的DOMSource对象的文档。最后,将DOMSource对象domSource传递给SOAPPart.setContent方法。
SOAPPart soapPart = message.getSOAPPart();
DocumentBuilderFactory dbf=
DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("file:///foo.bar/soap.xml");
DOMSource domSource = new DOMSource(doc);
soapPart.setContent(domSource);
填充消息中的附加部分
一个Message对象可能没有附加部分,但是如果它要包含任何非XML格式的内容,那么这样的内容就必须放在一个附加部分中。附加部分的数量没有限制,它们包含的内容也没有限制,从简单的文本到图像文件都可。在下面的代码段中,内容是一个
JPEG文件格式的图像,其URL被用于javax.activation.DataHandler对象dh的初始化。Message对象m创建了AttachmentPart的对象attachPart,再使用包含了图像URL的数据句柄初始化该对象。
URL url = new URL("http://foo.bar/img.jpg");
DataHandler dh = new DataHandler(url);
AttachmentPart attachPart = m.createAttachmentPart(dh);
m.addAttachmentPart(attachPart);
SOAPMessage对象还可以为一个AttachmentPart添加内容,方法是将一个对象及其内容的类型传递给方法createAttachmentPart。
AttachmentPart attachPart =
m.createAttachmentPart("content-string", "text/plain");
m.addAttachmentPart(attachPart);
第三种方法是创建一个空的AttachmentPart对象,然后将一个对象及其内容的类型传递给AttachmentPart.setContent方法。在该代码段中,这个对象就是一个用一个jpeg图像进行了初始化的ByteArrayInputStream对象。
AttachmentPart ap = m.createAttachmentPart();
ap.setContent(new ByteArrayInputStream(jpegData),
发送消息
一旦填充了一个SOAPMessage对象,你就可以发送这个对象了。Standalone客户端使用SOAPConnection的call方法来发送消息。该方法发送完消息之后就将自己阻塞起来,直到它收到响应为止。传给call方法的参数中,一个是要发送的消息,还有一个URL对象,该对象包含了接收者的端点的URL。
soapConnection.call(message, endpoint);
使用了messaging provide的应用通过调用ProviderConnection
的send方法来发送消息。该方法异步地发送消息,这意味着它发送完消息之后便立即返回。而该消息的响应(如果有的话)将在以后的某个时间单独发送。注意,这个方法只带一个参数,即要发送的消息。messaging
provider将使用头部信息来判断目的地。
providerConnection.send(message);
JAXR
Java API for XML Registries (JAXR)为在Internet上访问标准商业注册中心提供了方便的途径。商业注册中心通常被描述为电子黄页,因为它们包含了各大企业以及该企业提供的产品或服务的清单。JAXR为使用Java语言编写应用的开发者提供了一种统一的方法,来使用基于开发标准(例如ebXML)或工业联盟规范(例如UDDI)的商业注册中心。
企业可以通过一个注册中心来注册自己,或从中查找那些可能与之做生意的企业。另外,它们还可以通过注册中心来提供共享的资料,或从中搜索别人提供的资料。标准组织已经为特定类型的XML文档开发了响应的schema(模式),例如,两个企业可以协商使用它们的工业标准订单schema。由于这样的schema被存放在商业注册中心中,因此双方可以使用JAXR来访问它。
注册中心已经逐渐成为Web服务的重要组成部分,因为它们允许企业以一种松散耦合的方式动态地相互合作。相应地,对JAXR的需求同样也在增长,JAXR允许企业访问用Java编程语言编写的标准商业注册中心。
要了解其他关于JAXR技术的信息,包括如何实现一个JAXR客户端,以便将一个组织及其提供的Web服务发布到一个注册中心中以及在注册中心中查找组织和服务,参见第13章(URL:http://java.sun.com/webservices/docs/1.1/tutorial/doc/JAXR.html#wp63210)。第13章还解释了任何运行与本指南一起提供的示例程序。
使用JAXR
下面的几节给出了使用商业注册中心时常用的两种方法的示例。给出这两个示例的目的是为了让你清楚如何使用JAXR,但不要求理解透彻。
注册一个企业
一个使用Java平台做电子商务的组织可以使用JAXR来将自己注册到一个标准的注册中心。它需要提供企业名、自我介绍和一些类别信息,以便别人可以搜索到它。下面的代码段展示了这些内容。代码段首先创建一个RegistryService
对象 rs,然后用该对象创建一个BusinessLifeCycleManager对象lcm和BusinessQueryManager对象bqm。要注册的企业,即一个名为The
Coffee Break的咖啡馆连锁店,用一个Organization对象 org表示,Coffee Break向该对象中添加自己的名称,自我介绍,以及自己所属的类别,
即North American Industry Classification System (NAICS)。org现在包含了The Coffee
Break的一些属性和类别,接着,该对象被添加到一个Collection对象orgs中。最后,orgs被lcm存储起来,后者将管理orgs中的Organization对象的
生命周期。
RegistryService rs = connection.getRegistryService();
BusinessLifeCycleManager lcm =
rs.getBusinessLifeCycleManager();
BusinessQueryManager bqm =
rs.getBusinessQueryManager();
Organization org = lcm.createOrganization("The Coffee Break");
"Purveyor of only the finest coffees. Established 1895");
ClassificationScheme cScheme =
bqm.findClassificationSchemeByName("ntis-gov:naics");
Classification classification =
(Classification)lcm.createClassification(cScheme,
"Snack and Nonalcoholic Beverage Bars", "722213");
Collection classifications = new ArrayList();
classifications.add(classification);
org.addClassifications(classifications);
Collection orgs = new ArrayList();
lcm.saveOrganizations(orgs);
搜索一条注册
企业还可以使用JAXR来搜索其他企业的注册。下面的代码段使用BusinessQueryManager对象
bqm来搜索The Coffee Break。要使得bqm可以调用findOrganizations方法,代码还需要定义要使用的搜索标准。这时,findOrganizations被提供了六个可能需要的搜索参数中的三个;因为第3、5和6个参数被置为null,所有这些标准对搜索不起限制作用。第1、2、4个参数都是Collection对象,在这些对象中定义了findQualifiers和namePatterns。findQualifiers中惟一的一个元素是一个String,它指定了只有当组织的名称与参数namePatterns中的某个名称敏感匹配(区分大小写)时,才能返回该组织的名称。参数namePatterns(也是一个只有一个元素的Collection对象),认为名称中带有Coffee子串的企业是一个匹配项。另一个Collection
对象是classifications,它是在The Coffee Break自己进行注册时定义的。前面的代码段为The Coffee Break的industry,是一个定义类别的例子。
BusinessQueryManager bqm = rs.getBusinessQueryManager();
Collection findQualifiers = new ArrayList();
findQualifiers.add(FindQualifier.CASE_SENSITIVE_MATCH);
Collection namePatterns = new ArrayList();
namePatterns.add("%Coffee%"); // Find orgs with name containing
//Find using only the name and the classifications
BulkResponse response = bqm.findOrganizations(findQualifiers,
namePatterns, null, classifications, null, null);
Collection orgs = response.getCollection();
JAXR还允许使用一个SQL查询来搜索一条注册。这可以通过使用一个DeclarativeQueryManager对象来进行,下面的代码段对
此作了演示:
DeclarativeQueryManager dqm = rs.getDeclarativeQueryManager();
Query query = dqm.createQuery(Query.QUERY_TYPE_SQL,
"SELECT id FROM RegistryEntry WHERE name LIKE %Coffee% " +
"AND majorVersion >= 1 AND " +
"(majorVersion >= 2 OR minorVersion >= 3)");
BulkResponse response2 = dqm.executeQuery(query);
BulkResponse对象response2包含一个id值(一个uuid),对应着RegistryEntry中的每一项,RegistryEntry有一个“Coffee”在它的名称中,还有一个版本号,版本号可能是1.3,或更高。
为了确保JAXR客户端和一个注册中心实现之间的通信具有互操作性,消息发送是通过使用JAXM来进行的。这些都完全发生在后台,所以作为一个JAXR用户,你对此是一无所知的。
简单的情景
下面的情景展示了Java APIs for XML的用法,以及如何让这些API一起工作。Java
APIs for XML的一个豪华之处就是,在很多情况下,它可以为某项任务提供多种实现的途径,以便你可以随心所欲地编写代码来满足要求。本节将给出一些例子,在这些例子中有些API本来还有其他的选择,然后解释为什么某个API比起其他的API来是更好的选择。
情景
假设一家名为The Coffee Break的连锁咖啡馆店的老板想通过在网上销售咖啡的方式扩展生意。他指示他的业务经理找出一些新供应的咖啡,了解这些咖啡的批发价,以及在哪里可以进行订购。The
Coffee Break可以分析这些价格,然后判断哪些新的咖啡有利可图,以及从哪家公司购买这样的咖啡划算些。
找到新的批发商
这个业务经理将寻找潜在的新的咖啡供应源的任务分派给公司的软件工程师。她认为寻找新的咖啡供应者的最好办法就是搜索一个UDDI,在UDDI中已经有了The
Coffee Break自己的注册。
该工程师使用JAXR发出一个查询,这个查询要搜索的是可以提供咖啡批发的供应商。在后台,JAXR实现使用JAXM来发送查询到注册中心,但是这些对于工程师来说都是透明的。
UDDI注册中心收到查询,接着用收到的JAXR编码中的搜索标准与在该注册中心注册了的组织的信息进行对照。当搜索完成时,注册中心将返回信息,告诉向自己发送查询的人关于符合特定标准的能提供咖啡批发的供应商的联系方式。虽然注册中心在后台使用JAXM来传送信息,但是工程师收到的响应却是JAXR编码。
请求价格表
接下来这个工程师要做的就是请求每个咖啡批发商的价格表。她已经获得了每个批发商的WSDL描述,从中她可以知道该调用什么过程来获得价格,以及发送请求的目的URL。她编写的代码通过使用JAX-RPC来调用适当的远程过程,并接收来自批发商的响应。The
Coffee Break与其中的某个批发商在生意上有过长时间的合作,而且约定好彼此之间可以使用agreed-upon XML schema来交换JAXM消息。因此,对于这个批发商,该工程师的代码就使用JAXM
API来请求当前价格,然后这个批发商则通过一个JAXM消息返回价格表。
比较价格和订购咖啡
收到她发出的价格请求的响应之后,这个工程师便使用SAX来处理这些价格表。这里之所以使用SAX,而不是DOM,是为了可以简单地比较价格,这样效率会更高一些。(不过如果要修改价格表的话,她就需要使用DOM)收到不同供应商提供的价格之后,她的应用就比较这些价格,并且显示出比较的结果。
参考工程师得出的价格比较结果,老板和业务经理决定了与其中的一些供应商做生意,准备向这些供应商下订单。向新的批发商下的订单通过JAX-RPC发送,而向有过业务来往的批发商下的订单则通过JAXM来发送。不过是用JAX-RPC还是用JAXM,收到的响应都是由订单号和发货日期组成的确认信息。
在Internet上销售咖啡
这时,The Coffee Break已经做好了在网上销售咖啡的准备。它需要在自己网站的HTML页面上公布价格list/order表单。但是,在这之前,该公司需要先为这些咖啡定出价格来。工程师编写一个应用,将批发价乘于135%,这个乘积将作为The
Coffee Break定出的销售价格。稍作修改之后,就可以将零售价列表变成在线的订购表单。
工程师使用JavaServer
Pages
(JSP
)技术来创建一个订购表单,客户可以用这样的表单在网上订购咖啡。从JSP页面中,她可以获得每种咖啡的名称和价格,然后她将这些名称和价格插入到JSP页面中的一个HTML表(table)中。客户可以输入他需要的每种咖啡的数量,然后单击“Submit(提交)”按钮,这样就发送了订单。
小结
虽然,为了简单起见,我们对这个情景做了一些简化,但它还是向我们展示了XML技术是如何用于Web服务领域的。有了Java
APIs for XML 和J2EE平台,通过它们创建Web服务和编写应用就变得更加容易了。
第19章演示了本情景的一个简单的实现。
|