使用jboss netty 創(chuàng)建高性能webservice客戶端及服務(wù)端
使用jboss netty 創(chuàng)建高性能webservice客戶端及服務(wù)端
通過本文,讀者將了解以下內(nèi)容
(1)利用jboss netty創(chuàng)建一個(gè)高性能的web服務(wù)客戶端
(2)不使用任何第三方框架,手工在web容器內(nèi)創(chuàng)建webservice服務(wù)器端
在不依賴任何webservice框架的情況下,輕量級(jí)的實(shí)現(xiàn)這兩個(gè)目的,并且使你擁有更多的控制及定制能力。甚至可以越過soap協(xié)議的限制,使用你自己喜歡的自定義的消息格式來傳遞xml消息。
想象這樣一個(gè)情況:一個(gè)項(xiàng)目中使用apache cxf作為webservice使用的框架,消息的發(fā)送和接受要經(jīng)過漫長(zhǎng)的cxf處理管線。雖然cxf性能不錯(cuò),但是如果項(xiàng)目要求webservice交互的數(shù)量是每天數(shù)以百萬計(jì)呢?還有,在webservice的開發(fā)中總有一些令人煩惱的需求,比如同你交互的廠商技術(shù)不規(guī)范,而你又不得不遷就它的接口(國(guó)企實(shí)際情況,我必須和另外一家廠商的企業(yè)服務(wù)總線傳遞消息,而它的wsdl文件甚至無法通過schema驗(yàn)證),從而使你的cxf或axis不停報(bào)錯(cuò)。這時(shí)你要怎么辦呢?
另外,還有一些特殊要求。比如有的服務(wù)端要求你加上一些特殊的soap協(xié)議頭用來進(jìn)行認(rèn)證授權(quán)。雖然所有的開源框架都支持這樣做,但無論怎樣都是一件麻煩的事情。
有沒有一個(gè)方法可以給web服務(wù)開發(fā)以更大的定制性及更簡(jiǎn)化的開發(fā)方式呢?
整篇文章分三個(gè)部分,首先介紹一個(gè)xml解析類,用于在xml數(shù)據(jù)模型和java類型之間進(jìn)行轉(zhuǎn)換。然后在這個(gè)基礎(chǔ)上,介紹了如何使用netty創(chuàng)建高性能web服務(wù)客戶端。最后介紹自定義webservice服務(wù)器端的方法,算是對(duì)j2ee初學(xué)者的一個(gè)教學(xué)和啟示。
一.Xml解析器
自己實(shí)現(xiàn)webservice框架,少不了同xml打交道。我首先考慮是性能和靈活性。市面上的開源項(xiàng)目沒有能夠滿足要求的。于是自己寫了一個(gè)相對(duì)簡(jiǎn)單的解析器來解決問題。解析器有兩種解析方式(1)將xml轉(zhuǎn)換為java數(shù)據(jù)類型。為了提高速度只是解析為map和list, 并不能夠解析為bean (2)提供一個(gè)回調(diào)接口,支持更靈活的從xml提取感興趣的信息。
例如,解析如下報(bào)文
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://yourcompany.com">
<soapenv:Header/>
<soapenv:Body>
<com:requset>
<com:message>item1</com:message>
<com:message>item2</com:message>
<com:action>insert</com:action>
</com:requset>
<com:requset>
<com:action>delete</com:action>
</com:requset>
</soapenv:Body>
</soapenv:Envelope>
XMLTranslater.xmlToJava(xmlStr,new XmlCallback(){
public void onTagEnd(String tagName, Map<String, Object> attrs) {
System.out.println(tagName+"---"+attrs);
}
public void onTagStart(String tagName, Map<String, Object> attrs) {
}
});
經(jīng)解析后結(jié)果如下,讀者注意看以下的調(diào)試信息
soapenv:Header---{}
com:message---{@value=item1}
com:message---{@value=item2}
com:action---{@value=insert}
com:requset---{com:message=[item1, item2], com:action=insert}
com:action---{@value=delete}
com:requset---{com:action=delete}
soapenv:Body---{com:requset=[{com:message=[item1, item2], com:action=insert}, {com:action=delete}]}
soapenv:Envelope---{xmlns:soapenv@attr=http://schemas.xmlsoap.org/soap/envelope/, xmlns:com@attr=http://yourcompany.com, soapenv:Header={}, soapenv:Body={com:requset=[{com:message=[item1, item2], com:action=insert}, {com:action=delete}]}}
每個(gè)xml元素都會(huì)被解析為map
(1) 文本節(jié)點(diǎn)會(huì)被解析為:key為“@value”的鍵值對(duì),所有的xml屬性作為key時(shí)在末尾都會(huì)加上@attr后綴。
(2) 子節(jié)點(diǎn)會(huì)作為父節(jié)點(diǎn)屬性存在(也是作為map的一個(gè)key-value對(duì)),其key值為子節(jié)點(diǎn)元素名,因?yàn)?/span>sax解析并不能處理命名空間及前綴,解析出來節(jié)點(diǎn)名和屬性都帶著命名空間前綴,但這樣做的好處是提高了解析的效率。
(3) 如果同名子元素出現(xiàn)兩次,它將被封裝為list,而且如果其中某個(gè)子元素只有文本子節(jié)點(diǎn),這個(gè)子元素將被它內(nèi)嵌的文本子節(jié)點(diǎn)代替,如下所示:
<com:requset>
<com:message>item1</com:message>
<com:message>item2</com:message>
<com:action>insert</com:action>
</com:requset>
com:requset---{com:message=[item1, item2], com:action=insert}
另外,xmlToJava方法是有返回值的,返回xml的根元素解析出來的map對(duì)象
接口簡(jiǎn)明扼要,相信不需要我過多講解,直接看示例和源代碼即可。
現(xiàn)在,我們可以靈活高效的解析xml消息了,那么接下來我將介紹如何使用netty構(gòu)建webservice客戶端。
二.使用netty構(gòu)建webservice客戶端
通常,企業(yè)內(nèi)部的消息傳遞是遵循“接口簡(jiǎn)單,業(yè)務(wù)復(fù)雜”的原則來規(guī)劃的。也就是服務(wù)方法通常只有2,3個(gè)參數(shù),每個(gè)參數(shù)都是字符串型,用來封裝復(fù)雜的業(yè)務(wù)數(shù)據(jù)(xml或json格式)。這樣在業(yè)務(wù)的變化的時(shí)候是不用修改接口的,這就使得不同廠家之間服務(wù)的對(duì)接,調(diào)試,測(cè)試的工作不用再進(jìn)行一次,大大提高了對(duì)業(yè)務(wù)需求變化的響應(yīng)速度。
從“模型(model)”生成后退一步是“模板(template)”生成,很多框架都是如此。
我們來看一個(gè)實(shí)際的例子,是從真實(shí)項(xiàng)目日志中截取出來的消息負(fù)載
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://yourcompany"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:Requset>
<in0>request…</in0>
</ns1:Requset>
</soapenv:Body>
</soapenv:Envelope>
響應(yīng)如下:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<ns1:NotifyResponse xmlns:ns1="http://yourcompany">
<ns1:out>
Reponse…
</ns1:out>
</ns1:NotifyResponse>
</soap:Body>
</soap:Envelope>
因?yàn)槠年P(guān)系wsdl文件從略。
上面的例子采取的是所謂document-litaral-wrapped消息風(fēng)格(注意最后的wrapped,如果是unwrapped,消息結(jié)構(gòu)會(huì)不同,而且一般不用unwrapped風(fēng)格)。這種風(fēng)格也是一種推薦風(fēng)格,大部分的廠商都是使用這種風(fēng)格(其他風(fēng)格當(dāng)然也很容易支持)。從這種消息風(fēng)格中,我們其實(shí)可以找出規(guī)律形成模板,具體參數(shù)通過對(duì)字符串模板替換即可。
模板如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="${nameSpaceUri}" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body>${message}</soapenv:Body></soapenv:Envelope>
可以看到具體消息的部分${message}是在代碼里組裝而成的,掌握了規(guī)律后,手工拼出soap請(qǐng)求報(bào)文不是什么難事,具體實(shí)現(xiàn)看源代碼即可,這里不再贅述。
客戶端使用的例子如下:
BootstrapHolder holder = new BootstrapHolder();
holder.initMethod();
try {
NettyClient client = new NettyClient();
client.setHolder(holder);
client.setEndpoint("http://someurl");
client.setNameSpaceUri("http://yourCompany");
//注意設(shè)置wrapped風(fēng)格
client.setIsWrapped(true);
Object wsReturn = client.invoke("methodName", "paramName","paramValue");
} catch (Exception e) {
e.printStackTrace();
}
holder.destroyMethod();
這是調(diào)用一個(gè)單參數(shù)的webservice接口,返回值是soap:Body里中xml子節(jié)點(diǎn)轉(zhuǎn)換成的java對(duì)象。當(dāng)然框架還提供了一個(gè)多參數(shù)的方法,另外,可以對(duì)client設(shè)置一個(gè)xmlcallback回調(diào)函數(shù),來更加靈活的從服務(wù)器端返回消息中提取有用數(shù)據(jù)。
注意在netty編程中要注意如下問題:
(1)BootstrapHolder對(duì)象是單例的,所有的client都應(yīng)該共享這個(gè)對(duì)象。
(2)我一開始用的netty3.2.7final,同一個(gè)jetty server交互時(shí)出現(xiàn)了問題。出現(xiàn)了在連接真正關(guān)閉之前channel就已經(jīng)關(guān)閉的情況。具體原因是使用了如下方式channel.getCloseFuture().await()來同步socket的關(guān)閉事件。現(xiàn)在HttpResponseHandler中,使用了一個(gè)CountDownLatch來進(jìn)行同步解決了這個(gè)問題。
在webservice的項(xiàng)目開發(fā)中經(jīng)常出現(xiàn)一些特殊要求。比如對(duì)方提出要加一個(gè)soap header來進(jìn)行認(rèn)證和授權(quán),這個(gè)需求只需簡(jiǎn)單的修改模板(當(dāng)然源碼也得改,將字符串替換的邏輯加進(jìn)去)就可以實(shí)現(xiàn)了,是不是很簡(jiǎn)單啊!
Client調(diào)用的接口是同步的,要想進(jìn)一步提高性能需要做以下工作
(1) 將接口改成異步的,可以在高并發(fā)的情況下提高性能,節(jié)省資源消耗。
(2) 將消息格式改成非xml的,比如json或其他數(shù)據(jù)格式的.但是不同項(xiàng)目之間(不同廠家)的消息交互還是soap xml最通用。
三.在web容器內(nèi)手工創(chuàng)建webservice服務(wù)器端
實(shí)際上就是在web容器內(nèi)實(shí)現(xiàn)一個(gè)servlet即可,比如在web.xml中加如下配置
<servlet>
<servlet-name>xmlMessage</servlet-name>
<servlet-class>
xs.util.ws.server.XMLMessageServlet
</servlet-class>
<load-on-startup>100</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>xmlMessage</servlet-name>
<url-pattern>/xmlMessage/*</url-pattern>
</servlet-mapping>
下面就其特色簡(jiǎn)單說一下
1. 支持對(duì)wsdl的訪問。Wsdl文件需要提前編寫好放到項(xiàng)目的classpath中,最好跟業(yè)務(wù)類放到一起,便于實(shí)現(xiàn)不同的服務(wù)和維護(hù)。
2. 業(yè)務(wù)類需要實(shí)現(xiàn)XmlMessageService接口,在這個(gè)接口中有以下四個(gè)方法:
public abstract String invoke() throws Exception;
public abstract String error(Exception fault);
public abstract void before(InvokeContext invokecontext) throws Exception;
public abstract void after(InvokeContext invokecontext);
這四個(gè)接口都由XMLMessageServlet回調(diào),其中在before中可以進(jìn)行一些認(rèn)證和授權(quán)的工作,在after中可以做日志,invoke只用來實(shí)現(xiàn)業(yè)務(wù)。這種方式對(duì)程序員編碼來說會(huì)比較清爽。
請(qǐng)求參數(shù)由一個(gè)InvokeContext參數(shù)傳遞,減少耦合,增加擴(kuò)展性
這一部分比較簡(jiǎn)單,建議j2ee初學(xué)者閱讀源碼,提高框架設(shè)計(jì)能力。
整個(gè)項(xiàng)目提供下載,由于上傳文件大小的限制,請(qǐng)讀者自行補(bǔ)全依賴包,清單如下
commons-lang.jar
commons-logging-1.1.1.jar
commons-collections-3.1.jar
commons-beanutils.jar
log4j-1.2.17.jar
spring3.x.jar
netty-3.x.jar
其中有一個(gè)webTreeViewer-092b.jar是我自己以前的一個(gè)包,用于在web前端展現(xiàn)樹狀結(jié)構(gòu),現(xiàn)在只用里面一個(gè)載入資源的類,直接使用即可。
由于篇幅和時(shí)間所限講的比較粗略,想研究具體工作原理的朋友請(qǐng)自行閱讀源碼并進(jìn)行實(shí)驗(yàn)。
下載
posted on 2014-12-28 14:35 溪石 閱讀(5488) 評(píng)論(0) 編輯 收藏