放翁(文初)的一亩三分地

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  112 随笔 :: 1 文章 :: 320 评论 :: 0 Trackbacks
 

三.平台跨的不容易

       本来这部分内容应该作为很后面的内容,但是由于工作已经作了,也总结了,那么就先写下来贴一下,也算是个分享吧,这部分内容在网上找了很久都没有,所以也算是不错的一个实践。

       ISV有几家接了上来,有用PHP的,有.net的,这时候ASF框架的WebService继功能测试,性能测试,安全性测试进入了一个新的测试阶段,兼容性测试。由于ISV的技术力量参差不齐,所以我们需要包办实现所有语言的客户端调用Demo的工作,因此对我这个做ASF的人来说,又要懂得各个语言的客户端调用以及配置,幸好还有一个ISV Support部门也做一些这样的工作,但是由于都是新手,也没有太多的指望。

       WebService之所以能够被认为是SOA最行之有效的技术手段,主要还是因为其通过wsdl规范以xml作为数据和操作请求描述的载体,基于SOAP协议在http或者smtp上传输,实现业务逻辑交互与实现语言及平台的无关性,达到跨平台交互的效果。然而作为协议,往往来说是制定了规范性的框架,但是框架内的细节实现,不同的厂商,平台,开发语言,开源框架都会有不同的实现方式,因此也造成了WebService客户端解析Soap数据包兼容性的问题。这个问题在普通的接口中不容易出现,只是在调用接口返回数据类型为对象数组的时候出现。

首先出现在Java平台的两个比较通用的开源WebService框架上:axis2,xfire。(cxf暂时还没有去做测试)。现象:axis2xfire的两种客户端都无法正常解析ASF返回的数组对象。例如返回的是Account对象,Accountidnamevalue三个属性。模拟返回2Account对象,结果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返回的内容来看,Accountnamingspace丢失了,导致后面各个属性的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了,然后就像普通的对象调用一样,直接可以操作此服务(不过ASFwebservice的发布和引用也已经做的这么傻瓜了^_^)。简单是把双刃剑,容易上手,但是容易养成不求甚解的习惯,工作到现在,要不是开发框架,我根本不会去管wsdl中哪个元素是什么用处,工具生成好了,用就罢了,只要不出错。懒倒还是一方面,最痛苦的莫过于没有办法看到源码,只能黑盒测试以及猜测,这时候我觉得java真是好。还问了一个以前的高手朋友,他做了67年的java然后转到.net上,我说怎么跟踪.net的源码,他和我说:“据说.net快要开放源码了”。#_#|| 我回了一句:“我基本上等不到那天了。”言归正传,下面是如何分析.net问题的报告。

Java&.Net WebService兼容问题

Java发布的webservice .net客户端调用的时,数组对象类型返回兼容问题。

问题描述:

Java发布的WebServiceJava客户端调用下都是正常的,但是在.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的时候xsdschema就只有一份,那么.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);

}

接口中的返回对象和接口不在一个包内,那么生成的xsdschema就有多个,那么.net的客户端调用java发布的webservice就存在前面描述的问题。

因此用同样的wsdl分别用.netjava发布,通过.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中包含一个xsdschema.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接口改造成为这种方式,同时可以给客户封装类似的构造函数库提供给客户使用。

结束语:

       这个报告发给了我们的架构师们以及相关人员,晚上下班到家,收到了老大的邮件,让我们总架构师向微软提出这个问题,看是否真的是这样的情况,能否有好的方法解决。这让我想起了前一阵子谁说的一句话:“有多少人打过微软的客户服务电话反映过情况”。赫赫,我们这就算是反映了,效果么……,觉得求人不如求己,开源好啊^_^

更多的内容请访问我的bloghttp://blog.csdn.net/cenwenchu79

posted on 2007-11-21 22:16 岑文初 阅读(1596) 评论(2)  编辑  收藏

评论

# re: 在路上---基于SCA规范的应用服务框架成长记(三)(连载中...) 2008-05-06 17:26 hi
请教关于tuscany中spring实现component的问题,
我现在遇到的问题是:我们的系统中关于连接池,事务管理器的spring配置文件是由核心模块提供的,业务模块可以直接引用核心模块提供的bean。这样就造成了spring的application context分布在多个配置文件中,而tuscany只能指向一个单独的spring配置文件来构造application的context,不知道您是如何解决这个问题的,谢谢!  回复  更多评论
  

# re: 在路上---基于SCA规范的应用服务框架成长记(三)(连载中...) 2008-05-06 17:44 岑文初
改造Spring的组件,支持import  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: