2012年9月26日 星期三

AXIS 和 .NET 的互通性

AXIS 和 .NET 的互通性

This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟

請勿轉貼


目錄

  1. 開發 ASP.NET 的客戶端來存取 AXIS 的服務: simple datatypes
  2. 開發與使用回傳字串陣列的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式
  3. 開發與使用回傳 JavaBean 的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式
  4. 開發與使用回傳 JavaBean 陣列的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式



開發 ASP.NET 的客戶端來存取 AXIS 的服務

開發 Web Services 的最主要的目的之一,就是希望能達到跨平台性, 然而可惜的是:事實並非如此,因為每一個廠商的 implementation 或多或少有一些差異。目前,在 Java 的世界,Axis 是最常用的 web service 平台(根據 Axis 的 FAQ,採用 Axis 技術的商用軟體包含 BEA's Weblogic Platform, Borland, IBM WebSphere, Oracle Web Services Manager, JBoss 等);而在微軟的世界裡, .NET 卻是最常見的 web services 平台;因此,這兩者之間的 interoperability (稱為互通性)就更形重要。在前一節的文章中,我已經介紹了如何開發 Axis 客戶端 程式來存取 .NET 的服務;在本文中,,我會敘述如何開發 ASP.NET 的客戶端程式 來存取 Axis 的服務 我參考的主要資料來源為: 為了達到跨平台性,有幾個地方要特別注意的:
  1. 如之前說過的,傳送的 SOAP 資料格式必須設定成 wrapped/literal。這是因為 .NET 的預設傳送格式是 wrapped/literal,而 AXIS 卻不是。
  2. 目前確定可以交換的資料型態包含 Java 的 String、boolean、byte、short、int、long、float、以及 double。另外,上述資料型態的陣列,JavaBean,以及 JavaBean 的陣列也可以正確的傳送。
  3. 避免交換 multi-dimensional 和 jagged 陣列,以及避免利用 overloading 來 定義 web services。
我們的測試環境是:Windows XP Professional SP2 + IIS 5.1 + .NET Framework 2.0 + .NET Framework SDK 2.0。 開發的步驟包含:
  1. 利用 WSDL 檔案來產生 ASP.NET 的 proxy 檔案(這部份的概念跟 Axis 相同, ASP.NET 的程式也是利用 proxy 類別來跟遠端的服務來連結的,在本範例中, QueryTimeService.vb 就是 proxy 類別)。產生 proxy 類別的方式很簡單, 你只要使用 .NET Framework SDK 2.0 所提供的 wsdl.exe 即可;執行的指令為 wsdl /l:vb /protocol:SOAP http://localhost:8080/axis/services/TimeService?wsdl。你也可以不必使用 HTTP 存取 WSDL 檔,WSDL 檔也可以是一個 local 的檔案。由於我們開發的程式是以 VB 來撰寫,因此才有 /l:vb 的選項。
  2. 利用剛剛產生的原始碼來編譯並產生 .dll 檔(稱為 assembly)。 執行的指令為 vbc /out:bin\QueryTimeService.dll /t:library /r:System.Web.Services.dll /r:System.Xml.dll QueryTimeService.vb;QueryTimeService.vb 是前一個步驟所產生的原始碼,而這個步驟會產生一個 QueryTimeService.dll 的 .dll 檔。而這個 .dll 檔的儲存位置在目前這個路徑底下,名稱為 bin 的子目錄 內(由於執行 ASP.NET 的程式時,ASP 會自動從 bin 子目錄內去尋找所需的 .dll 檔,因此我們建議不要設定其它的輸出路徑)。假設我們執行第一和第二步驟 的路徑是 c:\Inetpub\wwwroot,則 QueryTimeService.vb 位於 c:\Inetpub\wwwroot,而 QueryTimeService.dll 位於 c:\Inetpub\wwwroot\bin
  3. 開發 ASP.NET 的客戶端程式。如果剛剛 QueryTimeService.dll 位於 c:\Inetpub\wwwroot\bin,則下列的 ASP.NET 的程式必須置放於 c:\Inetpub\wwwroot
    <%@Page Language="VB" %>
    <script runat=server>
    Sub Page_Load(o As Object, e As EventArgs)
      ' 這個是 Axis 的 service
      Const URL as String ="http://hostname:8080/axis/services/TimeService"
    
      ' QueryTimeService 的類別已經由 wsdl.exe 和 vbc.exe 產生
      ' 這個 QueryTimeService.dll 必須放在這個 .aspx 檔的同一個
      ' 目錄底下的 bin 目錄;依照我們的範例,就是放在
      ' c:\Inetpub\wwwroot\bin 內
      Dim service as New QueryTimeService()
    
      ' 設定服務的 URL
      service.Url = URL
    
      ' 呼叫服務的 getTime()
      Dim Answer As String = service.getTime()
    
      Label1.Text = Answer
    End Sub
    </script>
    <html>
    <body>
    <h2 align="center">ASP SOAP Client</h2>
    <hr/>
    <font color="gray" size="5">
    <asp:Label id="Label1" runat="server" />
    </font>
    <p/><p/>
    </body>
    </html>
    
  4. 執行這個 ASP.NET 的程式會得到如下的畫面:

開發與使用回傳字串陣列的 AXIS 服務

由之前的討論,Axis 和 .NET 之間可以互傳字串陣列、符合 JavaBean 規格 的物件、以及物件陣列,我們就依序測試。首先測試的是字串陣列:
  1. AXIS service: ArrayService.java 該服務會將服務的時間,以年、月、日、 時、分、秒的字串陣列回傳。請注意,由於 AXIS 的預設狀況 為 "Request" scope,也就是每一次 service 被呼叫的時候,AXIS 都會產生 一個新的 ArrayService 的物件,因此,每一次呼叫這個 service 都會回傳 最新的時間。
    import java.util.*;
    
    public class ArrayService {
      private String[] date;
    
      public ArrayService() {
        Calendar now = Calendar.getInstance();
        date = new String[6];
        date[0] = String.valueOf(now.get(Calendar.YEAR));
        date[1] = String.valueOf(now.get(Calendar.MONTH) + 1);
        date[2] = String.valueOf(now.get(Calendar.DATE));
        date[3] = String.valueOf(now.get(Calendar.HOUR));
        date[4] = String.valueOf(now.get(Calendar.MINUTE));
        date[5] = String.valueOf(now.get(Calendar.SECOND));
      }
    
      public String[] getTimeArray() {
        return date;
      }
    }
    
  2. 註冊服務,該服務的 WSDD 檔 deploy.wsdd 如下所示:
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="ArrayService" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="ArrayService" />
        <parameter name="allowedMethods" value="*" />
      </service>
    </deployment>
    

  3. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  4. 開發 AXIS 的客戶端程式
    1. 我們利用 AXIS 所提供的 WSDL2Java 的工具來產生所需要的 proxy 類別,然後開發客戶端程式如下:
    2. ArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._cc.axis.services.ArrayService.*;
      
      public class ArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          ArrayServiceService service = new ArrayServiceServiceLocator();
          ArrayService call = service.getArrayService();
          
          String[] result = call.getTimeArray();
      
          System.out.println(result[0]);
          System.out.println(result[1]);
          System.out.println(result[2]);
        }
      }
      
  5. 開發 .NET 的客戶端程式
    1. 將 service 佈置好了以後,就可以開始開發客戶端程式。過程跟之前所說的 一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後 在依照 proxy 類別產生 .dll 檔。
    2. VBArrayClient.aspx
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/StringArray"
      
        Dim service as New ArrayServiceService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getTime()
        Dim Answer() As String = service.getTimeArray()
      
        Label1.Text = Answer(0)
        Label2.Text = Answer(1)
        Label3.Text = Answer(2)
        Label4.Text = Answer(3)
        Label5.Text = Answer(4)
        Label6.Text = Answer(5)
      End Sub
      </script>
      <HTML>
      <BODY>
      <h2 align="center">ASP SOAP Client</h2>
      <hr/>
      <font color="gray" size="5">
      年/月/日: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      時/分/秒: <asp:Label id="Label4" runat="server" />/<asp:Label id="Label5" runat="server" />/<asp:Label id="Label6" runat="server" /><br/>
      </font>
      <p/><p/>
      </body>
      </html>
      

