級(jí)別: 初級(jí)
陳亞強(qiáng), 高級(jí)軟件工程師
2003 年 6 月 01 日
本文介紹JAXM Web服務(wù)開發(fā)的基本概念,然后結(jié)合一個(gè)具體的案例來(lái)介紹使用JAXM開發(fā)Web服務(wù)中要使用的編程技術(shù)和編程技巧。
閱讀本文前您需要以下的知識(shí)和工具:
JavaTM Web Services Developer Pack 1.1,并且會(huì)使用初步使用;
至少會(huì)使用一種EJB容器來(lái)開發(fā)、部署EJB,并且了解怎么在客戶端訪問EJB組件;
一般的Java編程知識(shí)。
在J2EE平臺(tái)里,要開發(fā)一個(gè)Web服務(wù),我們通常有兩種選擇:
使用JAX-RPC(Java API for XML-based RPC)
使用JAXM(Java API for XML Messaging)
作為對(duì)JAXM開發(fā)技術(shù)的入門,本文先不比較它們的技術(shù)特點(diǎn)。我將結(jié)合一個(gè)具體的案例來(lái)討論JAXM的開發(fā)技術(shù)方方面面。
本文的參考資料見 參考資料
本文的全部代碼在這里 下載
JAXM相關(guān)概念介紹
通常我們說(shuō)的JAXM API,它包括兩個(gè)包:
Javax.xml.soap:它是發(fā)送SOAP消息的基本包,主要包含了發(fā)送帶有附件的SOAP消息的API(SOAP with Attachments API for Java ,SAAJ)。它是SOAP消息的基本包,它為構(gòu)建SOAP包和解析SOAP包提供了重要的支持。它包含了發(fā)送請(qǐng)求-響應(yīng)消息相關(guān)的API。
Javax.xml.messaging:定義了JAXM的規(guī)范,包含了發(fā)送和接收消息所需的API。
JAXM包含了以下幾個(gè)概念:消息(Message)、連接(Connection)、消息提供者(Messaging providers)。
消息
JAXM消息遵循SOAP標(biāo)準(zhǔn),我們可以通過(guò)JAXM API方便的創(chuàng)建SOAP 消息。有兩種類型的消息,帶附件的消息和不帶附加的消息。不帶附件的消息結(jié)構(gòu)如圖1所示。
如圖1所示,在SAAJ API中,它使用SOAPMessage類來(lái)代表SOAPMessage,相應(yīng)的,使用SOAPPart類來(lái)代表SOAPPart,SOAPBody類代表SOAP Body。
圖1 不帶附件的SOAP消息
其中Header和SOAPFault是可選的,Header可以多個(gè),Body只有一個(gè),如果有SOAP Fault,那么它一定在SOAP Body后面。帶附加的SOAP消息如圖2所示。
圖2 帶附件SOAP消息
可以看出,一個(gè)SOAP消息可以有一個(gè)或者多個(gè)附件。SAAJ API使用AttachmentPart類來(lái)代表SOAP消息的附件。每個(gè)AttachmentPart有一個(gè)MIME Header來(lái)表示附件的類型。
連接
有兩種類型的連接,它們是:
消息發(fā)送者到接收者的直接連接,javax.xml.soap.SOAPConnection表示了這種類型的連接,由于它是點(diǎn)對(duì)點(diǎn)的,所以比較容易使用,即使不在Servlet或者J2EE容器里也能使用;
到消息提供者的連接,javax.xml.messaging.ProviderConnection表示了這種連接,這種方式需要消息提供者,消息發(fā)送者和消息使用者通過(guò)消息提供者來(lái)交互。
消息提供者
消息提供者主要負(fù)責(zé)傳送消息,它把消息路由到目的地,一個(gè)消息發(fā)出后,可能要經(jīng)過(guò)多個(gè)消息提供者才能到達(dá)目的地。
如果使用MessageProvider,可以達(dá)到以下的目的:
除了能夠發(fā)送request-response類型的消息外,還可以發(fā)送One-way(單向)消息;
(消息)客戶端有時(shí)也可以作為服務(wù)端來(lái)使用。
案例介紹
在本文,我將結(jié)合一個(gè)具體的案例來(lái)介紹JAXM Web服務(wù)的開發(fā)。此案例具體情況如下。
某圖書城決定使用Web服務(wù)來(lái)對(duì)外提供圖書信息查詢服務(wù),圖書城現(xiàn)有的系統(tǒng)運(yùn)行在J2EE平臺(tái)上,客戶端通過(guò)JAXM來(lái)使用圖書城提供的Web服務(wù)。系統(tǒng)的體系結(jié)構(gòu)如圖3所示:
圖3 系統(tǒng)體系結(jié)構(gòu)
客戶端可以是一般的java GUI程序(當(dāng)然也可以是JSP、Servlet等)。客戶端通過(guò)SOAP消息和Servlet容器里運(yùn)行的JAXM Servlet進(jìn)行交互,JAXM Servlet是服務(wù)提供者,EJB容器里運(yùn)行的是業(yè)務(wù)組件,它們?yōu)镴AXM Servlet提供服務(wù)。
客戶端請(qǐng)求傳遞的過(guò)程如圖4所示:
圖4 請(qǐng)求傳遞的過(guò)程
可以看出,客戶端通過(guò)SOAP和JAXM 服務(wù)端通信,JAXM使用EJB組件來(lái)獲得業(yè)務(wù)服務(wù)。
系統(tǒng)為客戶端提供了三種查詢服務(wù):查詢所有圖書,按類別查詢圖書,按圖書名搜索某本特定的圖書。這三種服務(wù)分別有服務(wù)端的三個(gè)JAXM Servlet實(shí)現(xiàn)。它們是:
ListAllBook:查詢所有的圖書信息;
ListByCategory:按類別查詢圖書信息;
BookDetail:查詢某個(gè)特定名稱的圖書信息。
客戶端是用Swing編寫的GUI界面,使用界面如圖5所示。
圖5 客戶端界面
客戶端和服務(wù)端傳輸圖書信息時(shí)采用例程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>
|
這個(gè)信息包含在SOAP消息的Body里,按照這個(gè)格式,傳輸?shù)腟OAP消息結(jié)構(gòu)如例程2所示。
例程2 傳輸?shù)腟OAP消息的格式(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企業(yè)應(yīng)用開發(fā)</books:name>
<books:publisher>電子工業(yè)出版社</books:publisher>
<books:price>60</books:price>
<books:category>計(jì)算機(jī)類</books:category>
<books:description>非常好的介紹J2EE企業(yè)應(yīng)用開發(fā)的書</books:description>
<books:author>陳亞強(qiáng)</books:author>
<books:author>劉曉華</books:author>
</books:book>
</books:GetAllBooks>
</soap-env:Body>
</soap-env:Envelope>
|
為了傳輸數(shù)據(jù)的便利,我把圖書信息用一個(gè)專門的值對(duì)象來(lái)表示,如例程3所示。
例程3 BookVO值對(duì)象
package com.hellking.webservice;
import java.util.Collection;
public class BookVO implements java.io.Serializable
{
private String name;//圖書名字
private String publisher;//圖書出版社
private float price;//圖書價(jià)格
private String isbn;//圖書ISBN
private String description;//圖書的簡(jiǎn)介
private String category;//圖書的類別
private Collection authors;//圖書的作者,因一本書可以有多個(gè)作者,故把它表示成Collection。
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
…其它的getter和setter方法
|
可以看出,BookVO其實(shí)是和例程1中的DTD是對(duì)應(yīng)的。需要指出的是,BookVO可以使用JAXB(Java API for XML Binding)中的工具來(lái)生成。
EJB組件介紹
本案例使用了兩個(gè)EJB組件,它們分別是BookEntityEJB和BookServiceFacadeEJB。其中BookEntityEJB是實(shí)體Bean,它代表了每本書的詳細(xì)信息;BookServiceFacadeEJB為有狀態(tài)會(huì)話Bean,它是一個(gè)會(huì)話門面,為JAXM Servlet提供業(yè)務(wù)服務(wù)。BookServiceFacadeEJB組件的遠(yuǎn)程接口如例程4所示。
例程4 BookServiceFacadeEJB組件的遠(yuǎn)程接口
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;
}
|
可以看出,它提供了三個(gè)業(yè)務(wù)服務(wù),分別是getAllBook(),findByCategory(String category),getBookDetail(String name)。這三個(gè)業(yè)務(wù)方法返回的都是java.util.Collection。其實(shí),getBookDetail( String name )方法返回的應(yīng)該是一個(gè)值對(duì)象,但是為了方便統(tǒng)一處理,也通過(guò)處理讓它返回java.util.Collection類型,這一點(diǎn)以后的代碼中體現(xiàn)出來(lái)。
在BookEntityEJB Home接口也提供了對(duì)應(yīng)的查找方法,如例程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;
}
|
開發(fā)服務(wù)端
下面開發(fā)服務(wù)端,我們前面說(shuō)過(guò),服務(wù)端共有三個(gè)JAXM Servlet,它們分別提供三種不同的查詢服務(wù)。
由于使用了點(diǎn)對(duì)點(diǎn)的消息模型,故服務(wù)端需要實(shí)現(xiàn)javax.xml.messaging. ReqRespListener接口,并且需要繼承javax.xml.messaging.JAXMServlet類。javax.xml.messaging.JAXMServlet是一個(gè)Servlet,它為開發(fā)消息服務(wù)的Servlet提供了一個(gè)框架。需要指出的是,javax.xml.messaging. ReqRespListener接口定義了一個(gè)
public SOAPMessage onMessage (SOAPMessage message)
|
方法,故我們開發(fā)的JAXM服務(wù)端Servlet必須實(shí)現(xiàn)這個(gè)方法。 onMessage 方法就是當(dāng)此Servlet接收到SOAPMessage時(shí)激發(fā)的方法,它通過(guò)此方法對(duì)外界提供服務(wù)(我們可以把這個(gè)方法簡(jiǎn)單的比喻成普通的HttpServlet中的doGet()、doPost()方法,HttpServlet正是通過(guò)doGet()、doPost()來(lái)為客戶端提供服務(wù))。
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);//在控制臺(tái)打印收到的消息
//調(diào)用其它類來(lái)實(shí)現(xiàn)業(yè)務(wù)方法
SOAPMessage msg=new XMLBusinessDelegate().listAllBook();
System.out.println("this is the reply.....");
msg.writeTo(System.out);
msg.saveChanges();//注意,在返回消息之前要調(diào)用這個(gè)方法
return msg;//返回消息
}
catch(Exception ex)
{
ex.printStackTrace();
//處理錯(cuò)誤….
return null;
}
}
}
|
在這個(gè)例子里,由于接收到的消息不含任何參數(shù),故沒有對(duì)它進(jìn)行處理,一般情況下,接收的消息是有參數(shù)的,并且服務(wù)端需要使用這個(gè)參數(shù)來(lái)調(diào)用業(yè)務(wù)層組件,如當(dāng)用戶按類別查找圖書時(shí),服務(wù)端需要獲得類別的名字,例程7是按類別查找圖書的服務(wù)端Servlet的部分代碼,我們看服務(wù)端是怎么獲得客戶端的請(qǐng)求參數(shù)。
例程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 消息中讀出的參數(shù)
SOAPMessage msg=new XMLBusinessDelegate().listByCategory(category);
msg.saveChanges();
return msg;//返回處理消息
}
catch(Exception ex)
{
ex.printStackTrace();
//處理錯(cuò)誤….
return null;//如果出錯(cuò),一般返回SOAPFault,這里簡(jiǎn)化了。
}
}
|
SAAJ API為解析SOAP 消息提供了很好的支持,注意上面的黑體子,它是讀取獲得查找圖書類別參數(shù)category的代碼,這個(gè)參數(shù)是客戶端設(shè)置的,在以后我們將看到客戶端怎么設(shè)置這個(gè)參數(shù)。
總結(jié)一下,JAXM服務(wù)端Servlet處理消息的步驟是:
- 獲得消息(onMessage)
- 讀取消息中需要的參數(shù)
- 利用參數(shù)調(diào)用對(duì)應(yīng)的業(yè)務(wù)處理
- 構(gòu)建響應(yīng)SOAP消息
- 返回處理后的消息
從上面的例子可以看出,JAXM服務(wù)端Servlet并沒有處理具體的業(yè)務(wù),而是把業(yè)務(wù)處理交給一個(gè)叫XMLBusinessDelegate業(yè)務(wù)代表的類來(lái)處理。
我們來(lái)看一下XMLBusinessDelegate是怎么來(lái)進(jìn)行業(yè)務(wù)處理的。如例程8所示。
例程8 XMLBusinessDelegate的部分代碼
…
public class XMLBusinessDelegate
{
InitialContext init=null;
BookServiceFacadeHome facadeHome;
OTDEngine otd;//對(duì)象到數(shù)據(jù)的轉(zhuǎn)換器
public XMLBusinessDelegate()throws NamingException
{
init=this.getInitialContext();
otd=new BeanToSOAPEngine(); //生成對(duì)象到數(shù)據(jù)轉(zhuǎn)換器實(shí)例
}
public static InitialContext getInitialContext() throws javax.naming.NamingException
{
//更據(jù)不同的EJB容器,使用不同的url和連接工廠來(lái)獲得上下文,然后返回…
}
//業(yè)務(wù)方法,按類別查找圖書
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);//調(diào)用業(yè)務(wù)方法
System.out.println("get result======>>");
System.out.println(result.size());
// 使用BeanToSOAPEngine把調(diào)用結(jié)果轉(zhuǎn)換成SOAP消息
otd.init(result,"GetAllBooks");
SOAPMessage ret=otd.getResult();
return ret;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
…
|
可以看出,XMLBusinessDelegate只是調(diào)用EJB組件的業(yè)務(wù)方法,然后把構(gòu)建SOAP消息的任務(wù)交給BeanToSOAPEngine,BeanToSOAPEngine是負(fù)責(zé)把包含了BookVO 的Collection轉(zhuǎn)換成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的實(shí)例
msg = mf.createMessage();//從MessageFactory建立一個(gè)空的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();
//創(chuàng)建body名字空間
Name bodyName=envelope.createName(type,"books","http://hellking.webservice.com" );
SOAPBodyElement books=body.addBodyElement(bodyName);//增加body
//以下程序把Collection中的BookVO轉(zhuǎn)化成SOAP消息
Iterator it=bookVos.iterator();
while(it.hasNext())
{
BookVO bookvo=(BookVO)it.next();
//構(gòu)建book
Name bookName=envelope.createName(
"book","books","http://hellking.webservice.com");
SOAPElement book=books.addChildElement(bookName);
book.addAttribute(envelope.createName("id"),bookvo.getIsbn());
//構(gòu)建name
Name elName=envelope.createName(
"name","books","http://hellking.webservice.com");
book.addChildElement(elName).addTextNode(bookvo.getName());
//構(gòu)建publisher
Name elPublisher=envelope.createName(
"publisher","books","http://hellking.webservice.com");
book.addChildElement(elPublisher).addTextNode(bookvo.getPublisher());
//構(gòu)建price
Name elPrice=envelope.createName(
"price","books","http://hellking.webservice.com");
book.addChildElement(elPrice).addTextNode(
new Float(bookvo.getPrice()).toString());
//構(gòu)建category
Name elCategory=envelope.createName(
"category","books","http://hellking.webservice.com");
book.addChildElement(elCategory).addTextNode(bookvo.getCategory());
//構(gòu)建description
Name elDescription=envelope.createName(
"description","books","http://hellking.webservice.com");
book.addChildElement(elDescription).addTextNode(bookvo.getDescription());
//author本來(lái)就是一個(gè)Collection,故要特別處理
Collection author_c=bookvo.getAuthors();
Iterator au_it=author_c.iterator();
while(au_it.hasNext())
{
String strAuth=(String)au_it.next();
//增加一個(gè)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構(gòu)建的SOAP消息應(yīng)該和例程2的結(jié)構(gòu)一致。
總結(jié)一下,構(gòu)建SOAP消息時(shí),按以下步驟進(jìn)行:
- 獲得MessageFactory 實(shí)例
- 利用MessageFactory 創(chuàng)建空的SOAPMessage
- 創(chuàng)建Header(可選)
- 創(chuàng)建body
- 創(chuàng)建body的名字空間
- 創(chuàng)建body的子元素
- 創(chuàng)建子元素名字空間
- 循環(huán)創(chuàng)建子子元素
- 把子元素增加到父元素里
- 往Message里增加body元素
- 創(chuàng)建附件(可選)
開發(fā)客戶端
客戶端和服務(wù)端通信,按以下的步驟進(jìn)行:
- 創(chuàng)建 SOAP 連接
- 創(chuàng)建 SOAP 消息
- 在SOAP消息里增加數(shù)據(jù)
- 發(fā)送消息
- 對(duì)SOAP應(yīng)答進(jìn)行處理
下面我們就按照這個(gè)步驟來(lái)一步步討論SOAP客戶端開發(fā)種種問題。在本案例中,和服務(wù)端進(jìn)行交互的客戶端是通過(guò)一個(gè)叫JAXMDelegate的類來(lái)進(jìn)行的。GUI客戶程序通過(guò)調(diào)用JAXMDelegate來(lái)獲得查詢結(jié)果(具體的數(shù)據(jù)傳輸機(jī)制和設(shè)計(jì)模式見本系列第二篇文章)。首先我們來(lái)看怎么創(chuàng)建SOAP連接。
創(chuàng)建 SOAP 連接
由于我們使用的是點(diǎn)對(duì)點(diǎn)的消息發(fā)送模型,所以連接的類型是SOAPConnection。如例程10所示。
例程10 創(chuàng)建連接
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是通過(guò)SOAPConnectionFactory來(lái)創(chuàng)建的。
創(chuàng)建 SOAP 消息
接下來(lái)是創(chuàng)建消息,在本案例中,有一個(gè)專門的方法來(lái)創(chuàng)建消息,如11所示。
例程11 創(chuàng)建消息
…
//target為目標(biāo)名字空間,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;
}
}
…
|
可以看出創(chuàng)建消息的步驟,首先通過(guò)MessageFactory來(lái)創(chuàng)建一個(gè)消息實(shí)例,然后依次創(chuàng)建SOAPEnvelope、SOAPHeader、SOAPBody、AttachmentPart,最后調(diào)用msg.saveChanges()來(lái)保存消息的變化。
在SOAP消息里增加數(shù)據(jù)
在SOAP消息里增加數(shù)據(jù)如例程12所示。
例程12 往SOAP消息里增加數(shù)據(jù)
//創(chuàng)建名字空間
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參數(shù)不為空,那么創(chuàng)建好的消息結(jié)構(gòu)應(yīng)該如例程13所示。
例程13 客戶端創(chuàng)建的消息的結(jié)構(gòu)
<?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為客戶端要查詢的參數(shù)。如果覺得上面填充消息的過(guò)程過(guò)于復(fù)雜,我們也可以從文件填充消息內(nèi)容,如例程14所示。
例程14 從文件填充消息內(nèi)容
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);
|
可以看出,從文件里填充消息比較快速。這兩種方法各有所長(zhǎng),因?yàn)槿绻蛻舳说恼?qǐng)求的種類特別多的話,特別是請(qǐng)求帶有參數(shù)的話,那么我們要為每一種請(qǐng)求都預(yù)先寫一個(gè)SOAP消息文件,可能是不符合實(shí)際的;但是如果客戶端請(qǐng)求的類型比較固定,那么事先編寫好SOAP消息然后再調(diào)用不失是一種好的選擇。
發(fā)送消息
發(fā)送消息相對(duì)比較簡(jiǎn)單,首先要獲得一個(gè)Endpoint,這個(gè)Endpoint就是要發(fā)送的消息的目標(biāo)(也就是接收消息Servlet的url),然后就發(fā)送消息,如例程15所示。
例程15 發(fā)送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是終端定位器,調(diào)用getBookByCategory_Endpoint()它返回的結(jié)果將是"http://localhost:8080/jaxm_jaxrpc/listbycategory",這個(gè)地址就是ListByCategory Servlet的RUL,更據(jù)不同的設(shè)置來(lái)確定。由于使用的是點(diǎn)對(duì)點(diǎn)的消息發(fā)送模型,調(diào)用con.call()后返回的也是SOAPMessage。
對(duì)SOAP應(yīng)答進(jìn)行處理
接下來(lái)對(duì)消息進(jìn)行處理,因?yàn)椴还懿樵兊慕Y(jié)果如何,返回的消息都是例程2所示的結(jié)構(gòu),故我們使用一個(gè)專門的類來(lái)把SOAP消息轉(zhuǎn)化成包含有BookVO的Collection,這個(gè)類是SOAPToBeanEngine,SOAPToBeanEngine的部分代碼如例程16所示。
例程16 SOAPToBeanEngine的部分代碼
package com.hellking.webservice;
import javax.xml.messaging.*;
…
public class SOAPToBeanEngine implements DTOEngine
{
SOAPMessage reply;
Collection bookVos;//轉(zhuǎn)換后的結(jié)果,用Collection表示
//構(gòu)造方法,reply為要轉(zhuǎn)換的SOAP消息
public SOAPToBeanEngine(SOAPMessage reply)
{
this.reply=reply;
}
…
public Collection getResult()
{
build();
return bookVos;
}
//build為具體轉(zhuǎn)換的方法
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();
//錯(cuò)誤處理…
}
}
}
|
注意上面例子中的①之類的標(biāo)號(hào),對(duì)應(yīng)如圖6所示的元素。
圖6 SOAP消息
如上所示,在開發(fā)中,為了減少層之間的耦合性,我們一般不把SOAP消息直接發(fā)送到GUI客戶端,而是先處理,把它轉(zhuǎn)換成Java的基本數(shù)據(jù)類型或者Collection等類型。
GUI客戶端
最后我們看一下GUI客戶端怎么使用JAXMDelegate來(lái)調(diào)用業(yè)務(wù)。
例程17 BookClientGUI部分代碼
…
public class BookClientGUI
{
JTable table;//表格
…
int amount;
Collection books;//表示的數(shù)據(jù)
BookBusiness business;
JButton search,findAll,findByCategory;
public BookClientGUI()
{
business=new JAXMDelegate();//生成一個(gè)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實(shí)現(xiàn)了BookBusiness接口,BookClientGUI持有BookBusiness的實(shí)例,它通過(guò)這個(gè)實(shí)例來(lái)獲得信息。BookBusiness返回的信息都是java.util.Collection,這樣,給我們編程帶來(lái)了極大的便利性。
總結(jié)
本文結(jié)合一個(gè)具體的案例,介紹了使用JAXM來(lái)構(gòu)造Web服務(wù)的方法。需要強(qiáng)調(diào)的是,如果使用點(diǎn)對(duì)點(diǎn)的消息發(fā)送模型,那么服務(wù)端Servlet必須實(shí)現(xiàn)ReqRespListener接口,onMessage()方法將是開發(fā)服務(wù)端Servlet的重點(diǎn)任務(wù)。客戶端編程中,將按照以下步驟進(jìn)行:
- 創(chuàng)建 SOAP 連接
- 創(chuàng)建 SOAP 消息
- 在SOAP消息里增加數(shù)據(jù)
- 發(fā)送消息
- 對(duì)SOAP應(yīng)答進(jìn)行處理
下一步
經(jīng)過(guò)以上的逐步的解釋,相信讀者對(duì)JAXM編程已經(jīng)有一個(gè)比較深入發(fā)了解。您可以在這里。
可以看出,通過(guò)使用一定的設(shè)計(jì)模式和接口,我們可以減少各層之間的耦合,在下一篇中,我將繼續(xù)深入分析JAXM設(shè)計(jì)的體系結(jié)構(gòu)和模式。 |