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

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  210 随笔 :: 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 @ 2007-11-21 22:16 岑文初 阅读(1686) | 评论 (2)编辑 收藏

 在路上---基于SCA规范的应用服务框架成长记(二)(连载中...)    文章指数:0  CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。
 二.背上铺盖带上干粮SCA服务框架之路启程

记得我在推广SCA规范的时候,常常和Spring作比较,Spring广为流传很大的一点就是在于它的IOC理念,SCA中也很彻底贯彻了这点(这点应该是个趋势,包括OSGI等等开源框架),但是也正是这个理念,在实际运用当中会带来困扰。当开发系统越来越大,一个工厂里面的bean组装复杂度不断增加,庞大的spring bean factory就好比一个大锅子,越来越多配置交织在一起,最终模块与模块之间无法分割,架构师虽然规划了很好的目录结构以及配置文件,但是在运行期的结构依然是耦合性极强,难以分割的业务模块逻辑团。这样的系统所面临的问题就像当初OO要解决的问题一样,只是上升到了业务级别:业务耦合性强,需求变更适应能力弱,维护成本高,无法剥离较为独立的业务组件提供复用,模块与模块之间牵制性强等。

SCA规范中描述的元数据就是CompositeComponent,在代码级别的概念中Component就是SpringbeanComposite可以看作Spring bean factory(其实使用Spring也可以实现SCA,只是如果使用factory来作为composite那么可能在性能上和可扩展性上还有一些问题)。在业务级别的设计中Composite就是业务模块,Component就是业务内部的业务实现逻辑单元,同时引入了ServiceReference的概念,将服务和引用单独作为内部逻辑单元,同时定义了ServiceReference的两种级别(CompositeComponent),达到了业务实现作用域的控制,真正做到了业务组件级别的封装。

应该来说,除了开放性的特质以外,业务模块封装的特质是SCA的模块化最突出的优势,也是解决系统日益庞大情况下,如何降低维护成本,如何适应需求变更,如何提高开发效率的有效手段。但就是规范中的这一点,Tuscany的实现,不得不让我由原来的基于Tuscany架构二次扩展开发的想法做了转变,同时在后面对于服务框架的需求不断发展的情况下,对于Tuscany在服务框架中的定位不断的作着改变。

Tuscany在业务模块封装上面究竟有什么问题呢?在Tuscany中提供给第三方嵌入的SCA容器EmbeddedSCADomain结构如下:

                                                                             

EmbeddedSCADomain运行期结构图

       上面的图展示了EmbeddedSCADomain如何载入外部的Composite到容器中,这里所用的一个技巧,就是includeComposite可以include多个Composite。下图是一个很简陋的活动图,主要就是大致描述了EmbeddedSCADomain是如何加载所有的Composites的。这里面省略了Tuscany对于插件扩展的载入以及一些细节方面的处理。

EmbeddedSCADomain启动活动图

       根据上面两个图就出现了两个比较大的问题:

1.       首先EmbeddedSCADomain所有的Composite和资源都是根据固定的URI载入,但是这个或者是目录或者是jar,但是如果是目录中的jar将不会去解析,那么对于我们业务模块当前的开发要求,各个业务开发组会把不同的Composite最后打包到各个业务模块的jar中,这样就没有办法通过一个EmbeddedSCADomain去装载,互通就更加说不上了。不过这个问题不是根本性的问题。而后面2的问题是根本性的原则问题。

2.       Tuscany让所有的Composite互通,是将所有的composite通过include到一个domainComposite中,在build的过程中将所有的Composite的内部components克隆到了domainComposite中,这样其实所有的Component就在一个Composite中,也就很方便的互通了。这样SCA框架的业务模型封装形同虚设,和spring一样一个大的factory没有什么区别,丢失了最大的一个优势。同时serivcereference都没有两种级别之分,业务模块化就无法实现和控制。

就这样两个问题,让我需要考虑重新基于Tuscany的部分内核机制来重新构建容器装载,解析,组装机制。下图是ASF(Application Service FrameWork)的运行期结构设计图

ASF(Application Service FrameWork)的运行期结构设计图

问题一的解决方案:

ASF中定义了真正包容所有CompositeService Container,创建的过程中,通过搜索Classpath中所有的有composite-load-config.xml文件的jarcomposite-load-config.xml定义了需要装载的Composite,然后resolve这些jar的所有的resource,根据定义要加载的内容动态加载所有需要加载的composite

问题二的解决方案:

       基于问题一的解决,然后在Container中记录各个composite的必要信息和状态,然后按照不同级别的servicereference组装所有的composite,最后激活所有的composite

这两个问题的解决,也标志着ASF的基础框架形成,后续的战斗真正开始。

更多内容可访问我的blog:http://blog.csdn.net/cenwenchu79

posted @ 2007-11-21 08:03 岑文初 阅读(1232) | 评论 (1)编辑 收藏

 

       每个人在人生的不同阶段都在成长,父母们为自己记录了过去的成长历程,自己也在成年以后记录着自己的成长历程。程序员或者架构师都有着自己的“孩子”,不论自己的孩子是好是坏,都为自己的孩子有一点成绩而激动不已。现在的我也正在培育着一个自己的“孩子”,虽然在它成长过程中我要付出很多,但是看着它的成长,让我觉得所有的付出都是值得的。因此通过这种方式,记录下它的成长,记录下遇到的种种困难和解决之道,为自己也为其他程序员架构师在培育“孩子”的过程中提供可能有帮助的一些信息。

