程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 用JAX-RPC構建RPC服務和客戶機:使用Java API構建基於RPC的Web服務(二)

用JAX-RPC構建RPC服務和客戶機:使用Java API構建基於RPC的Web服務(二)

編輯:關於JAVA

構建客戶機來訪問 Web 服務

到目前為止,我們用了很多篇幅討論 JAX-RPC,但是還沒有實際使用這個 API。即使在自動部署 BookSearcher 類時,與 JAX-RPC 相關的工作也是由 Axis 完成的。現在,要讓 JAX-RPC 發揮作用了。 構建了希望訪問的 Web 服務之後,需要編寫客戶機來使用 Web 服務。

更新類路徑

在開始編寫代碼之前,需要修改類路徑。在前面,已經把幾個 JAR 文件放在 servlet 引擎的 lib/ 目錄中,並使用 Axis 檢驗 JSP 確認這些 JAR 的位置是正確的。因為為了運行 Web 服務,servlet 引 擎需要 JAX-RPC 和 Axis 類及其依賴項。

對於 Web 服務客戶機也是如此。當然,可以重復相同的步驟,把相同的 JAR 文件放在 JDK 或 JRE 的 ext/lib 目錄中。但是,這樣做會弄亂 Java 系統並導致版本問題,還會把在您的機器上運行 Java 的其他人弄糊塗。更好的方法是更新 CLASSPATH 變量並設置配置文件或環境,使修改只對您的個人用戶 設置生效。

添加 JAX-RPC 和 Axis JAR

首先,進入 Axis 安裝目錄,看看 lib 目錄。應該會看到與清單 18 相似的結果:

清單 18. Apache Axis 的 lib/ 目錄中的 JAR

[bdm0509:/usr/local/java/axis-1_4] ls lib/
axis-ant.jar     log4j-1.2.8.jar
axis.jar     log4j.properties
commons-discovery-0.2.jar  saaj.jar
commons-logging-1.0.4.jar  wsdl4j-1.5.1.jar
jaxrpc.jar

把所有這些 JAR 文件添加到類路徑中。惟一的可選文件是 axis-ant.jar,如果打算用 Ant 構建項目 ,就應該添加這個 JAR;它包含與 Ant 相關的擴展,支持在 Ant 構建文件中添加 Axis 任務。

添加這些 JAR 的最佳方法之一是,在 Windows 系統中設置環境變量,或者在 Mac OS X 或 Linux 環 境中使用 .profile(或 .bashrc)。清單 19 給出我的 .profile 的一部分,它定位 Axis 安裝目錄, 然後把其中的幾個 JAR 添加到類路徑中:

清單 19. 這個 .profile 把 Axis JAR 添加到 CLASSPATH 環境變量中

export JAVA_BASE=/usr/local/java
export JAVA_HOME=/Library/Java/Home
export XALAN_HOME=$JAVA_BASE/xalan-j_2_7_1
export AXIS_HOME=$JAVA_BASE/axis-1_4

export EDITOR=vi
export CVS_RSH=ssh
export PS1="['whoami':\w] "

export CLASSPATH=$XALAN_HOME/xalan.jar:
          $XALAN_HOME/xml-apis.jar:
          $XALAN_HOME/xercesImpl.jar:
          $XALAN_HOME/xalan.jar:
          $XALAN_HOME/xsltc.jar:
          $XALAN_HOME/serializer.jar:
          $AXIS_HOME/lib/axis.jar:
          $AXIS_HOME/lib/jaxrpc.jar:
          $AXIS_HOME/lib/commons-logging-1.0.4.jar:
          $AXIS_HOME/lib/commons-discovery-0.2.jar:
          $AXIS_HOME/lib/saaj.jar:
          $AXIS_HOME/lib/wsdl4j-1.5.1.jar:
          .

清單 19 中的硬換行只是為調整格式添加的。在實際的 .profile 文件中,所有類路徑項都在一行上 。