開發與使用回傳 JavaBean(或者複雜的資料型態)的 AXIS 服務

就像之前文件的說明,我們不能任意的傳送 Java Objects,目前只建議 傳送 JavaBean。但是,傳送的 JavaBean 被 SOAP 訊息正確的表達之後 (也就是所謂的 serialize 或者 unmarshall), 我們必須注意 .NET 可以正確的解讀(也就是所謂的 deserialize 或者 unmarshall)。 由於 serialize/deserialize 的問題,我一直無法正確的讀取資料, 這一個問題困擾了我半天,終於在輸入正確的 keywords 之後,Goolge 大神 引導我到這一篇解答: Solved: null responses with interop between Axis service and .NET client。 我們以範例來說明,要如何設計一個符合 JavaBean 的物件,讓 .NET 的 client 能夠正確的從 Axis service 讀取。
  1. 定義一個 JavaBean: DateBean.java 這個物件將會被 Axis service 傳送。
    import java.util.*;
    
    public class DateBean {
      private int year, month, day;
    
      public DateBean() {
        Calendar now = Calendar.getInstance();
        year = now.get(Calendar.YEAR);
        month = now.get(Calendar.MONTH);
        day = now.get(Calendar.DATE);
      }
    
      public int getYear() {
        return year;
      }
    
      public int getMonth() {
        return month + 1;
      }
    
      public int getDay() {
        return day;
      }
    
      public void setYear(int y) {
        year = y;
      }
    
      public void setMonth(int m) {
        month = m;
      }
    
      public void setDay(int d) {
        day = d;
      }
    }
    
  2. 定義服務:我們開發一個簡單的 BeanService.java。請特別留意,在 getDate() 這個方法回傳的是一個 DateBean 物件。
    public class BeanService {
      public DateBean getDate() {
        DateBean d = new DateBean();
        return d;
      }
    }
    
  3. 定義 wsdd 檔:這個檔案有幾個需要特別注意的地方:
    1. 我們必須增加一個 beanMapping 宣告,這個宣告就是下列範例中呈現暗紅色 的這一段。宣告中,明確的說明 DateBean 物件是一個以 Java 所寫出來的物件, 而且它的 QName 是 myNS:DateBean。
    2. 由於 AXIS 和 .NET 的轉換過程中,非常容易出現 namespace 對不上的情形, 而因為我們採用的是先開發 JavaBean 才由 AXIS 轉成相對的 WSDL 檔,又因為 AXIS 的預設行為就是產生一個名為 http://DefaultNamespace 的 targetNamespace,所以在為 DateBean 定義 namespace 的時候,我們就採用 http://DefaultNamespace;否則 .NET 客戶端所接受到的資料會是 0。 希望以後能夠找得到一種經由 AXIS 的設定,可以採用我們自訂的 namespace 的方式。
  4. <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="BeanService" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="BeanService" />
        <parameter name="allowedMethods" value="*" />
    
        <beanMapping qname="myNS:DateBean" xmlns:myNS="http://DefaultNamespace"
                     languageSpecificType="java:DateBean" />
    
      </service>
    </deployment>
    
  5. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  6. 開發 AXIS 的客戶端程式
    1. 過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後把這些類別 import 到客戶端程式即可。由於我們在 WSDD 內定義 DateBean 的 namespace 是 DefaultNamespace,因此 WSDL2Java 會產生一個 DateBean.java 的程式在 DefaultNamespace 的子目錄內,所以程式中也記得 要把 DefaultNamespace import 進來。
    2. BeanClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._dd.axis.services.BeanService.*;
      import DefaultNamespace.*;
      
      public class BeanClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          String URL = "http://aa.bb.cc.dd:8080/axis/services/BeanService";
      
          BeanServiceService service = new BeanServiceServiceLocator();
          BeanService call = service.getBeanService();
          
          DateBean result = call.getDate();
      
          System.out.println(result.getYear());
          System.out.println(result.getMonth());
          System.out.println(result.getDay());
        }
      }
      
  7. 開發 .NET 的客戶端程式
    1. 過程跟之前所說的一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後依照 proxy 類別產生 .dll 檔。跟 Axis 不一樣的地方,.NET 的 wsdl.exe 並不會單獨在 DefaultNamespace 目錄內,產生 DateBean 的 proxy 類別。
    2. VBBeanClient.aspx
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/BeanService"
      
        Dim service as New BeanServiceService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getDate()
        Dim Answer As DateBean = service.getDate()
      
        Label1.Text = Answer.year
        Label2.Text = Answer.month
        Label3.Text = Answer.day
      End Sub
      </script>
      <HTML>
      <BODY>
      <h3 align="center">ASP.NET 2.0 SOAP Client</h3>
      <h4 align="center">Process JavaBeans</h4>
      <hr/>
      <font color="gray" size="5">
      年/月/日: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      </font>
      <p/>
      </body>
      </html>
      
    3. 執行後的畫面如下:

