2012年9月26日 星期三

開發回傳字串陣列、物件、以及物件陣列的服務

開發回傳字串陣列、物件、以及物件陣列的服務

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟
請勿轉貼


在之前的範例中,我們可以觀察到只要交換資料的資料型態是基本資料型態 (如 short, int, float, double 等)以及字串,client 的程式都非常 簡單。可是如果是陣列,或者物件(尤其是日期的物件),程式的開發有沒有 困難?
  1. 開發一個會回傳字串陣列的服務,該服務提供一 String[] getTimeArray(String[] input) operation,而該 operation 接受一個字串陣列的參數,並將回傳一 字串陣列。
    1. 服務端:StringArray.java
      import java.util.*;
      
      public class StringArray {
        private String[] date;
      
        public StringArray() {
          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(String[] input) {
          for(int i=0; i<date.length; i++) {
            date[i] = date[i] + " " + input[i];
          }
      
          return date;
        }
      }
      
    2. 產生一個註冊用的 WSDD 檔並註冊,跟之前的類似,故省略。
    3. 利用 WSDL2Java 產生必要的 proxy 類別,跟之前的類似,故省略。
    4. 產生客戶端程式:ArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      
      import localhost.axis.services.StrArrayService.*;
      
      public class ArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          StringArrayService service = new StringArrayServiceLocator();
          StringArray call = service.getStrArrayService();
          
          String[] input = {"年","月","日","時","分","秒"};
          String[] result = call.getTimeArray(input);
      
          for(int i=0; i<result.length; i++)
            System.out.print(result[i] + " ");
          System.out.println();
        }
      }
      


  2. 開發一個回傳物件以及物件陣列的服務,該服務提供 Worker getWorker()、Worker[] getArray()、以及 void setWorker(Worker w) 的 operations.
    1. 服務端:WorkerService.java
      import java.util.*;
      
      public class WorkerService {
        private Worker worker;
      
        public WorkerService() {
          worker = new Worker();
        }
      
        public Worker getWorker() {
          return worker;
        }
      
        public void setWorker(Worker w) {
          worker = w;
        }
      
        public Worker[] getArray() {
          Calendar c = Calendar.getInstance();
          c.set(1978, Calendar.MAY, 17);
          Worker w2 = new Worker("Dave Mentis", 60, c);
          Worker[] w = {new Worker(), w2};
      
          return w;
        }
      }
      
    2. 自訂的類別: Worker.java(為了避免麻煩,我們將 Worker.java 依據 JavaBean 的規範設計,細節討論,請參考 AXIS 與 .NET 互通性)。另外,我們在 Worker.java 也增加了一個日期的資料成員,根據經驗,請不要使用 java.util.Date,而應該使用 java.util.Calendar 或者 java.util.GregorianCalendar。請注意,所謂 JavaBean 的設計規範是說:當開發人員設計一個類別時,請務必設計該類別的預設建構元、 包含所有資料成員當作參數的建構元、以及每一個資料成員都有一對 get/set 方法。 以 Worker.java 為例,由於它有三個資料成員,所以我們需要設計出一個預設建構元 public Worker()、包含所有資料成員當作參數的建構元 public Worker(String n, int h, Calendar c)、資料成員 name 有一對 setName()/getName() 方法、資料成員 hours 有一對 setHours()/getHours() 方法、以及資料成員 birthday 有一對 setBirthday()/getBirthday() 方法。
      import java.util.*;
      
      public class Worker {
        private String name;
        private int hours;
        private Calendar birthday;
      
        public Worker() {
          name = "Eric Lu";
          hours = 40;
          birthday = Calendar.getInstance();
          birthday.set(1988, Calendar.JULY, 15);
        }
      
        public Worker(String n, int h, Calendar c) {
          setName(n);
          setHours(h);
          setBirthday(c);
        }
      
        public String getName() {
          return name;
        }
      
        public int getHours() {
          return hours;
        }
      
        public Calendar getBirthday() {
          return birthday;
        }
      
        public void setName(String n) {
          name = n;
        }
      
        public void setHours(int h) {
          hours = h;
        }
      
        public void setBirthday(Calendar d) {
          birthday = d;
        }
      }
      
    3. WSDD 檔:由於在 WorkerService 中,使用了自訂的類別 Worker,因此在 WSDD 的定義上,需要增加 beanMapping 的定義,該定義會使得 WSDL2Java 產生 恰當的 Worker 檔在 client 端。另外,由於 WSDL 檔內的 targetNamespace 是預設的 http://DefaultNamespace,最好在定義 beanMapping 的時候, 也設定適當的命名空間。
      <deployment xmlns="http://xml.apache.org/axis/wsdd/"
          xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="ObjectService" provider="java:RPC" style="wrapped" use="literal">
          <parameter name="className" value="WorkerService" />
          <parameter name="allowedMethods" value="*" />
      
          <beanMapping qname="tns:Worker" xmlns:tns="http://DefaultNamespace"
                       languageSpecificType="java:Worker" />
      
        </service>
      </deployment>
      
    4. 客戶端程式 WorkerClient.java:過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後把這些類別 import 到客戶端程式 即可。跟之前的範例有一點不太一樣的地方是,這次我們定義了 Worker, 由於我們在 WSDD 內定義它的 namespace 是 DefaultNamespace,因此 WSDL2Java 會產生一個 Worker.java 的程式在 DefaultNamespace 的子目錄內。
      import java.rmi.*;
      import javax.xml.rpc.*;
      
      import localhost.axis.services.ObjectService.*;
      import DefaultNamespace.*;
      import java.util.*;
      
      public class WorkerClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          WorkerServiceService service = new WorkerServiceServiceLocator();
          WorkerService call = service.getObjectService();
      
          // 先利用 service 設定一個 Worker 物件
          Calendar c = Calendar.getInstance();
          c.set(1970, Calendar.JUNE, 22);
          Worker s = new Worker(c, 55, "Mary Ann"); 
          call.setWorker(s);
          
          // 再取回剛剛的 Worker 物件(???) 
          Worker w = call.getWorker();
          System.out.print(w.getName() + "\t");
          System.out.print(w.getHours() + "\t");
          c = w.getBirthday();
          System.out.println(c.get(Calendar.YEAR) + "/" +
                             (c.get(Calendar.MONTH) + 1) + "/" +
                             c.get(Calendar.DATE) + "\n");
      
          // 取得一個預設的 Worker[] 陣列
          Worker[] ww = call.getArray();
          for(int i=0; i < ww.length; i++) {
            System.out.print(ww[i].getName() + "\t");
            System.out.print(ww[i].getHours() + "\t");
            c = ww[i].getBirthday();
            System.out.println(c.get(Calendar.YEAR) + "/" +
                               (c.get(Calendar.MONTH) + 1) + "/" +
                               c.get(Calendar.DATE));
          }
        }
      }
      
      這個客戶端程式首先利用 setWorker() 設定一個 Worker 物件到 service 端, 然後,試圖從 service 端將剛剛設定好的 Worker 物件取回;最後,再從 service 端取得一個 Worker 物件陣列。 這個程式跟以往的程式大同小異,但是有兩個地方要特別注意,這兩個地方 以棗紅色字體顯示:(1) 雖然在 service 端,Worker 建構元的 參數順序為 name, hours, birthday,但是在 client 端,Worker 建構元的 參數順序卻是 birthday, hours, name。這是因為在 ObjectService 的 WSDL 中(如下圖所示),Worker 的三個 element 依序是 birthday, hours, name。 因此,客戶端的程式必須以 WSDL 的方式撰寫;這其實很合理,因為大部分時間, 程式設計人員是無法知道遠端服務是如何開發的。
      (2) 雖然在程式在第二段中試圖利用 getWorker() 的方式想把剛剛第一段 設定的 Worker 物件取回(也就是 Many Ann),但是執行程式之後,其結果 如以下畫面:
      這個結果清楚顯示,每一次 service 被呼叫,該 service 物件(也就是 WorkerService)都會被重新產生一次,因此取回的 Worker 物件還是預設 建構元所產生的物件。雖然這個特性會使得部分應用變得不方便,但是如果 service 端的服務都配合著資料庫的使用,這個不方便應可以降到最低。
      依據 Axis 的文件,上述情形(每次呼叫 service,該 service 物件都會 重新被產生)稱之為 "request",這是 Axis 的預設行為;Axis 還提供其它 兩種行為模式:一個是 "session"(每一個 session 只有一個 service 物件), 另一個是 "application"(這個 client application 都只使用一個 service 物件)。 由於有可能會有多個程式同時呼叫該 service 物件的方法,使用 "session" 和 "application" 的開發人員必須自行控制可能發生的情形。
      若需要更改 service 物件的產生方式會以 application 為 scope,我們需要 更改 wsdd 的內容,更新後的 WSDD 檔的內容如下:
      <deployment xmlns="http://xml.apache.org/axis/wsdd/"
          xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="ObjectService" provider="java:RPC" style="wrapped" use="literal">
      
          <parameter name="scope" value="Application" />
      
          <parameter name="className" value="WorkerService" />
          <parameter name="allowedMethods" value="*" />
          <beanMapping qname="tns:Worker" xmlns:tns="http://DefaultNamespace"
                       languageSpecificType="java:Worker" />
        </service>
      </deployment>
      
      我們建議在註冊(deploy)這個服務之前,請記得先將之前的舊服務先 移除(undeploy)掉;在不更改其他的程式之下,重新執行 WorkerClient, 你將會看到如下的畫面:
      
      
      練習題: 如果將 WorkerService 的 getArray() 中的 Worker[] w = {new Worker(), w2}; 改成 Worker[] w = {worker, w2};,請問在不同的 scope 設定下,輸出的結果 有什麼變化?為什麼?






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



沒有留言:

張貼留言