基于SCA规范的应用服务框架成长记

来源:互联网 发布:生活记账软件排名 编辑:程序博客网 时间:2024/05/20 00:13
本来这部分内容应该作为很后面的内容,但是由于工作已经作了,也总结了,那么就先写下来贴一下,也算是个分享吧,这部分内容在网上找了很久都没有,所以也算是不错的一个实践。
     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返回的包体如下:
    
    
    
    
     11
     false
     100.23
    
    
     111
     false
     111.23
    
    
    
    
     先来解释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.
    
    
    
    
    
    
    
    
    
    
    
    
     2.
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
     配置一的情况:
     有两种场景出现:
     场景一:
     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包:
    
    
    
    
     11
     false
     100.23
    
    
     111
     false
     111.23
    
    
    
    
     .net返回的soap包:
    
    
    
     12.12
     11
    
    
    
     12.12
     11
    
    
    
    
     但就作为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; iqr.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接口改造成为这种方式,同时可以给客户封装类似的构造函数库提供给客户使用。
     结束语:
     这个报告发给了我们的架构师们以及相关人员,晚上下班到家,收到了老大的邮件,让我们总架构师向微软提出这个问题,看是否真的是这样的情况,能否有好的方法解决。这让我想起了前一阵子谁说的一句话:“有多少人打过微软的客户服务电话反映过情况”。赫赫,我们这就算是反映了,效果么……,觉得求人不如求己,开源好啊^_^  
原创粉丝点击