一路拾遺
          Collect By Finding All The Way ......
          posts - 81,comments - 41,trackbacks - 0
          <2008年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          我的博客開張啦!歡迎大家多多來踩!

          常用鏈接

          留言簿(5)

          隨筆檔案

          文章檔案

          相冊

          搜索

          •  

          積分與排名

          • 積分 - 64638
          • 排名 - 823

          最新評論

          閱讀排行榜

          評論排行榜

          為了充分發揮 Web 服務的靈活性和力量,用戶必須能夠動態地發現和調用 Web 服務實現。這是 Web 服務的最終承諾,也是開發像 UDDI 這樣的技術的最初原因。甚至有人這樣提議:企業能提供可供公眾訪問的 Web 服務的實現,無論張三李四都可以用這些實現來購買打折的鼻涕熊玩具或更實用的東西(如保險)。盡管這個想法還不能成為主流,我們還是可以在防火墻后進行這樣的動態調用。在本文中,Damian Hagge 演示了 Web 服務客戶機在事先不了解一個 Web 服務的組成的情況下如何動態地發現和調用該 Web 服務。
          在本文中,我將根據 Web 服務的基本原則進行構建。如果您不是很了解 UDDI、WSDL 和 Axis,或者說您以前未曾創建過一個基本的 Web 服務,那么您可能不會覺得這篇文章很有用。如果是這種情況,那么我建議您先看看 參考資料,那里解釋了 Web 服務及相關的一些技術。另一方面,如果您確實了解 Web 服務并且對本文中出現的任何資料感到困惑或不確定,那么請在 Web Services Technical 論壇上張貼您的意見,我將定期進行查看。

          服務器實現


          雖然本文不是關于 Web 服務的服務器端實現的,但還是值得提一下我將使用的 Web 服務的組成。我將以一個簡單的 Java 類作為我的 Web 服務的基礎,這個 Java 類所帶的方法將接受兩個整數作為參數,并返回一個整數(兩個參數的乘積)。當然,這個類將被作為一個 Web 服務公開,而且這個服務將需要被發布到 UDDI。

          如果您正在使用 WebSphere SDK for Web Services(WSDK),那么只需下載我已經為您創建的服務器端實現(請參閱 參考資料部分),并遵循下列步驟:


          將已下載的文件解壓縮到 WSDK 的 services\applications 目錄下。(即 services\applications\WebMath)。
          運行 wsdkconfig 并安裝 WebMath。
          啟動應用程序服務器。
          在 services\applications\WebMath\client 目錄下的命令行中輸入 run publish。

          這樣就可以了!Web 服務被發布到 UDDI 并等待客戶機請求。

          如果您沒有 WebSphere SDK for Web Services,那么您可以免費下載它(請參閱 參考資料部分)。否則,您將需要在您想要使用的任何一個平臺上從零開始創建上面描述的 Web 服務。只要確定當這個 Web 服務被發布到 UDDI 時,業務實體為“ Damian Hagge Ltd.”且業務服務為“ WebMathService” 就可以了。如果您這么做了,那么用于本文的包中提供的客戶機代碼(請參閱 參考資料)應該很好用。


          我現在可以談論有趣的部分了 — 動態發現和調用。我將討論的代碼是 DynamicInvoke.java ,您可以在 下載文件的 WebMath\client 目錄下找到該代碼。雖然這個代碼稍微有點兒長,但我還是會努力盡可能簡練地解釋它。

           調用 Web 服務的開放源碼 API


          uddi4j - 查詢和發布到 UDDI
          wsdl4j - 解析和收集來自 WSDL 的信息
          Apache Axis - 對 SOAP 消息進行編碼/解碼

           
           
          那么,我需要做什么才能動態地發現和調用這個服務?首先,我需要發現來自 UDDI 的關于此服務的信息。其次,我將需要閱讀來自服務提供者的 WSDL 實現文件并進行解析,以獲取各種信息。最后,我將擁有足夠的信息來用 Axis 編入(編碼)對 Web 服務實現的 SOAP 請求。

          在客戶機代碼中,這是通過使用開放源碼包 uddi4j 、 wsdl4j 和 Apache Axis 實現的。我將用 uddi4j 來瀏覽 UDDI 注冊中心,因為它能提供一個使用戶能夠查詢和發布到任何 UDDI 2.0 注冊中心的 API。 wsdl4j 包將被用于以編程方式表示 WSDL 文件的元素,所以我可以瀏覽和收集來自該文件的各種信息。然后我將用 Axis 向服務器發出真正的 SOAP 請求并等待應答。

          在 UDDI 中查找 Web 服務


          為了動態地調用 Web 服務,您需要知道四條信息。您必須知道 UDDI 注冊中心的查詢 URL、UDDI 的發布 URL、業務實體的名稱以及業務服務的名稱。您可以在 清單 1中找到這些信息。

          清單 1. DynamicInvoke.java — 第 38 行     public class DynamicInvoke {
                  private String uddiInquiryURL = "http://localhost:80/uddisoap/inquiryapi";
                  private String uddiPublishURL = "http://localhost:80/uddisoap/publishapi";
                  private String businessName = "Damian Hagge Ltd.";
                  private String serviceName  = "WebMathService";
           


          這段代碼說明了 UDDI 的位置、要查找哪個業務實體以及找到了這個業務實體后選擇哪個業務服務。我使用 UDDI URL 創建到 UDDI 注冊中心的代理,然后我用這個代理對注冊中心進行查詢以查找業務服務。這是如 清單 2中所示完成的。

          清單 2. DynamicInvoke.java — 第 65 行     // create a proxy to the UDDI
              UDDIProxy proxy = new UDDIProxy(
                               new URL(uddiInquiryURL), new URL(uddiPublishURL));
              // we need to find the business in the UDDI
              // we must first create the Vector of business name
              Vector names = new Vector();
              names.add(new Name(businessName));
              // now get a list of all business matching our search criteria
              BusinessList businessList =
                     proxy.find_business(names, null, null, null, null, null,10);
              // now we need to find the BusinessInfo object for our business
              Vector businessInfoVector  =
                     businessList.getBusinessInfos().getBusinessInfoVector();
              BusinessInfo businessInfo = null;
              for (int i = 0; i < businessInfoVector.size(); i++) {
                 businessInfo = (BusinessInfo)businessInfoVector.elementAt(i);
                 // make sure we have the right one
                 if(businessName.equals(businessInfo.getNameString())) {
                    break;
                 }
              }


          您在此處可以看到我創建了一個 UDDIProxy 。接著,我創建了一個包含所期望的業務名稱的 Vector 并將它作為參數傳送給了 UDDIProxy.find_business 。這個調用返回了一個包含 BusinessInfos 對象的 BusinessList 對象。 BusinessInfos 僅包含若干個 BusinessInfo 對象的一個向量。這是 uddi4j API 的共性,在這個 API 中,一個對象有到另一個僅包裝一個向量的對象的引用。整個客戶機代碼中將使用這種編程模式,所以在本文接下來的部分中,我不想解釋這段代碼的語義,它只是查找向量中的一個類,而這個向量包裝在一個父對象中。

          然后,我在所有的 BusinessInfo 對象中反復查找名為 Damian Hagge Ltd. 的 BusinessInfo 對象。這就確保了我得到了正確的業務實體。

          現在我需要查找名為 WebMathService 的業務服務。這是通過搜索 BusinessInfo 對象引用的(雖然是間接地)所有 BusinessService 對象實現的。當我找到名為 WebMathService 的 BusinessService 時,我就得到了正確的類。 清單 3中是實現該查找的代碼。

          清單 3. DynamicInvoke.java — 第 88 行     // now find the service info
              Vector serviceInfoVector  =
                      businessInfo.getServiceInfos().getServiceInfoVector();
              ServiceInfo serviceInfo = null;
              for (int i = 0; i < serviceInfoVector.size(); i++) {
                  serviceInfo = (ServiceInfo)serviceInfoVector.elementAt(i);
                  // make sure we have the right one
                  if(serviceName.equals(serviceInfo.getNameString())) {
                     break;
                  }
              }
              // we now need to get the business service object for our service
              // we do this by getting the ServiceDetail object first, and
              // getting the BusinessService objects through it
              ServiceDetail serviceDetail = proxy.get_serviceDetail(
                                                     serviceInfo.getServiceKey());
              Vector businessServices = serviceDetail.getBusinessServiceVector();
              BusinessService businessService = null;
              for (int i = 0; i < businessServices.size(); i++) {
              businessService = (BusinessService)businessServices.elementAt(i);
                  // make sure we have the right one
                  if(serviceName.equals(businessService.getDefaultNameString())) {
                      break;
                  }
              }


          同樣,您可以看到一個對象引用另一個對象(它僅包含我正在查找的對象的一個向量)的編程方式的模式。

          既然我擁有了業務服務,我就需要以某種方式收集服務提供者機器上的 WSDL 實現的 URI。這將由 UDDI 注冊中心中的 tModle 的 Overview URL 元素表示。要查找這個元素,我必須先找到業務服務的綁定模板,它包含了一個基于 http 的訪問點。一旦我找到了這個模板,那么我就可以找到由綁定的模板引用的適當的 tModel 了。在 清單 4中,我以編程方式實現了這個查找。

          清單 4. DynamicInvoke.java — 第 115 行     // ok, now we have the business service so we can get the binding template
              Vector bindingTemplateVector =
                      businessService.getBindingTemplates().getBindingTemplateVector();
              AccessPoint accessPoint = null;
              BindingTemplate bindingTemplate = null;
              for(int i=0; i<bindingTemplateVector.size(); i++) {
                  // find the binding template with an http access point
                  bindingTemplate = (BindingTemplate)bindingTemplateVector.elementAt(i);
                  accessPoint = bindingTemplate.getAccessPoint();
                  if(accessPoint.getURLType().equals("http")) {
                      break;
                  }
              }
              // ok now we know which binding template we're dealing with
              // we can now find out the overview URL
              Vector tmodelInstanceInfoVector =
                  bindingTemplate.getTModelInstanceDetails().getTModelInstanceInfoVector();
              String wsdlImplURI = null;
              for(int i=0; i<tmodelInstanceInfoVector.size(); i++) {
                  TModelInstanceInfo instanceInfo =
                          (TModelInstanceInfo)tmodelInstanceInfoVector.elementAt(i);
                  InstanceDetails details = instanceInfo.getInstanceDetails();
                  OverviewDoc wsdlImpl = details.getOverviewDoc();
                  wsdlImplURI = wsdlImpl.getOverviewURLString();
                  if(wsdlImplURI != null) break;
              }


          我在第一個代碼塊中查看業務服務引用的所有綁定模板,并找到具有“http”URL 類型的綁定模板。然后,我用綁定模板反復進行對 tModel 的所有引用,在找到第一個 overview URL(即 WSDL 實現的位置)時停止。既然我知道 WSDL 實現的 URL,那我就可以繼續用 wsdl4j 直接請求來自服務提供者的文檔并對其進行解析,從而獲得我為了調用服務所需要傳送給 Axis 的參數。

          請注意:根據您正在使用的遵循 UDDI 2.0 注冊中心的實現,tModle 中包含的 overview URL 可能并不指向 WSDL 實現。如果是這種情況,那么您將需要考慮更多事情,比如怎樣才能從 UDDI 或 overview URL 指向的位置收集到服務提供者的 WSDL 實現。

          解析 WSDL


          既然您擁有了用于 Web 服務的 WSDL 實現的 URL,那么您就可以用 wsdl4j 來解析它。為了使用 Axis 調用該服務,您將需要從 WSDL 收集下列信息:


          目標名稱空間
          服務名稱
          端口名稱
          操作名稱
          操作輸入參數

          在我的示例中,為了收集這些信息,我必須首先獲取表示 WSDL 實現和 WSDL 接口的 Definition 對象。我的代碼如 清單 5所示。

          清單 5. DynamicInvoke.java — 第 159 行  // first get the definition object got the WSDL impl
           try {
               WSDLFactory factory = new WSDLFactoryImpl();
               WSDLReader reader = factory.newWSDLReader();
               implDef = reader.readWSDL(implURI);
           } catch(WSDLException e) {
               e.printStackTrace();
           }
           if(implDef==null) {
                  throw new WSDLException(
                      WSDLException.OTHER_ERROR,"No WSDL impl definition found.");
           }
           // now get the Definition object for the interface WSDL
           Map imports = implDef.getImports();
           Set s = imports.keySet();
           Iterator it = s.iterator();
           while(it.hasNext()) {
                      Object o = it.next();
               Vector intDoc = (Vector)imports.get(o);
               // we want to get the ImportImpl object if it exists
               for(int i=0; i<intDoc.size(); i++) {
            Object obj = intDoc.elementAt(i);
                if(obj instanceof ImportImpl) {
                    interfaceDef = ((ImportImpl)obj).getDefinition();
                }
               }
           }
           


          如同您可以看到的,我從細讀 UDDI 注冊中心時收集到的實現 URL 處獲取了 WSDL 實現的一個 Definition 對象。然后,我通過搜索實現 WSDL 中定義的所有 imports 獲取了另一個 Definition 對象,它表示 WSDL 接口。一個格式良好的實現 WSDL 應該擁有一個指向其對應的 WSDL 接口的 imports。

          現在,查找要被傳送給 Axis 的目標名稱空間很簡單。我只要調用 WSDL 實現對象上的 Definition.getTargetNamespace() 就可以了。接下來,我們要查找包含了我想要調用的操作的端口。 清單 6中實現了這個查找。

          清單 6. DynamicInvoke.java — 第 195 行     // great we've got the WSDL definitions now we need to find the PortType so
           // we can find the methods we can invoke
           Vector allPorts = new Vector();
                  Map ports = interfaceDef.getPortTypes();
           s = ports.keySet();
           it = s.iterator();
           while(it.hasNext()) {
                  Object o = it.next();
               Object obj = ports.get(o);
               if(obj instanceof PortType) {
                allPorts.add((PortType)obj);
               }
           }
           // now we've got a vector of all the port types - normally some logic would
           // go here to choose which port type we want to use but we'll just choose
           // the first one
           PortType port = (PortType)allPorts.elementAt(0);
           List operations = port.getOperations();
           


          我獲得了接口 WSDL 中聲明的所有端口且只選擇第一個端口。當然,如果這是一個真正的應用程序,我就會希望開發某種類型的算法來選擇期望的端口和操作。但是,為了簡練起見,我不會這樣做。

          既然我已經選擇了要使用的端口,我就要查找我將提供給 Axis 的服務名稱和端口名稱。我通過在對應于選定的端口的 WSDL 接口中查找綁定來查找服務名稱和端口名稱。然后,我在 WSDL 實現中收集服務和端口,該實現為這個綁定提供一個端點。

          您可能發現這有點兒過于復雜,所以我將簡化它。請看一看 WebMath_Impl.wsdl 文件和 WebMath_Interface.wsdl 文件,您可以在 WebMath\webapp 目錄下找到這兩個文件。您可以在實現文件中看到有一個 <service> 標記,它包含一個 <port> 標記。在接口文件中,您可以看到有一個 <binding> 標記,它包含一個 <operation> 標記和一個表示我已經選擇的端口的 <port> 標記。在這兩個文件中,可能有這些端口中的幾個端口,所以我需要做的就是確保我在實現文件中選擇的服務包含引用綁定(它包含在接口文件中選擇的端口)的端口。

          換言之,我需要確保我在接口 WSDL 中選擇的綁定包含我已經選擇的同一個端口(在上面的代碼片斷中)。然后,我需要確保在實現 WSDL 中選擇的服務包含一個端口,這個端口的 binding 屬性值和接口 WSDL 綁定的 name 屬性值相同。如果這些是相同的,那么我就已經找到了引用我選擇的端口的服務和綁定。

          請在 清單 7中看看上面的算法是什么樣的。

          清單 7. DynamicInvoke.java — 第 215 行  // let's get the service in the WSDL impl which contains this port
           // to do this we must first find the QName of the binding with the
           // port type that corresponds to the port type of our chosen part
           QName bindingQName = null;
           Map bindings = interfaceDef.getBindings();
           s = bindings.keySet();
           it = s.iterator();
           while(it.hasNext()) {
               Binding binding = (Binding)bindings.get(it.next());
               if(binding.getPortType()==port) {
                // we've got our binding
                bindingQName = binding.getQName();
               }
           }
           if(bindingQName==null) {
               throw new WSDLException(WSDLException.OTHER_ERROR,
                                  "No binding found for chosen port type.");        
           }
           // now we can find the service in the WSDL impl which provides an
           // endpoint for the service we just found above
           Map implServices = implDef.getServices();
           s = implServices.keySet();
           it = s.iterator();
           while(it.hasNext()) {
               Service serv = (Service)implServices.get(it.next());
               Map m = serv.getPorts();
               Set set = m.keySet();
               Iterator iter = set.iterator();
               while(iter.hasNext()) {
                Port p = (Port)m.get(iter.next());
                if(p.getBinding().getQName().toString().equals(
                                                  bindingQName.toString())) {
                    // we've got our service store the port name and service name
                    portName = serv.getQName().toString();
                    serviceName = p.getName();
                    break;
                   }
               }
               if(portName != null) break;
           }
           


          通過執行上面的代碼,我收集到了需要傳送給 Axis 的端口名稱和服務名稱。現在,我需要的唯一其他的幾條信息就是要傳送到方法調用中的方法名稱和幾個參數。

          清單 8中的代碼將發現那些參數以及這些參數映射的 Java 類型(即 Java 類,或稱本機類型)。

          清單 8. DynamicInvoke.java — 第 256 行     // ok now we got all the operations previously - normally we would have some
              logic here to
           // choose which operation, however, for the sake of simplicity we'll just
           // choose the first one
           Operation op = (Operation)operations.get(0);
           operationName = op.getName();
           // now let's get the Message object describing the XML for the input and output
           // we don't care about the specific type of the output as we'll just cast it to
           an Object
           Message inputs = op.getInput().getMessage();
           // let's find the input params
           Map inputParts = inputs.getParts();
           // create the object array which Axis will use to pass in the parameters
           inputParams = new Object[inputParts.size()];
           s = inputParts.keySet();
           it = s.iterator();
           int i=0;
           while(it.hasNext()) {
               Part part = (Part)inputParts.get(it.next());
               QName qname = part.getTypeName();
               // if it's not in the http://www.w3.org/2001/XMLSchema namespace then
               // we don't know about it - throw an exception
               String namespace = qname.getNamespaceURI();
               if(!namespace.equals("http://www.w3.org/2001/XMLSchema")) {
               throw new WSDLException(
                              WSDLException.OTHER_ERROR,"Namespace unrecognized");
               }
               // now we can get the Java type which the the QName maps to
               // we do this by using the Axis tools which map WSDL types
                      // to Java types in the wsdl2java tool
               String localPart = qname.getLocalPart();
               javax.xml.rpc.namespace.QName wsdlQName =
                              new javax.xml.rpc.namespace.QName(namespace,localPart);
               TypeMapping tm = DefaultTypeMappingImpl.create();
               Class cl = tm.getClassForQName(wsdlQName);
               // if the Java type is a primitive, we need to wrap it in an object
               if(cl.isPrimitive()) {
                cl = wrapPrimitive(cl);
               }
               // we could prompt the user to input the param here but we'll just
               // assume a random number between 1 and 10. First we need to
               // find the constructor which takes a string representation of a number
               // if a complex type was required we would use reflection to break it
               // down and prompt the user to input values for each member variable
                      // in Object representing the complex type
               try {
                Constructor cstr = cl.getConstructor(
                                  new Class[] { Class.forName("java.lang.String") });
                inputParams[i] = cstr.newInstance(
                                  new Object [] { ""+new Random().nextInt(10) });
               } catch(Exception e) {
                // shoudn't happen
               e.printStackTrace();
               }
               i++;
           }
           


          我來一步步解釋所發生的一切。我已經有了一個 Operation 對象,是從我在端口對象對 Port.getOperations() 的調用中獲得的。接下來,只需調用 Operation.getName() 就會產生方法名稱。

          現在,我需要獲取表示 <input> 標記的 Message 對象,這個 input 標記由所選的端口引用。 <input> 標記指定幾個應該被傳送到方法調用中的參數。這是由代碼 Operation.getInput().getMessage() 完成的。一旦我收集到了這個 Message 對象,我就需要查找 Message 對象包含的所有部件。

          我反復查看所有的部件并確保名稱空間是 XML Schema 名稱空間,這樣我就能確保可以把部件類型映射到 Java 代碼。當然,在一個真正的應用程序中,這將被擴展以便盡可能地支持其他類型。

          然后,我稍微向 Axis 方向轉換一點兒以便發現類型映射到哪個 Java 類上。 Axis 包含一個 TypeMapping.getClassForQName() 方法,用在它的 wsdl2java 工具中。我所需要做的就是創建一個表示類型的 javax.xml.rpc.namespace.QName 對象。一旦我進行了調用,那么我就擁有了一個表示類型的 Java Class 對象。

          如果 Class 是基本類型,那么我需要將它包裝在一個 Java 對象中。這里沒有介紹實現這個包裝的代碼,因為它太簡單了,但是,如果您想要查看此代碼,您可以在 DynamicInvoke.java 找到它。當我擁有了要傳送給 Axis 的 Java 對象后,我需要對它進行實例化,給構造程序提供一個值,然后存儲它。我使用反射(reflection)查找這個對象的構造程序,并提供 1 到 10 之間的一個隨機數字對其實例化。當然,如果這不僅僅是一個演示的話,某個其他的邏輯將選擇參數,可能會提示用戶提供該信息。最后,我將實例化的對象存儲在 Object 數組中。

          好!我已經收集到了調用服務所需的所有信息。現在我就可以用一個 Axis 調用來調用 Web 服務了。

          用 Axis 調用 Web 服務


          用 Axis 調用 Web 服務是一個相對簡單的過程, 清單 9中是此過程的編碼。

          清單 9. DynamicInvoke.java — 第 365 行     public void axisInvoke(String targetNamespace, String serviceName,
                      String portName, String operationName, Object[] inputParams,
                      String implURI) {
               try {
                   // first, due to a funny Axis idiosyncrasy we must strip portName of
                   // it's target namespace so we can pass it in as
                      // targetNamespace, localPart
                   int index = portName.indexOf(":",
                              portName.indexOf("http://")+new String("http://").length());
                   String portNamespace = portName.substring(0,index);
                      portName = portName.substring(
                                  index==0?index:index+1); // to strip the :
                   javax.xml.rpc.namespace.QName serviceQN =
                new javax.xml.rpc.namespace.QName( portNamespace, portName );
                   org.apache.axis.client.Service service =
                new org.apache.axis.client.Service(new URL(implURI), serviceQN);
                   javax.xml.rpc.namespace.QName portQN =
                new javax.xml.rpc.namespace.QName( targetNamespace, serviceName );
                   // This Call object will be used the invocation
                   Call call = (Call) service.createCall();
                   // Now make the call...
                   System.out.println("Invoking service>> " + serviceName + " <<...");
                   call.setOperation( portQN, operationName );
                   Object ret = (Integer) call.invoke( inputParams );
                      System.out.println("Result returned from call to "+
                                                      serviceName+" -- "+ret);
               } catch(java.net.MalformedURLException e) {
                   System.out.println("Error invoking service : "+e);
               } catch(javax.xml.rpc.ServiceException e2) {
                   System.out.println("Error invoking service : "+e2);
               } catch(java.rmi.RemoteException e3) {
                   System.out.println("Error invoking service : "+e3);
               }
             }
           


          那么,我現在在做什么呢?首先,我創建了一個表示端口名稱的 QName 。 Axis 不提供從一個完整的名稱空間創建 QName 的構造程序,所以我不得不把端口名稱分割成名稱空間和本地部件。然后,我創建一個 Service 對象(它提供 WSDL 實現的 URL)和 QName (它表示端口名稱)。

          然后我創建另一個 QName 對象,這一次它表示服務名稱。現在我從 Service 對象創建 Call 對象。我通過傳入這個 Call 對象要調用的端口 QName 和操作名稱來設置它將調用的操作。最后,我實際調用了在解析 WSDL 時創建的、傳入 Object[] 參數中的操作。這樣就可以了!現在我將返回值作為一個 Java Object ,并把它打印出來。


          總結


          我希望您已經理解了我在本文中講述的所有代碼。這可能有點兒多,但是我已經說過了:理解 uddi4j 、 wsdl4j 和 Axis API 如何工作對于有效編寫定制的 Web 服務客戶機是很重要的。現在,您最好是研究研究代碼,然后試著找出 UDDI 注冊中心或 WSDL 文檔中包含的信息。如果您對 UDDI 元素的結構或 WSDL 的結構有些模糊的話,那么請嘗試著研讀它們。有一些關于這些主題的優秀 參考資料。再重申一遍,如果您對于本文所介紹的任何內容有任何疑問的話,請在 Web Services Technical 論壇上張貼意見。

          【轉自】:http://www.ibm.com/developerworks/cn/webservices/ws-udax/index.html 

          posted on 2008-07-31 18:05 胖胖泡泡 閱讀(476) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 临海市| 中西区| 和林格尔县| 德钦县| 佛教| 承德县| 丹棱县| 庐江县| 巴楚县| 双流县| 建平县| 天等县| 娄烦县| 临朐县| 奈曼旗| 电白县| 崇阳县| 彰武县| 乌审旗| 友谊县| 平遥县| 永年县| 宽城| 古丈县| 社旗县| 台前县| 榆树市| 贡觉县| 南宁市| 赣州市| 安平县| 涪陵区| 山西省| 武隆县| 静乐县| 岳池县| 恭城| 东至县| 平果县| 革吉县| 肃北|