開發與使用回傳 JavaBean 陣列的 AXIS 服務

  1. 定義 JavaBean:TimeBean.java
    import java.util.*;
    
    public class TimeBean {
      private int hour, minute, second;
    
      public TimeBean() {
        Calendar now = Calendar.getInstance();
        hour = now.get(Calendar.HOUR);
        minute = now.get(Calendar.MINUTE);
        second = now.get(Calendar.SECOND);
      }
    
      public int getHour() {
        return hour;
      }
    
      public int getMinute() {
        return minute;
      }
    
      public int getSecond() {
        return second;
      }
    
      public void setHour(int h) {
        hour = h;
      }
    
      public void setMinute(int m) {
        minute = m;
      }
    
      public void setSecond(int s) {
        second = s;
      }
    }
    
  2. 開發服務:TimeArray.java 該服務會產生兩個 TimeBean 物件,並以 物件陣列的方式回傳給 client 端。
    import java.io.*;
    
    public class TimeArray {
      public TimeBean[] getTimeArray() throws Exception {
        TimeBean[] res = new TimeBean[2];
        res[0] = new TimeBean();
    
        // 只是為了確保兩個 TimeBean 的物件差一秒
        Thread.sleep(1000);
    
        res[1] = new TimeBean();
        return res;
      }
    }
    
  3. 定義 wsdd 檔
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="BeanArray" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="TimeArray" />
        <parameter name="allowedMethods" value="*" />
        <beanMapping qname="myNS:TimeBean" xmlns:myNS="http://DefaultNamespace"
                     languageSpecificType="java:TimeBean" />
      </service>
    </deployment>
    
  4. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  5. 開發 AXIS 客戶端程式
    1. 過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後將這些類別檔 import 到客戶端程式。
    2. BeanArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._dd.axis.services.BeanArray.*;
      import DefaultNamespace.*;
      
      public class BeanArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          String URL = "http://aa.bb.cc.dd:8080/axis/services/BeanArray";
      
          TimeArrayService service = new TimeArrayServiceLocator();
          TimeArray call = service.getBeanArray();
          
          TimeBean[] result = call.getTimeArray();
      
          System.out.print(result[0].getHour() + ":");
          System.out.print(result[0].getMinute() + ":");
          System.out.println(result[0].getSecond());
          System.out.print(result[1].getHour() + ":");
          System.out.print(result[1].getMinute() + ":");
          System.out.println(result[1].getSecond());
        }
      }
      
  6. 開發 .NET 客戶端程式
    1. 過程跟之前所說的 一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後 在依照 proxy 類別產生 .dll 檔。
    2. 開發 .NET 客戶端程式
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/BeanArray"
      
        Dim service as New TimeArrayService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getDate()
        Dim Answer() As TimeBean = service.getTimeArray()
      
        Label1.Text = Answer(0).hour
        Label2.Text = Answer(0).minute
        Label3.Text = Answer(0).second
      
        Label4.Text = Answer(1).hour
        Label5.Text = Answer(1).minute
        Label6.Text = Answer(1).second
      End Sub
      </script>
      <HTML>
      <BODY>
      <h3 align="center">ASP.NET 2.0 SOAP Client</h3>
      <h4 align="center">Process JavaBean Arrays</h4>
      <hr/>
      <font color="gray" size="5">
      時/分/秒: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      時/分/秒: <asp:Label id="Label4" runat="server" />/<asp:Label id="Label5" runat="server"/>/<asp:Label id="Label6" runat="server" /><br/>
      </font>
      <p/>
      </body>
      </html>
      


Written by: 國立中興大學資管系呂瑞麟






沒有留言:

張貼留言