使用jboss netty 創建高性能webservice客戶端及服務端
通過本文,讀者將了解以下內容
(1)利用jboss netty創建一個高性能的web服務客戶端
(2)不使用任何第三方框架,手工在web容器內創建webservice服務器端
在不依賴任何webservice框架的情況下,輕量級的實現這兩個目的,并且使你擁有更多的控制及定制能力。甚至可以越過soap協議的限制,使用你自己喜歡的自定義的消息格式來傳遞xml消息。
想象這樣一個情況:一個項目中使用apache cxf作為webservice使用的框架,消息的發送和接受要經過漫長的cxf處理管線。雖然cxf性能不錯,但是如果項目要求webservice交互的數量是每天數以百萬計呢?還有,在webservice的開發中總有一些令人煩惱的需求,比如同你交互的廠商技術不規范,而你又不得不遷就它的接口(國企實際情況,我必須和另外一家廠商的企業服務總線傳遞消息,而它的wsdl文件甚至無法通過schema驗證),從而使你的cxf或axis不停報錯。這時你要怎么辦呢?
另外,還有一些特殊要求。比如有的服務端要求你加上一些特殊的soap協議頭用來進行認證授權。雖然所有的開源框架都支持這樣做,但無論怎樣都是一件麻煩的事情。
有沒有一個方法可以給web服務開發以更大的定制性及更簡化的開發方式呢?
整篇文章分三個部分,首先介紹一個xml解析類,用于在xml數據模型和java類型之間進行轉換。然后在這個基礎上,介紹了如何使用netty創建高性能web服務客戶端。最后介紹自定義webservice服務器端的方法,算是對j2ee初學者的一個教學和啟示。
一.Xml解析器
自己實現webservice框架,少不了同xml打交道。我首先考慮是性能和靈活性。市面上的開源項目沒有能夠滿足要求的。于是自己寫了一個相對簡單的解析器來解決問題。解析器有兩種解析方式(1)將xml轉換為java數據類型。為了提高速度只是解析為map和list, 并不能夠解析為bean (2)提供一個回調接口,支持更靈活的從xml提取感興趣的信息。
例如,解析如下報文
<?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) {
}
});
經解析后結果如下,讀者注意看以下的調試信息
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}]}}
每個xml元素都會被解析為map
(1) 文本節點會被解析為:key為“@value”的鍵值對,所有的xml屬性作為key時在末尾都會加上@attr后綴。
(2) 子節點會作為父節點屬性存在(也是作為map的一個key-value對),其key值為子節點元素名,因為sax解析并不能處理命名空間及前綴,解析出來節點名和屬性都帶著命名空間前綴,但這樣做的好處是提高了解析的效率。
(3) 如果同名子元素出現兩次,它將被封裝為list,而且如果其中某個子元素只有文本子節點,這個子元素將被它內嵌的文本子節點代替,如下所示:
<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對象
接口簡明扼要,相信不需要我過多講解,直接看示例和源代碼即可。
現在,我們可以靈活高效的解析xml消息了,那么接下來我將介紹如何使用netty構建webservice客戶端。
二.使用netty構建webservice客戶端
通常,企業內部的消息傳遞是遵循“接口簡單,業務復雜”的原則來規劃的。也就是服務方法通常只有2,3個參數,每個參數都是字符串型,用來封裝復雜的業務數據(xml或json格式)。這樣在業務的變化的時候是不用修改接口的,這就使得不同廠家之間服務的對接,調試,測試的工作不用再進行一次,大大提高了對業務需求變化的響應速度。
從“模型(model)”生成后退一步是“模板(template)”生成,很多框架都是如此。
我們來看一個實際的例子,是從真實項目日志中截取出來的消息負載
<?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>
響應如下:
<?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>
因為篇幅的關系wsdl文件從略。
上面的例子采取的是所謂document-litaral-wrapped消息風格(注意最后的wrapped,如果是unwrapped,消息結構會不同,而且一般不用unwrapped風格)。這種風格也是一種推薦風格,大部分的廠商都是使用這種風格(其他風格當然也很容易支持)。從這種消息風格中,我們其實可以找出規律形成模板,具體參數通過對字符串模板替換即可。
模板如下:
<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}是在代碼里組裝而成的,掌握了規律后,手工拼出soap請求報文不是什么難事,具體實現看源代碼即可,這里不再贅述。
客戶端使用的例子如下:
BootstrapHolder holder = new BootstrapHolder();
holder.initMethod();
try {
NettyClient client = new NettyClient();
client.setHolder(holder);
client.setEndpoint("http://someurl");
client.setNameSpaceUri("http://yourCompany");
//注意設置wrapped風格
client.setIsWrapped(true);
Object wsReturn = client.invoke("methodName", "paramName","paramValue");
} catch (Exception e) {
e.printStackTrace();
}
holder.destroyMethod();
這是調用一個單參數的webservice接口,返回值是soap:Body里中xml子節點轉換成的java對象。當然框架還提供了一個多參數的方法,另外,可以對client設置一個xmlcallback回調函數,來更加靈活的從服務器端返回消息中提取有用數據。
注意在netty編程中要注意如下問題:
(1)BootstrapHolder對象是單例的,所有的client都應該共享這個對象。
(2)我一開始用的netty3.2.7final,同一個jetty server交互時出現了問題。出現了在連接真正關閉之前channel就已經關閉的情況。具體原因是使用了如下方式channel.getCloseFuture().await()來同步socket的關閉事件。現在HttpResponseHandler中,使用了一個CountDownLatch來進行同步解決了這個問題。
在webservice的項目開發中經常出現一些特殊要求。比如對方提出要加一個soap header來進行認證和授權,這個需求只需簡單的修改模板(當然源碼也得改,將字符串替換的邏輯加進去)就可以實現了,是不是很簡單啊!
Client調用的接口是同步的,要想進一步提高性能需要做以下工作
(1) 將接口改成異步的,可以在高并發的情況下提高性能,節省資源消耗。
(2) 將消息格式改成非xml的,比如json或其他數據格式的.但是不同項目之間(不同廠家)的消息交互還是soap xml最通用。
三.在web容器內手工創建webservice服務器端
實際上就是在web容器內實現一個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>
下面就其特色簡單說一下
1. 支持對wsdl的訪問。Wsdl文件需要提前編寫好放到項目的classpath中,最好跟業務類放到一起,便于實現不同的服務和維護。
2. 業務類需要實現XmlMessageService接口,在這個接口中有以下四個方法:
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);
這四個接口都由XMLMessageServlet回調,其中在before中可以進行一些認證和授權的工作,在after中可以做日志,invoke只用來實現業務。這種方式對程序員編碼來說會比較清爽。
請求參數由一個InvokeContext參數傳遞,減少耦合,增加擴展性
這一部分比較簡單,建議j2ee初學者閱讀源碼,提高框架設計能力。
整個項目提供下載,由于上傳文件大小的限制,請讀者自行補全依賴包,清單如下
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
其中有一個webTreeViewer-092b.jar是我自己以前的一個包,用于在web前端展現樹狀結構,現在只用里面一個載入資源的類,直接使用即可。
由于篇幅和時間所限講的比較粗略,想研究具體工作原理的朋友請自行閱讀源碼并進行實驗。
下載