UML软件工程组织

用JAXM开发Web服务
陈亚强 (cyqcims@mail.tsinghua.edu.cn)
北京华园天一科技有限公司高级软件工程师  (选自IBM中国)

J2EE Web服务开发系列之一
 
JAXM相关概念介绍
    消息
    连接
    消息提供者
案例介绍
EJB组件介绍
开发服务端
开发客户端
创建 SOAP 连接
创建 SOAP 消息
在SOAP消息里增加数据
发送消息
对SOAP应答进行处理
GUI客户端
总结
参考资料
关于作者
本文介绍JAXM Web服务开发的基本概念,然后结合一个具体的案例来介绍使用JAXM开发Web服务中要使用的编程技术和编程技巧。

阅读本文前您需要以下的知识和工具:

JavaTM Web Services Developer Pack 1.1,并且会使用初步使用;

至少会使用一种EJB容器来开发、部署EJB,并且了解怎么在客户端访问EJB组件;

一般的Java编程知识。

在J2EE平台里,要开发一个Web服务,我们通常有两种选择:

使用JAX-RPC(Java API for XML-based RPC)

使用JAXM(Java API for XML Messaging)

作为对JAXM开发技术的入门,本文先不比较它们的技术特点。我将结合一个具体的案例来讨论JAXM的开发技术方方面面。

本文的参考资料见参考资料

本文的全部代码在这里下载

JAXM相关概念介绍
通常我们说的JAXM API,它包括两个包:

Javax.xml.soap:它是发送SOAP消息的基本包,主要包含了发送带有附件的SOAP消息的API(SOAP with Attachments API for Java ,SAAJ)。它是SOAP消息的基本包,它为构建SOAP包和解析SOAP包提供了重要的支持。它包含了发送请求-响应消息相关的API。

Javax.xml.messaging:定义了JAXM的规范,包含了发送和接收消息所需的API。

JAXM包含了以下几个概念:消息(Message)、连接(Connection)、消息提供者(Messaging providers)。

消息
JAXM消息遵循SOAP标准,我们可以通过JAXM API方便的创建SOAP 消息。有两种类型的消息,带附件的消息和不带附加的消息。不带附件的消息结构如图1所示。

如图1所示,在SAAJ API中,它使用SOAPMessage类来代表SOAPMessage,相应的,使用SOAPPart类来代表SOAPPart,SOAPBody类代表SOAP Body。

图1 不带附件的SOAP消息

其中Header和SOAPFault是可选的,Header可以多个,Body只有一个,如果有SOAP Fault,那么它一定在SOAP Body后面。带附加的SOAP消息如图2所示。

图2 带附件SOAP消息

可以看出,一个SOAP消息可以有一个或者多个附件。SAAJ API使用AttachmentPart类来代表SOAP消息的附件。每个AttachmentPart有一个MIME Header来表示附件的类型。

连接
有两种类型的连接,它们是:

消息发送者到接收者的直接连接,javax.xml.soap.SOAPConnection表示了这种类型的连接,由于它是点对点的,所以比较容易使用,即使不在Servlet或者J2EE容器里也能使用;

到消息提供者的连接,javax.xml.messaging.ProviderConnection表示了这种连接,这种方式需要消息提供者,消息发送者和消息使用者通过消息提供者来交互。

消息提供者
消息提供者主要负责传送消息,它把消息路由到目的地,一个消息发出后,可能要经过多个消息提供者才能到达目的地。

如果使用MessageProvider,可以达到以下的目的:

除了能够发送request-response类型的消息外,还可以发送One-way(单向)消息;

(消息)客户端有时也可以作为服务端来使用。

案例介绍
在本文,我将结合一个具体的案例来介绍JAXM Web服务的开发。此案例具体情况如下。

某图书城决定使用Web服务来对外提供图书信息查询服务,图书城现有的系统运行在J2EE平台上,客户端通过JAXM来使用图书城提供的Web服务。系统的体系结构如图3所示:

图3 系统体系结构

客户端可以是一般的java GUI程序(当然也可以是JSP、Servlet等)。客户端通过SOAP消息和Servlet容器里运行的JAXM Servlet进行交互,JAXM Servlet是服务提供者,EJB容器里运行的是业务组件,它们为JAXM Servlet提供服务。

客户端请求传递的过程如图4所示:

图4 请求传递的过程