添加可選的 JAR

然後,還可能希望添加另外兩項。在運行檢驗 JSP 時,Axis 曾經報告了幾個可選項,也就是 activation.jar 和 mail.jar。如果按照前面的說明操作,可能已經下載了這兩個 JAR 並把它們添加到 servlet 引擎的 lib/ 目錄中。也應該把它們添加到類路徑中。支持這些實用程序的服務器只能與也支持 它們的客戶機交互,所以這是一個關鍵步驟。

可以引用 servlet 引擎中的 JAR(如果在同一台機器上運行客戶機代碼和 servlet 引擎),也可以 把這兩個 JAR 重新下載或復制到客戶機機器中,並把它們添加到類路徑中。清單 20 給出所需的代碼:

清單 20. 在類路徑中添加郵件和激活 JAR

export JAVA_BASE=/usr/local/java
export JAVA_HOME=/Library/Java/Home
export XALAN_HOME=$JAVA_BASE/xalan-j_2_7_1
export AXIS_HOME=$JAVA_BASE/axis-1_4
export TOMCAT_HOME=$JAVA_BASE/apache-tomcat-6.0.16

export EDITOR=vi
export CVS_RSH=ssh
export PS1="['whoami':\w] "

export CLASSPATH=$XALAN_HOME/xalan.jar:
          $XALAN_HOME/xml-apis.jar:
          $XALAN_HOME/xercesImpl.jar:
          $XALAN_HOME/xalan.jar:
          $XALAN_HOME/xsltc.jar:
          $XALAN_HOME/serializer.jar:
          $AXIS_HOME/lib/axis.jar:
          $AXIS_HOME/lib/jaxrpc.jar:
          $AXIS_HOME/lib/commons-logging-1.0.4.jar:
          $AXIS_HOME/lib/commons-discovery-0.2.jar:
          $AXIS_HOME/lib/saaj.jar:
          $AXIS_HOME/lib/wsdl4j-1.5.1.jar:
          $TOMCAT_HOME/lib/activation.jar:
          $TOMCAT_HOME/lib/mail.jar:
          .

一定要確保應用修改,或對 .profile 執行 source,或重新啟動終端應用程序。可以用 echo $CLASSPATH 命令檢查類路徑變量。應該會看到剛才添加的所有 JAR。

構建客戶機類

現在可以開始構建連接 Web 服務的類。首先編寫一個簡單的骨架;它應該是一個 Java 類,它不需要 實現特定的接口或擴展另一個類。實際上,客戶機類中沒有與 RPC 相關的內容。

清單 21 給出一個簡單的 BookSearcherClient 骨架。這個類通過命令行獲取一個關鍵字,然後調用 Web 服務中的方法。當然,這些方法目前僅僅是占位方法,稍後會編寫它們的邏輯。

清單 21. Web 服務客戶機類骨架

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;

public class BookSearcherClient {

  public static final String SERVICE_URL =
   "http://localhost:8080/axis/BookSearcher.jws";

  public BookSearcherClient() { }

  public Object[] search(String keyword) throws IOException {
   // placeholder
   return new Object[];
  }

  public static void main(String[] args) throws IOException {
   if (args.length != 1) {
    System.err.println("Usage: java BookSearcherClient [search keyword]");
    return;
   }
   String keyword = args[0];
   BookSearcherClient client = new BookSearcherClient();
   Object[] results = client.search(keyword);
   System.out.println("Returned books for keyword '" + keyword + "':");
   for (int i = 0; i<results.length; i++) {
    System.out.println(" " + results[i]);
   }
  }
}

清單 21 首先導入需要的所有類。這樣就不必分散地添加這些類,這個過程單調乏味而且容易產生錯 誤。幾個類與 URL 連接相關,在連接 Web 服務時需要這些類。另外四個類(兩個在 javax.xml 中,兩 個在 org.apache.axis.client 中)與 RPC 相關。後面會詳細討論每個類。