一.初涉SCA

       阿里新子公司成立,我也从业务开发组调动到了平台架构组,平台架构组当时的职责就是完善XPlatform来支持多个业务产品线部门的业务开发,XPlatform是基于MDA理念的快速开发平台。但随着业务部门开发灵活性要求的日益增加,XPlatform在可定制化,模块化就日渐暴露出了不足。在4月份的技术战略调整以后,开始了翻天覆地的XPlatform重构计划,而我就被分配负责考虑底层架构重构以及服务框架的设计。

       当时首先考虑模块化重构底层的时候,感觉OSGI是一个很好的方向,因此在重构服务框架前期的Cache模块时候采用了OSGI策略,可以动态替换Cache,但是在使用的过程中发现,此模块化并非我们所需的模块化。OSGI的模块化比较彻底,就连每个Bundledependency都是自己维护和管理,这样对于动态载入和卸载提供了坚实的基础,但同时也为它提供了最难处理的一个问题:复杂的ClassLoader机制。而回顾当前的业务开发场景,是否真的有这样强模块隔离的需求,回答往往是否定的。而作为动态的载入和卸载,的确是很好的一种特性,但是作为商业化的产品,特别是现在的互联网应用,机群中部署的应用模块往往来说并不需要单独装载和部署。另一方面来看OSGI面向的主要是对于单机服务开发模块化的解决方案,并非SOA的解决方案,而当前互联网应用在很大程度上需要互联互通,模块化是基础,但是并非最终的解决手段。

       这时候开始关注与SCA,对于SOA具体实施的一种实践性的规范,规范本身的模块化可扩展性给我留下了很深的印象。SCAOSGI在模块化思想上有很多类似的地方,在SCAComposite就是Bundle虚拟体,而SCA的优点就在于它只是规范,只是一种设计思想,不约束实现语言,平台以及其它细节,这也是互联网应用的一种趋势。信息共享达到信息价值最大化,这是企业应用的目标,在实现这个目标的时候需要抛开实现过程中的细节。就好比程序员往往关注的是使用什么好的技术能够开发出最炫的应用,而老板关注的是如何在最短最快的情况下满足用户需求,两者之间如何能达成双赢,那么就是架构师的职责了。

       既然认定了SCA这条路,那么就开始走吧,第一步当然是看规范,OSOA上关于SCA的规范十分详尽。SCA规范虽然已经有些年头了,但是正式纳入OSOA并且成为一个较为广泛认可的规范其实时间并不长,国外Sun,Bea都用一些类似的产品,但也没有大规模推广,而在Apache的孵化项目中TuscanySCA最出名的实践开源项目,对于我来说当然是加入开源大家庭。我在接触Tuscany的时候,它还是0.9之前的milestone2,但到现在为止,短短的几个月,已经发布到了v1版本了,可见发展迅速。对于Tuscany的关注,我看见现在国内也越来越多,Tuscany最大的特点就是它的架构可扩展性很强,这也是SCA规范所需要的架构体系,要做到SCA的模块化和扩展性,必须有灵活的基础架构作为支撑。

       三下五除二,用Tuscany搭建了SCA规范中描述的几个通用场景,简单的java beanspring的集成,异步回调,rmi远程调用等等,当然遇到了不少问题,由于Tuscany本身也是孵化过程中,最大的问题就是相应的文档少,作为一个初学者,只能通过Demo了解大致的使用方式,遇到问题也就通过跟踪它的代码来学习,不过这个过程,让我也对整个框架的体系架构有所了解,就如大牛所说,真正的体系架构是一个运行期的概念,而非设计期的,设计期其实就是SCA规范本身,而运行期的部分才是真正的体系架构。

       预研终究是在做实验性的工作,是否能够产品化,还需要实践来检验。XPlatform的重构正式启动了,平台架构师们组织了一次内部重构技术方案讨论会,我将前期的研究结果以及SCA的思想和实现都作了展示,同时结合我们重构的目标作了技术实施可行性介绍。与会的另外一个架构师提出了使用Spring实现SOAESB模式的解决方案,经过比较和讨论最后我的计划方案得到了肯定,但是我自己心里其实也没有底,毕竟如果真的在XPlatform的体系架构中实施SCA,那么将会波及各个产品线的底层架构,如果后续出现无法修复的问题,那么这个责任可不小。同时在过去的那些年头,EJB就是一个很好的反面例子,一个规范理念是重要的,但是实施环境是否合适也会决定成败,SCA是否适合未来的阿里软件技术方向,当时的我没有把握。最后我主动提出降低风险的设计方案,平台底层分成两部分:基础服务框架(BSF)和应用服务框架(ASF),前者主要应用于XPlatform内部核心组件的插拔交互,后者应用于上层应用的组装,发布,交互。

Alisoft-xplatform-core-service-framework这个就是服务框架项目的子工程目录名,也是我的SCA之路的第一步。当走出这第一步以后,就再也不能回头了,常用老马的一句话来激励自己:我可能不是最聪明的人,也不是最努力的人,但是坚持可能就让我比别人成功。Tuscany为我开启了SCA的实践之路,但Tuscany并不是像看起来那么美,要走适合服务框架的SCA之路,难题才刚刚开始。

posted @ 2007-11-15 17:01 岑文初 阅读(1344) | 评论 (1)编辑 收藏

仅列出标题
共12页: First 上一页 4 5 6 7 8 9 10 11 12