三.平台跨的不容易 本來這部分內容應該作為很後面的內容,但是由於工作已經作了,也總結了,那麼就先寫下來貼一下,也算是個分享吧,這部分內容在網上找了很久都沒有,所以也算是不錯的一個實踐。 ISV有幾家接了上來,有用PHP的,有.net的,這時候ASF架構的WebService繼功能測試,效能測試,安全性測試進入了一個新的測試階段,相容性測試。由於ISV的技術力量參差不齊,所以我們需要包辦實現所有語言的用戶端調用Demo的工作,因此對我這個做ASF的人來說,又要懂得各個語言的用戶端調用以及配置,幸好還有一個ISV Support部門也做一些這樣的工作,但是由雩都是新手,也沒有太多的指望。 WebService之所以能夠被認為是SOA最行之有效技術手段,主要還是因為其通過wsdl規範以xml作為資料和操作請求描述的載體,基於SOAP協議在http或者smtp上傳輸,實現商務邏輯互動與實現語言及平台的無關性,達到跨平台互動的效果。然而作為協議,往往來說是制定了規範性的架構,但是架構內的細節實現,不同的廠商,平台,開發語言,開源架構都會有不同的實現方式,因此也造成了WebService用戶端解析Soap資料包相容性的問題。這個問題在普通的介面中不容易出現,只是在調用介面返回資料類型為對象數組的時候出現。首先出現在Java平台的兩個比較通用的開源WebService架構上:axis2,xfire。(cxf暫時還沒有去做測試)。現象:axis2和xfire的兩種用戶端都無法正常解析ASF返回的數組對象。例如返回的是Account對象,Account有id,name,value三個屬性。類比返回2個Account對象,結果axis2用戶端獲得一個數組,內部有一個Account對象,不過三個屬性都是沒有被初始化。xfire用戶端獲得一個數組,內部有兩個Account對象,同樣屬性都沒有被初始化。跟蹤兩個用戶端源碼並結合返回的Soap訊息分析,得到了問題的原因。 SOAP返回的包體如下:<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <_ns_:getUserAccountArr2Response xmlns:_ns_="http://webservice.asf.xplatform.alisoft.com"> <return xmlns="http://webservice.asf.xplatform.alisoft.com"> <Account xmlns=””> <accountId>11</accountId> <isDeleted>false</isDeleted> <accountBalance>100.23</accountBalance> </Account> <Account xmlns=””> <accountId>111</accountId> <isDeleted>false</isDeleted> <accountBalance>111.23</accountBalance> </Account> </return> </_ns_:getUserAccountArr2Response> </soapenv:Body></soapenv:Envelope>先來解釋Axis2的問題,Axis2用戶端在解析此包體的時候,首先檢查return標籤,然後根據wsdl中的描述確認內部數組物件類型為Account,然後迴圈擷取結果集構造對象,但是按照axis2的內部邏輯處理正常的情況,應該沒有Account這層標籤,直接是多個結構體組裝而成,由於多了Account這層外圍標籤,導致解析第一個對象就出現問題,因此,就出現了上面描述的結果。此時有些懷疑是否是ASF架構在返回SOAP的時候沒有遵循WSDL的規範,但是沒有檢驗過xfire也不能確定是否是沒有符合規範而造成的。在來解釋一下XFire用戶端調用問題的原因。同樣跟蹤了XFire的用戶端代碼,發現問題主要是出在最後給對象擷取屬性值的操作上。首先XFire用戶端啟動時會根據本地的介面包或者對象包路徑來反轉成為namingspace然後和屬性名稱一起產生QName緩衝在本地,作為屬性對象。然後當獲得了返回SOAP訊息包體的時候,根據這些QName去擷取屬性的內容,但是可以從上面描述的SOAP返回的內容來看,Account的namingspace丟失了,導致後面各個屬性的namingspace也都丟失了。看了一下ASF在返回SOAP的代碼,的卻在構造SOAP返回包的時候無法獲得對象的namingspace,只有它的上級return類型有namingspace,那麼如何解決呢,轉念一想,其實這也是一種規範,wsdl的產生工具大部分都遵循這種包反轉作為namingspace的策略,因此在構造返回包體的時候採取了這個策略來填充SOAP包,XFire用戶端正常。(後話,萬一遇到一些和我一樣自己喜歡修改wsdl的人,那麼xfire就未必能夠正常解析這類服務了)。從這兒也驗證了ASF對於WSDL的訊息包返回規範是正確的,也就也證明了axis2用戶端的一個缺陷,因此在java平台暫時不建議客戶使用axis2,同時axis2的用戶端友好度遠遠低於xfire,不過axis2的優勢在於配置靈活以及可插入性(這也是ASF為什麼整合axis2作為預設的webservice發布架構的原因,後續blog會回顧其他幾個測試的曆程)這還是開始,由雩都是開源架構,所以調試和檢測相對來說還比較方便。接著測試部就提出在用.net用戶端調用返回對象數組出現問題,問題和XFire最早一樣。當時我就很肯定地就是應該問題出在解析那些屬性上。說實話,第一次接觸.net,什麼都不會,裝了個vs 2005就開始搗鼓,不過.net真是傻瓜工具,調用webservice相當簡單,就只需要建立一個web reference,其中web reference就指向一個wsdl地址,那麼.net就自動替你動態產生好client了,然後就像普通的對象調用一樣,直接可以操作此服務(不過ASF的webservice的發布和引用也已經做的這麼傻瓜了^_^)。簡單是把雙刃劍,容易上手,但是容易養成不求甚解的習慣,工作到現在,要不是開發架構,我根本不會去管wsdl中哪個元素是什麼用處,工具產生好了,用就罷了,只要不出錯。懶倒還是一方面,最痛苦的莫過於沒有辦法看到源碼,只能黑箱測試以及猜測,這時候我覺得java真是好。還問了一個以前的高手朋友,他做了6,7年的java然後轉到.net上,我說怎麼跟蹤.net的源碼,他和我說:“據說.net快要開放源碼了”。#_#|| 我回了一句:“我基本上等不到那天了。”言歸正傳,下面是如何分析.net問題的報告。 Java&.Net WebService相容問題Java發布的webservice 在.net用戶端調用的時,數組物件類型返回相容問題。 問題描述:Java發布的WebService在Java用戶端調用下都是正常的,但是在.net的用戶端調用下,如果返回的類型是數組物件類型,那麼就會發現得到了數組,並且數組內部對象產生,但是對象內部的屬性值無法獲得。 問題分析:在wsdl中定義數組物件類型返回有兩種方式:1. <xs:complexType name="Account"> <xs:sequence> <xs:element minOccurs="0" name="accountBalance" type="xs:double"/> <xs:element minOccurs="0" name="accountId" nillable="true" type="xs:string"/> <xs:element minOccurs="0" name="isDeleted" nillable="true" type="xs:string"/> </xs:sequence></xs:complexType> <xs:element name="getUserAccountArrResponse"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" minOccurs="0" name="return" nillable="true" type="xsd:Account"/> </xs:sequence> </xs:complexType> </xs:element> 2. <xs:element name="getUserAccountArr2Response"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="return" nillable="true" type="xsd:ArrayOfAccount"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="Account"> <xs:sequence> <xs:element minOccurs="0" name="accountBalance" type="xs:double"/> <xs:element minOccurs="0" name="accountId" nillable="true" type="xs:string"/> <xs:element minOccurs="0" name="isDeleted" nillable="true" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:complexType name="ArrayOfAccount"> <xs:complexContent> <xs:restriction base="soapenc:Array"> <xs:attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:Account[]"></xs:attribute> </xs:restriction> </xs:complexContent> </xs:complexType>
配置一的情況: 有兩種情境出現:情境一: public interface IAccountService2{ public Account checkUserAccount(String accountId); public Account[] getUserAccountList(String accountIdBeg,String accountIdEnd); public Account[] getUserAccountArr(String accountIdBeg); public Account[] getUserAccountArr2(String accountIdBeg); public double payForAppOrder(Account account,double fee); public void delAccount(Account account,String name); public int checkUser(String accountId,String accountId1);}其中介面中所有的返回或者參數對象都和介面定義在同一個包體內,這樣產生wsdl的時候xsd的schema就只有一份,那麼.net的用戶端數組對象返回問題不存在。 情境二:public interface IAccountService{ public AccountBean checkUserAccount(String accountId) throws InvocationTargetException; public AccountBean[] getUserAccountList(String accountIdBeg,String accountIdEnd); public AccountBean[] getUserAccountArr(String accountIdBeg); public Account[] getUserAccountArr2(String accountIdBeg); public double payForAppOrder(AccountBean account,double fee); public void delAccount(AccountBean account,String name); public int checkUser(String accountId,String accountId1);}介面中的返回對象和介面不在一個包內,那麼產生的xsd的schema就有多個,那麼.net的用戶端調用java發布的webservice就存在前面描述的問題。 因此用同樣的wsdl分別用.net和java發布,通過.net用戶端去調用,前者不存在問題,後者有問題,截獲soap相應報文如下: java 返回的soap包:<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <_ns_:getUserAccountArr2Response xmlns:_ns_="http://webservice.asf.xplatform.alisoft.com"> <return xmlns="http://webservice.asf.xplatform.alisoft.com"> <Account> <accountId>11</accountId> <isDeleted>false</isDeleted> <accountBalance>100.23</accountBalance> </Account> <Account> <accountId>111</accountId> <isDeleted>false</isDeleted> <accountBalance>111.23</accountBalance> </Account> </return> </_ns_:getUserAccountArr2Response> </soapenv:Body></soapenv:Envelope> .net返回的soap包:<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <getUserAccountArr2Response xmlns="http://webservice.asf.xplatform.alisoft.com"> <return> <accountBalance>12.12</accountBalance> <accountId>11</accountId> <isDeleted xsi:nil="true"/> </return> <return> <accountBalance>12.12</accountBalance> <accountId>11</accountId> <isDeleted xsi:nil="true"/> </return> </getUserAccountArr2Response> </soap:Body></soap:Envelope>但就作為wsdl中定義的話,return只有一個內容就是Account數組,java的定義應該比較符合定義內容。 部分結論:也就是說在第一種配置情況下,wsdl中包含一個xsd的schema,.net用戶端不存在任何問題。wsdl中存在多個schema的情況下,數組對象無法構造成功,但是對於單個對象返回可以正常解析。 解決方案:1.修改服務架構服務端代碼適應.net用戶端(不可行,會導致java的出現問題)2.將這種特殊介面的schema中定義的類都放在一個包裡(覺得不是很合適)3.把對象都序列化然後作為結果返回,個人感覺效能比較低,不過可以真的減小跨平台的問題。
配置二的情況: 不存在用戶端調用的構造問題,不過需要改造用戶端代碼(其實就是獲得了xml的資料片斷,自己去解析xml的資料來構造用戶端對象)。此類方法在網上也很通用,可以參看www.salesforce.com提供給第三方的API介面介紹,就是類似的。 C# Example private void querySample() { QueryResult qr = null; binding.QueryOptionsValue = new sforce.QueryOptions(); binding.QueryOptionsValue.batchSize = 250; binding.QueryOptionsValue.batchSizeSpecified = true; qr = binding.query("select FirstName, LastName from Contact"); bool bContinue = true; int loopCounter = 0; while (bContinue) { Console.WriteLine("/nResults Set " + Convert.ToString(loopCounter++) + " - "); //process the query results for (int i=0;i<qr.records.Length;i++) { sforce.sObject con = qr.records[i]; string fName = con.Any[0].InnerText; string lName = con.Any[1].InnerText; if (fName == null) Console.WriteLine("Contact " + (i + 1) + ": " + lName); else Console.WriteLine("Contact " + (i + 1) + ": " + fName + " " + lName); } //handle the loop + 1 problem by checking to see if the most recent queryResult if (qr.done) bContinue = false; else qr = binding.queryMore(qr.queryLocator); } Console.WriteLine("/nQuery succesfully executed."); Console.Write("/nHit return to continue..."); Console.ReadLine(); } } 此時,我們的用戶端代碼修改成為:原來的代碼:jdk2Service.AccountService service5 = new jdk2Service.AccountService();jdk2Service.Account[] re = service5.getUserAccountArr("demo");jdk2Service.Account re2 = service5.checkUserAccount("test"); 現在的代碼:jdkService.AccountService service3 = new jdkService.AccountService();jdkService.ArrayOfAccountBean res = service3.getUserAccountArr("tea");string name = res.Any[0].FirstChild.InnerText;//擷取了第一個返回對象的第一個屬性值。
這種模式比較通用在現在的跨平台的用戶端調用
webservice
。
因此考慮
AEP
介面改造成為這種方式,同時可以給客戶封裝類似的建構函式庫提供給客戶使用。 結束語: 這個報告發給了我們的架構師們以及相關人員,晚上下班到家,收到了老大的郵件,讓我們總架構師向微軟提出這個問題,看是否真的是這樣的情況,能否有好的方法解決。這讓我想起了前一陣子誰說的一句話:“有多少人打過微軟的客戶服務電話反映過情況”。赫赫,我們這就算是反映了,效果麼……,覺得求人不如求己,開源好啊^_^ 更多的內容請訪問我的blog:http://blog.csdn.net/cenwenchu79