清單 21 還包含一個常量,這是要連接的 Web 服務的 URL。如果您使用不同的主機名或端口,就需要 修改這個 URL,使它與自己的 servlet 引擎和 BookSearcher Web 服務路徑匹配。URL 可能類似於 http://dev.myDomain.com/apps/BookSearcher.jws。可以使用不同的 URL,只要在浏覽器中輸入這個 URL 時能夠獲得與 圖 7 相似的響應即可。

接下來是一個空的構造函數和一個根據關鍵字進行搜索的方法。稍後將詳細討論這個方法,所以暫時 不必討論這個方法為什麼返回 Object[] (一個對象數組)。但是,目前這個方法只返回 null,這樣就 可以編譯這個類。這個方法包含大部分 RPC 客戶機代碼。

最後一段代碼(類的 main() 方法)創建一個新對象,從命令行獲取一個關鍵字,並把它發送給 search() 方法,這個方法進而向 Web 服務發出請求。然後,構建客戶機代碼,向 Web 服務發出請求。

創建一個 Service 和 Call 對象

JAX-RPC 客戶機代碼的兩個基本對象是 org.apache.axis.client.Call 和 org.apache.axis.client.Service。這兩個對象是 JAX-RPC javax.xml.rpc.Call 和 javax.xml.rpc.Service 類的實現。Apache Axis 提供了這些對象,其他 RPC 框架也提供自己的實現。

在任何 RPC 客戶機中,第一步都是創建 Service 的新實例。然後,從這個實例創建 Call 的新實例 ,這樣就可以調用服務了。清單 22 給出應該在 BookSearcherClient 類中添加的代碼:幾個新的成員變 量和 search() 中的幾行:

清單 22. 創建 Service 和 Call 實現

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;

public class BookSearcherClient {

  public static final String SERVICE_URL =
   "http://localhost:8080/axis/BookSearcher.jws";

  private Service service;
  private Call call;
  private URL serviceUrl;

  public BookSearcherClient() { }

  public Object[] search(String keyword) throws IOException {
   try {
    if (service == null) {
     service = new Service();
    }
    if (call == null) {
     call = (Call)service.createCall();
    }

    // placeholder
    return new Object[];
   } catch (ServiceException e) {
    throw new IOException("Error creating service call: " + e.getMessage ());
   }
  }

  public static void main(String[] args) throws IOException {
   if (args.length != 1) {
    System.err.println("Usage: java BookSearcherClient [search keyword]");
    return;
   }
   String keyword = args[0];
   BookSearcherClient client = new BookSearcherClient();
   Object[] results = client.search(keyword);
   System.out.println("Returned books for keyword '" + keyword + "':");
   for (int i = 0; i<results.length; i++) {
    System.out.println(" " + results[i]);
   }
  }
}

清單 22 中的代碼創建一個新的 Service 實現,然後以這個類作為工廠創建 Call 實現的新實例(這 與使用 JAXP 等框架時編寫的代碼非常相似,您應該很熟悉)。

在更健壯的實現中,可能用一個工廠類創建 Service 實現,使 Apache Axis 代碼根本不使用這個類 (至少不直接引用)。對於 Call 的 Apache Axis 實現也是如此。但是,為了簡單,這個示例直接使用 這些 Axis 類。

有了 Call 對象,就可以做一些真正的 RPC 工作了。

指定要連接什麼

在調用服務之前,需要配置 Call 對象。每個 Call 通常連接一個特定的服務 URL(按照 SOAP 和 RPC 術語,這是目標端點)和操作。如果想改變端點 URL 或操作,就要重新配置 Call(或創建新實例) 。

首先,創建一個 Java URL 對象來存儲目標端點(發布 Web 服務的可通過 Web 訪問的 URL):

serviceUrl = new URL(SERVICE_URL);

接下來,使用創建的 serviceURL 作為 Call 對象的 setTargetEndpointAddress() 方法的輸入參數 :