可以看出,客户端通过SOAP和JAXM 服务端通信,JAXM使用EJB组件来获得业务服务。

系统为客户端提供了三种查询服务:查询所有图书,按类别查询图书,按图书名搜索某本特定的图书。这三种服务分别有服务端的三个JAXM Servlet实现。它们是:

ListAllBook:查询所有的图书信息;

ListByCategory:按类别查询图书信息;

BookDetail:查询某个特定名称的图书信息。

客户端是用Swing编写的GUI界面,使用界面如图5所示。

图5 客户端界面

客户端和服务端传输图书信息时采用例程1所示的格式。

例程1 传输图书信息的格式(book.dtd)


<!ELEMENT books(book*)>
<!ELEMENT book (name,publisher,price,author+,category,description)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT price (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT category (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ATTLIST book id  CDATA #REQUIRED>

这个信息包含在SOAP消息的Body里,按照这个格式,传输的SOAP消息结构如例程2所示。

例程2 传输的SOAP消息的格式(book.msg)


<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
    <soap-env:Header/>
    <soap-env:Body>
        <books:GetAllBooks xmlns:books="http://hellking.webservice.com">
           <books:book id="2-1234-4455-4">
				<books:name>J2EE企业应用开发</books:name>
				<books:publisher>电子工业出版社</books:publisher>
				<books:price>60</books:price>
				<books:category>计算机类</books:category>
				<books:description>非常好的介绍J2EE企业应用开发的书</books:description>
				<books:author>陈亚强</books:author>
				<books:author>刘晓华</books:author>
	      </books:book>
</books:GetAllBooks>
   </soap-env:Body>
</soap-env:Envelope>

为了传输数据的便利,我把图书信息用一个专门的值对象来表示,如例程3所示。

例程3 BookVO值对象


package com.hellking.webservice;
import java.util.Collection;
public class BookVO implements java.io.Serializable
{
   private String name;//图书名字
   private String publisher;//图书出版社
   private float price;//图书价格
   private String isbn;//图书ISBN
   private String description;//图书的简介
   private String category;//图书的类别
   private Collection authors;//图书的作者,因一本书可以有多个作者,故把它表示成Collection。
public void setName(String name)
   {
   		this.name=name;
  }
public String getName()
  {
   		return this.name;
  }
…其它的getter和setter方法

可以看出,BookVO其实是和例程1中的DTD是对应的。需要指出的是,BookVO可以使用JAXB(Java API for XML Binding)中的工具来生成。

EJB组件介绍
本案例使用了两个EJB组件,它们分别是BookEntityEJB和BookServiceFacadeEJB。其中BookEntityEJB是实体Bean,它代表了每本书的详细信息;BookServiceFacadeEJB为有状态会话Bean,它是一个会话门面,为JAXM Servlet提供业务服务。BookServiceFacadeEJB组件的远程接口如例程4所示。

例程4 BookServiceFacadeEJB组件的远程接口


package com.hellking.webservice.ejb;
import java.rmi.RemoteException;
import javax.ejb.*;

public interface BookServiceFacade extends EJBObject
{
    /**
     * @J2EE_METHOD  --  getAllBook,查找所有的书
     */
    public java.util.Collection getAllBook    () throws RemoteException;
    
    /**
     * @J2EE_METHOD  --  findByCategory,按类别查找
     */
    public java.util.Collection findByCategory    (String category) throws RemoteException;

/**
     * @J2EE_METHOD  --  getBookDetail   ,按名字查找
     */

    public java.util.Collection getBookDetail    (String name) throws RemoteException;
}

可以看出,它提供了三个业务服务,分别是getAllBook(),findByCategory(String category),getBookDetail(String name)。这三个业务方法返回的都是java.util.Collection。其实,getBookDetail( String name )方法返回的应该是一个值对象,但是为了方便统一处理,也通过处理让它返回java.util.Collection类型,这一点以后的代码中体现出来。

在BookEntityEJB Home接口也提供了对应的查找方法,如例程5所示。

例程5 BookEntityEJB的Home接口


package com.hellking.webservice.ejb;

import java.rmi.RemoteException;
import javax.ejb.*;

public interface BookEntityHome extends EJBHome
{
    public BookEntity findByPrimaryKey    (String primaryKey) 
                throws RemoteException, FinderException;
   
    public BookEntity create(String isbn) throws RemoteException, CreateException;
    
    /**
     * @J2EE_METHOD  --  getAllBook,查找所有的书
     */
public java.util.Collection findAllBook    () 
                throws RemoteException, FinderException; 
  
/**
     * @J2EE_METHOD  --  findByCategory,按类别查找
     */

    public java.util.Collection findByCategory    (String category) 
                throws RemoteException, FinderException; 
   
/**
     * @J2EE_METHOD  --  getBookDetail   ,按名字查找
   */
 public BookEntity findByName    (String name) 
                throws RemoteException, FinderException; 
    
}

开发服务端
下面开发服务端,我们前面说过,服务端共有三个JAXM Servlet,它们分别提供三种不同的查询服务。

由于使用了点对点的消息模型,故服务端需要实现javax.xml.messaging. ReqRespListener接口,并且需要继承javax.xml.messaging.JAXMServlet类。javax.xml.messaging.JAXMServlet是一个Servlet,它为开发消息服务的Servlet提供了一个框架。需要指出的是,javax.xml.messaging. ReqRespListener接口定义了一个


public SOAPMessage onMessage  (SOAPMessage message)

方法,故我们开发的JAXM服务端Servlet必须实现这个方法。 onMessage 方法就是当此Servlet接收到SOAPMessage时激发的方法,它通过此方法对外界提供服务(我们可以把这个方法简单的比喻成普通的HttpServlet中的doGet()、doPost()方法,HttpServlet正是通过doGet()、doPost()来为客户端提供服务)。

ListAllBook的部分代码如例程6所示。

例程6 ListAllBook的部分代码


public class ListAllBook extends JAXMServlet  implements ReqRespListener
{
    public void init    (ServletConfig servletConfig) throws ServletException 
    {
    	super.init(servletConfig);
    }
    public SOAPMessage onMessage    (SOAPMessage message)  
    { 
		System.out.println("from ListAllBook Servlet:receive a message");		
		try
		{
			System.out.println("from ListAllBook Servlet:");
			message.writeTo(System.out);//在控制台打印收到的消息	
             //调用其它类来实现业务方法		
            SOAPMessage msg=new XMLBusinessDelegate().listAllBook();
            System.out.println("this is the reply.....");
            msg.writeTo(System.out);
            msg.saveChanges();//注意,在返回消息之前要调用这个方法
            return msg;//返回消息
        }
        catch(Exception ex)
        {
        	ex.printStackTrace();
           //处理错误….
        	return null;
        }
        
    }
}

在这个例子里,由于接收到的消息不含任何参数,故没有对它进行处理,一般情况下,接收的消息是有参数的,并且服务端需要使用这个参数来调用业务层组件,如当用户按类别查找图书时,服务端需要获得类别的名字,例程7是按类别查找图书的服务端Servlet的部分代码,我们看服务端是怎么获得客户端的请求参数。

例程7 ListByCategory的部分代码


public SOAPMessage onMessage  (SOAPMessage message)  
    { 
		try
		{
		    …
			SOAPEnvelope env=message.getSOAPPart().getEnvelope();
			Iterator it=env.getBody().getChildElements(
env.createName("books","GetBookByCategory","http://hellking.webservice.com"));
			SOAPElement books=(SOAPElement)it.next();
			
			Iterator it2=books.getChildElements(
env.createName("category","GetBookByCategory","http://hellking.webservice.com"));
			String category=((SOAPElement)it2.next()).getValue();
              //这里的category是从SOAP 消息中读出的参数
			SOAPMessage msg=new XMLBusinessDelegate().listByCategory(category);
             msg.saveChanges();
            return msg;//返回处理消息
        }
        catch(Exception ex)
        {
        	ex.printStackTrace();
         //处理错误….
        	return null;//如果出错,一般返回SOAPFault,这里简化了。
        }
    }

SAAJ API为解析SOAP 消息提供了很好的支持,注意上面的黑体子,它是读取获得查找图书类别参数category的代码,这个参数是客户端设置的,在以后我们将看到客户端怎么设置这个参数。

总结一下,JAXM服务端Servlet处理消息的步骤是:

  1. 获得消息(onMessage)
  2. 读取消息中需要的参数
  3. 利用参数调用对应的业务处理
  4. 构建响应SOAP消息
  5. 返回处理后的消息

从上面的例子可以看出,JAXM服务端Servlet并没有处理具体的业务,而是把业务处理交给一个叫XMLBusinessDelegate业务代表的类来处理。

我们来看一下XMLBusinessDelegate是怎么来进行业务处理的。如例程8所示。

例程8 XMLBusinessDelegate的部分代码


…
public class XMLBusinessDelegate
{
InitialContext init=null;
	 BookServiceFacadeHome facadeHome;
OTDEngine otd;//对象到数据的转换器
	public XMLBusinessDelegate()throws NamingException
	{
	  init=this.getInitialContext();
otd=new BeanToSOAPEngine(); //生成对象到数据转换器实例       
	}
	public static InitialContext getInitialContext()  throws javax.naming.NamingException 
   {
      //更据不同的EJB容器,使用不同的url和连接工厂来获得上下文,然后返回…
}
//业务方法,按类别查找图书
public SOAPMessage listByCategory(String category)
   {
   		try
		{
			
	   Object objref = init.lookup("ejb/bookservicefacade");		
        facadeHome = (BookServiceFacadeHome)
javax.rmi.PortableRemoteObject.narrow(objref, BookServiceFacadeHome.class); 
			System.out.println("call jboss======>>");
			Collection result=facadeHome.create().findByCategory(category);//调用业务方法
			System.out.println("get result======>>");
			System.out.println(result.size());
		   // 使用BeanToSOAPEngine把调用结果转换成SOAP消息
otd.init(result,"GetAllBooks");
			SOAPMessage ret=otd.getResult();
			return ret;   	   	
	   }
	   catch(Exception e)
	   {
	   		e.printStackTrace();
	   		return null;
	   }
   }	
…	

可以看出,XMLBusinessDelegate只是调用EJB组件的业务方法,然后把构建SOAP消息的任务交给BeanToSOAPEngine,BeanToSOAPEngine是负责把包含了BookVO 的Collection转换成SOAP消息的专门的类。BeanToSOAPEngine的部分代码如例程9所示。

例程9 BeanToSOAPEngine的部分代码


…
public class BeanToSOAPEngine implements OTDEngine
{
	Collection bookVos;//要处理的信息
	SOAPMessage msg;//待返回的消息
	String type;//type为返回消息的名字空间,如GetBookByCategory
	public BeanToSOAPEngine()
	{		
		try
		{
			MessageFactory mf = MessageFactory.newInstance();//获得MessageFactory的实例
			msg = mf.createMessage();//从MessageFactory建立一个空的Message
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}	
	}
	public void init()
	{
this.bookVos=c;
		this.type=type;
	}
	public SOAPMessage getResult()
	{
		build();
		return msg;
	}
	public void build()
	{		
		try
		{			
	        SOAPPart part = msg.getSOAPPart();
	        SOAPEnvelope envelope = part.getEnvelope();
	        SOAPBody body = envelope.getBody();
            //创建body名字空间
	        Name bodyName=envelope.createName(type,"books","http://hellking.webservice.com"	);
		   SOAPBodyElement books=body.addBodyElement(bodyName);//增加body
		   
//以下程序把Collection中的BookVO转化成SOAP消息
Iterator it=bookVos.iterator();
		    while(it.hasNext())
		    {
		    	BookVO bookvo=(BookVO)it.next();	
             
               //构建book	    	
		    	Name bookName=envelope.createName(
"book","books","http://hellking.webservice.com");
				SOAPElement book=books.addChildElement(bookName);
				book.addAttribute(envelope.createName("id"),bookvo.getIsbn());
				
				//构建name
Name elName=envelope.createName(
"name","books","http://hellking.webservice.com");
				book.addChildElement(elName).addTextNode(bookvo.getName());
				
				//构建publisher
				Name elPublisher=envelope.createName(
"publisher","books","http://hellking.webservice.com");
				book.addChildElement(elPublisher).addTextNode(bookvo.getPublisher());
				
				//构建price
				Name elPrice=envelope.createName(
"price","books","http://hellking.webservice.com");
				book.addChildElement(elPrice).addTextNode(
new Float(bookvo.getPrice()).toString());
				
	              //构建category
				Name elCategory=envelope.createName(
"category","books","http://hellking.webservice.com");
				book.addChildElement(elCategory).addTextNode(bookvo.getCategory());

				//构建description
				Name elDescription=envelope.createName(
"description","books","http://hellking.webservice.com");
				book.addChildElement(elDescription).addTextNode(bookvo.getDescription());
				//author本来就是一个Collection,故要特别处理
				Collection author_c=bookvo.getAuthors();
				Iterator au_it=author_c.iterator();
				while(au_it.hasNext())
				{
					String strAuth=(String)au_it.next();
					//增加一个author
					Name elAuth=envelope.createName(
"author","books","http://hellking.webservice.com");
				    book.addChildElement(elAuth).addTextNode(strAuth);
			    }				
			}
			msg.writeTo(System.out);	//打印消息	
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}
}
…

按照上面的代码,BeanToSOAPEngine构建的SOAP消息应该和例程2的结构一致。

总结一下,构建SOAP消息时,按以下步骤进行:

  1. 获得MessageFactory 实例
  2. 利用MessageFactory 创建空的SOAPMessage
  3. 创建Header(可选)
  4. 创建body
    1. 创建body的名字空间
    2. 创建body的子元素
      1. 创建子元素名字空间
      2. 循环创建子子元素
      3. 把子元素增加到父元素里
    3. 往Message里增加body元素
  5. 创建附件(可选)

开发客户端
客户端和服务端通信,按以下的步骤进行:

  1. 创建 SOAP 连接
  2. 创建 SOAP 消息
  3. 在SOAP消息里增加数据
  4. 发送消息
  5. 对SOAP应答进行处理

下面我们就按照这个步骤来一步步讨论SOAP客户端开发种种问题。在本案例中,和服务端进行交互的客户端是通过一个叫JAXMDelegate的类来进行的。GUI客户程序通过调用JAXMDelegate来获得查询结果(具体的数据传输机制和设计模式见本系列第二篇文章)。首先我们来看怎么创建SOAP连接。

创建 SOAP 连接
由于我们使用的是点对点的消息发送模型,所以连接的类型是SOAPConnection。如例程10所示。

例程10 创建连接


package com.hellking.webservice;
import javax.xml.soap.*;
…
public class JAXMDelegate implements BookBusiness
{
	SOAPConnection con =null;
	EndpointLocator locator=new EndpointLocator();
	Collection allbook;//cache	
	public JAXMDelegate()
	{
		allbook=new ArrayList();
		try
		{
			SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
        	     con = scf.createConnection();
        }
        catch(Exception e)
        {
        	e.printStackTrace();
        }
    }
…

在上面的例子中,SOAPConnection是通过SOAPConnectionFactory来创建的。

创建 SOAP 消息
接下来是创建消息,在本案例中,有一个专门的方法来创建消息,如11所示。

例程11 创建消息


…
//target为目标名字空间,name为查询某本特定书的书字,category为要查询的类别
public SOAPMessage createMessage(String target,String name,String category) 
    {
    	try
    	{
    		    MessageFactory mf = MessageFactory.newInstance();
	         SOAPMessage msg = mf.createMessage();	
		    	SOAPPart sp = msg.getSOAPPart();
			
			SOAPEnvelope envelope = sp.getEnvelope();			
			//SOAPHeader hdr = envelope.createSOAPHeader();
			
			SOAPBody body = envelope.getBody();
			// AttachmentPart attachment = msg.createAttachmentPart();
msg.saveChanges();
			 …
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}		
	}
…

可以看出创建消息的步骤,首先通过MessageFactory来创建一个消息实例,然后依次创建SOAPEnvelope、SOAPHeader、SOAPBody、AttachmentPart,最后调用msg.saveChanges()来保存消息的变化。

在SOAP消息里增加数据
在SOAP消息里增加数据如例程12所示。

例程12 往SOAP消息里增加数据


//创建名字空间
Name bodyName=envelope.createName("books",target,"http://hellking.webservice.com");
		 //增加body元素
SOAPBodyElement gpp=body.addBodyElement(bodyName);
			
			if(category!=null)
			{
				//增加category
gpp.addChildElement("category").addTextNode(category);
			}
			
			if(name!=null)
			{
				gpp.addChildElement("name").addTextNode(name);
			}	
			msg.saveChanges();
			return msg;

如果例程11中传入的category参数不为空,那么创建好的消息结构应该如例程13所示。

例程13 客户端创建的消息的结构


<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <GetBookByCategory:books xmlns:GetBookByCategory="http://hellking.webservice.com">
   <GetBookByCategory:category>computer</GetBookByCategory:category>
  </GetBookByCategory:books>
 </soapenv:Body>
</soapenv:Envelope>

注意上面的computer为客户端要查询的参数。如果觉得上面填充消息的过程过于复杂,我们也可以从文件填充消息内容,如例程14所示。

例程14 从文件填充消息内容


import javax.xml.soap.SOAPElement;
import java.io.FileInputStream;
import javax.xml.transform.stream.StreamSource;
…
StreamSource preppedMsgSrc = new StreamSource( 
                 new FileInputStream("e://msgs//book_getBycategory.msg"));
        soapPart.setContent(preppedMsgSrc);

可以看出,从文件里填充消息比较快速。这两种方法各有所长,因为如果客户端的请求的种类特别多的话,特别是请求带有参数的话,那么我们要为每一种请求都预先写一个SOAP消息文件,可能是不符合实际的;但是如果客户端请求的类型比较固定,那么事先编写好SOAP消息然后再调用不失是一种好的选择。

发送消息
发送消息相对比较简单,首先要获得一个Endpoint,这个Endpoint就是要发送的消息的目标(也就是接收消息Servlet的url),然后就发送消息,如例程15所示。

例程15 发送SOAP消息


EndpointLocator locator=new EndpointLocator();
…
try
{
	SOAPMessage msg=createMessage("GetBookByCategory",null,category);	
     String endpoint=locator.getBookByCategory_Endpoint();
	SOAPMessage reply=con.call(msg , new URL(endpoint));
    …
}
…

EndpointLocator是终端定位器,调用getBookByCategory_Endpoint()它返回的结果将是"http://localhost:8080/jaxm_jaxrpc/listbycategory",这个地址就是ListByCategory Servlet的RUL,更据不同的设置来确定。由于使用的是点对点的消息发送模型,调用con.call()后返回的也是SOAPMessage。

对SOAP应答进行处理
接下来对消息进行处理,因为不管查询的结果如何,返回的消息都是例程2所示的结构,故我们使用一个专门的类来把SOAP消息转化成包含有BookVO的Collection,这个类是SOAPToBeanEngine,SOAPToBeanEngine的部分代码如例程16所示。

例程16 SOAPToBeanEngine的部分代码


package com.hellking.webservice;
import javax.xml.messaging.*;
…
public class SOAPToBeanEngine implements DTOEngine
{
	SOAPMessage reply;
	Collection bookVos;//转换后的结果,用Collection表示
     //构造方法,reply为要转换的SOAP消息
	public SOAPToBeanEngine(SOAPMessage reply)
	{
		this.reply=reply;
	}
	…
	public Collection getResult()
	{
		build();
		return bookVos;
	} 
    //build为具体转换的方法   	
    public void build()
    {
    	try
    	{
    		
	    	Collection ret=new ArrayList();
	    	//System.out.println(reply.getSOAPPart().getEnvelope().getBody().getElementName());
	    	Iterator child=reply.getSOAPPart().getEnvelope().getBody().getChildElements();①
	    	SOAPElement bookall=(SOAPElement)child.next();②
	    	Iterator books=bookall.getChildElements();③
	    	SOAPEnvelope env=reply.getSOAPPart().getEnvelope();
	    	
	    	SOAPElement temps;	    	
	    	Name name;
	    	
	    	while(books.hasNext())
	    	{
	    		
	    		BookVO bookVo=new BookVO();
	    		temps=(SOAPElement)books.next();
	    		String id=(String)(
	    			(temps.getAttributeValue(
	    				(Name)temps.getAllAttributes().next()
	    				                    )
	    			));
	    		bookVo.setIsbn(id);④
	    		name=env.createName("name","books","http://hellking.webservice.com"	);⑤
	    		bookVo.setName(
	    			((SOAPElement)temps.getChildElements(name).next())
	    			.getValue());
	    		//System.out.println(bookVo.getName());
	    		
	    		name=env.createName("price"	,"books", "http://hellking.webservice.com"	);
	    		bookVo.setPrice(Float.parseFloat(
	    			((SOAPElement)temps.getChildElements(name).next())
	    			.getValue()));
	    		
	    		name=env.createName("category", "books", "http://hellking.webservice.com"		);
	    		bookVo.setCategory(
	    			((SOAPElement)temps.getChildElements(name).next())
	    			.getValue());
	    			
	    		name=env.createName("publisher","books","http://hellking.webservice.com"		);
	    		bookVo.setPublisher(
	    			((SOAPElement)temps.getChildElements(name).next())
	    			.getValue());
	    		
	    		name=env.createName("description","books","http://hellking.webservice.com"		);
	    		bookVo.setDescription(
	    			((SOAPElement)temps.getChildElements(name).next())
	    			.getValue());
	    		//	bookVo.setDescription("kdjfkdjfj");
	    			
	    		name=env.createName("author","books","http://hellking.webservice.com"		);
	    		Collection au=new ArrayList();
	    		Iterator auth=temps.getChildElements(name);
	    		while(auth.hasNext())
	    		{
	    			
	    			SOAPElement tempp=(SOAPElement)auth.next();
	    			
	    			//System.out.println("author:"+tempp.getValue());
	    			au.add(tempp.getValue());
	    		}	    		
	    		bookVo.setAuthors(au);
	    		ret.add(bookVo);	    		
	    	}
	    	bookVos=ret;	      
	    }
	    catch(Exception ex)
	    {
	     	ex.printStackTrace();	  
			//错误处理…  	
	    }
	 }
}

注意上面例子中的①之类的标号,对应如图6所示的元素。

图6 SOAP消息

如上所示,在开发中,为了减少层之间的耦合性,我们一般不把SOAP消息直接发送到GUI客户端,而是先处理,把它转换成Java的基本数据类型或者Collection等类型。

GUI客户端
最后我们看一下GUI客户端怎么使用JAXMDelegate来调用业务。

例程17 BookClientGUI部分代码


…
public class BookClientGUI 
{
JTable table;//表格
…
int amount;
    Collection books;//表示的数据
     BookBusiness business;	
	JButton search,findAll,findByCategory;
	
	public BookClientGUI()
	{
		business=new JAXMDelegate();//生成一个JAXMDelegate,
		books=business.getAllBooks();//获得所有的图书信息
		//amount=books.size();
		//System.out.println(amount);
	}
	public static void main(String[] args)
	{		
		new BookClientGUI().go();
	}
	public void go()
	{
findByCategory=new JButton("按类别查找");
findByCategory.addMouseListener(new _MouseListener());
…
}
public void showResult()
{
		clearTable();
     	Iterator book_i=books.iterator();
    …
}
public void clearTable()
{
…
}
class _MouseListener extends MouseAdapter
{
		public void mouseClicked(MouseEvent e)
		{
 if(((JButton)e.getSource()).getLabel().equals("按类别查找"))
			{
				 books=business.getBookByCategory((String)jTextField.getText());
				 showResult();
			}
    …
}
}

JAXMDelegate实现了BookBusiness接口,BookClientGUI持有BookBusiness的实例,它通过这个实例来获得信息。BookBusiness返回的信息都是java.util.Collection,这样,给我们编程带来了极大的便利性。

总结

本文结合一个具体的案例,介绍了使用JAXM来构造Web服务的方法。需要强调的是,如果使用点对点的消息发送模型,那么服务端Servlet必须实现ReqRespListener接口,onMessage()方法将是开发服务端Servlet的重点任务。客户端编程中,将按照以下步骤进行:

  • 创建 SOAP 连接
  • 创建 SOAP 消息
  • 在SOAP消息里增加数据
  • 发送消息
  • 对SOAP应答进行处理

下一步

经过以上的逐步的解释,相信读者对JAXM编程已经有一个比较深入发了解。您可以在这里。

可以看出,通过使用一定的设计模式和接口,我们可以减少各层之间的耦合,在下一篇中,我将继续深入分析JAXM设计的体系结构和模式。

参考资料

 

关于作者
陈亚强的照片 陈亚强:北京华园天一科技有限公司高级软件工程师,擅长J2EE技术,曾参与多个J2EE项目的设计和开发,对Web服务有很大的兴趣并且有一定的项目经验。热爱学习,喜欢新技术。即将由电子工业出版社出版的《J2EE企业应用开发》正在最终定稿阶段,目前正从事J2EE方面的开发和J2EE Web服务方面的图书写作。您可以通过 cyqcims@mail.tsinghua

 

 

版权所有:UML软件工程组织