call.setTargetEndpointAddress(serviceUrl);

現在,Call 知道了要連接哪個服務。但是,還需要指定要調用的操作。這需要使用 setOperationName() 方法,而且不能只傳遞簡單的字符串。相反,必須傳遞一個 QName。但是,可以動 態地創建一個 QName,表示要使用 SOAP 編碼,然後提供要調用的操作的字符串名:

call.setOperationName(new QName("http://soapinterop.org/", "search"));

這行代碼寫起來容易,但是不容易理解。QName 僅僅是一種表示限定名的方法,可以向 Java 平台提 供與 XML 相似的數據,讓 Java 平台替您執行一些幕後處理。對於操作,要牢記 WSDL(一種 XML 變體 )用來表示每個操作。這是因為 Web 服務使用的 SOAP 傳輸協議以 XML 作為數據格式。所以,不能只使 用操作的 Java 字符串名;需要一個 QName。但是,在 javax.xml.namespace.QName 類的幫助下,這很 容易實現。清單 23 給出 search() 方法中需要的代碼(請記住,前面已經添加了所有導入語句):

清單 23. 為服務調用設置端點和操作

public Object[] search(String keyword) throws IOException {
  try {
   if (service == null) {
    service = new Service();
   }
   if (call == null) {
    call = (Call)service.createCall();
   }
   if (serviceUrl == null) {
    serviceUrl = new URL(SERVICE_URL);
   }
   call.setTargetEndpointAddress(serviceUrl);

   // Select operation to call 
   call.setOperationName(new QName("http://soapinterop.org/",
              "search"));

   // placeholder
   return new Object[];

  } catch (MalformedURLException e) {
   throw new IOException("Error creating service URL at " + SERVICE_URL);
  } catch (ServiceException e) {
   throw new IOException("Error creating service call: " + e.getMessage());
  }
}

獲得結果

下面就要執行實際調用(使用 Call 對象)並獲得結果。這要用 invoke() 方法來完成,這個方法以 一個對象數組作為參數:(Object[])。按照次序把參數放在這個數組中。對於 search() 方法,只有一個 參數(關鍵字),所以像下面這樣創建對象數組:

Object[] params = new Object[] { keyword };

如果要傳遞多個參數,比如在 addBook() 方法中,就要使用下面這樣的代碼:

Object[] params = new Object[] { bookTitle, keywordList };

然後,把這些參數傳遞給 invoke() 方法。請記住,不需要 指定要調用的服務的操作名(方法名); 這已經通過 setOperationName() 方法完成了。下面需要獲得結果並處理結果。

invoke() 方法返回一個 Object,但是這僅僅是實際返回類型的包裝器。對於返回字符串的方法,這 個對象包裝一個 String;只需把返回的對象轉換為正確的類型:

Object[] results = (Object[])call.invoke(new Object[] { keyword });

在這個示例中,服務上的方法返回一個 List。但是,SOAP 並不理解列表,所以它默認使用一種更泛 化的類型:對象數組。這增加了程序員的負擔,因為程序員必須親自處理這些泛型對象並確保類型安全。 這也是泛型和參數化目前在 Web 服務中用處不大的原因之一;在把信息從客戶機傳遞給服務器的過程中 ,會喪失類型安全性。

把這行代碼放到 search() 方法中,見清單 24:

清單 24. 執行調用並返回結果

public Object[] search(String keyword) throws IOException {
  try {
   if (service == null) {
    service = new Service();
   }
   if (call == null) {
    call = (Call)service.createCall();
   }
   if (serviceUrl == null) {
    serviceUrl = new URL(SERVICE_URL);
   }
   call.setTargetEndpointAddress(serviceUrl);

   // Select operation to call 
   call.setOperationName(new QName("http://soapinterop.org/",
              "search"));

   // Make call and get result 
   Object[] results = (Object[])call.invoke(new Object[] { keyword });

   return results;
  } catch (MalformedURLException e) {
   throw new IOException("Error creating service URL at " + SERVICE_URL);
  } catch (ServiceException e) {
   throw new IOException("Error creating service call: " + e.getMessage());
  }
}

與設置端點和操作的代碼一樣,這裡的代碼也很簡單,但是它會完成許多操作:

Call 實例查找請求要發送到的 URL 端點。

Call 實例查找要調用的操作。

使用目標端點和操作名以及您提供的參數,構造一個新的 SOAP 請求。這個請求采用 XML 格式。

把 XML 格式的 SOAP 請求發送給 Web 服務。

Web 服務用一個 XML 格式的響應來回復這個請求。

把生成的 XML 轉換為 Java 對象(在這裡是一個對象數組)並返回給程序。

當然,程序員只需調用 invoke() 方法即可。

現在,程序完成了,重新編譯它並確保 Web 服務和 servlet 引擎正在運行。然後,運行客戶機程序 ;應該會得到與清單 25 相似的結果:

清單 25. 運行 BookSearcher Web 服務客戶機程序

[bdm0509:~/Documents/developerworks/jax-rpc] java BookSearcherClient design
Returned books for keyword 'design':
  Emotional Design
  Indexed

這個結果沒有多少新奇的地方;實際上,它與 清單 7 中測試程序的結果(清單 8)非常接近。主要 的區別並不 明顯:所有請求和響應都使用 SOAP、XML 格式和 RPC 模型。您只需編寫非常簡單明了的 Java 代碼。

結束語

利用本教程的最佳方法是把學到的知識應用於自己要解決的問題。教程中的示例都是虛構的,只用來 解釋關鍵的概念。您應該選擇一個問題並應用 JAX-RPC 來解決它。您可以構建一個基於 RPC 的服務,用 它響應來自 Web 頁面的 Ajax 請求。Java 程序中可以通過從服務器獲取信息受益,並且 RPC 是響應 Java 程序請求的好方法。還可以把使用 SOAP 的現有代碼改為使用 JAX-RPC。在一兩個真實的應用程序 中應用 JAX-RPC,無論什麼應用程序。您肯定會遇到本教程沒有詳細討論的問題,在解決這些問題的過程 中,您會鞏固在這裡學到的知識。

JAX- RPC(以及一些最出色的 Web 服務工具集和 API)的優點之一是,API 非常寬松。換句話說, JAX-RPC 並沒有對發送或接收的數據施加太多限制。只要使用與語言相關的格式並正確地設置客戶機和服 務器,JAX-RPC 就能夠發揮作用。這大大減少了對編程的干擾;您只需設置好 JAX-RPC 組件,然後就可 以編寫自己的業務邏輯、應用程序邏輯等等。不必擔心 JAX-RPC 會影響您的代碼。

由於這些原因,JAX-RPC 是很不錯的 Web 服務技術。它使我們能夠對特定類型的服務器端程序進行特 定類型的調用。如果需要特定的客戶機-服務器關系,就使用 JAX-RPC。如果不需要,就不使用。換句話 說,您可以自由地決定是否使用 JAX-RPC;與此相反,選擇 Java 作為編程語言或選擇 Eclipse 作為開 發環境,就會喪失一定的自由度。您不必局限於 JAX-RPC,也不會被迫改變編程標准或實踐。實際上, JAX-RPC 是實現 Web 服務和客戶機-服務器交互的幾種最佳技術之一。

因此,您應該用 JAX-RPC 構建幾個程序,盡可能掌握 JAX-RPC 技術。當需要向部門、組織或公司外 的消費者提供簡單的 Web 服務時,JAX-RPC 是不錯的選擇。它符合 SOAP 和 WSDL 規范,大多數 Web 服 務消費者都能夠處理它。如果您要應付的局面沒這麼簡單,可以選用其他技術,包括新的 JAX-WS。總之 ,多掌握一種工具,就多一種選擇,更容易找到最適合您的 應用程序的解決方案。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved