Web 服務移轉實務:JAX-RPC 到 JAX-WS 和 JAXB
本主題說明將 Java™ API for XML-based RPC (JAX-RPC) Web 服務移轉至 Java API for XML-Based Web 服務 (JAX-WS) 和 Java Architecture for XML Binding (JAXB) Web 服務的實務。
開發環境可能隱藏工具中的大部分變更而不讓使用者看到,例如 WebSphere® Application Server 隨附的組合工具。另外,視 XML 中指定的資料而定,某些用於 JAX-RPC 服務的方法只需要在 JAX-WS 服務中稍加變更或完全無需變更即可使用。 但有時候確實需要在 JAX-WS 服務中變更。
因為 Java EE 環境強調相容性,大部分支援 JAX-WS 和 JAXB 新版規格的應用程式伺服器會繼續支援前一個 JAX-RPC 規格。 於是,現有的 Web 服務很可能仍然基於 JAX-RPC,但使用 JAX-WS 和 JAXB 程式設計模型來開發新的 Web 服務。
然而,隨著時間經過,應用程式經過修訂和改寫,有時將基於 JAX-RPC 的 Web 服務移轉至基於 JAX-WS 和 JAXB 程式設計模型的 Web 服務,可能才是最佳策略。 這可能是因為供應商選擇加強服務品質,但僅限於新的程式設計模型中。例如,SOAP 1.2 和「SOAP 訊息傳輸最佳化機制 (MTOM)」支援只適用於 JAX-WS 2.x 和 JAXB 2.x 程式設計模型,並不適用於 JAX-RPC。
JAX-RPC 到 JAX-WS 和 JAXB 程式設計模型的比較
如果要尋找有關 JAX-RPC 及 JAX-WS 和 JAXB 程式設計模型之間有何不同的相關資訊,IBM developerWorks 已發佈許多 Web 服務提示和技巧主題。
開發模型
下列顯示 JAX-RPC 及 JAX-WS 和 JAXB 的高階合併開發模型:
從高階觀點看,模型都非常相似。唯一的差別是 JAX-RPC 模型會產生可攜式與不可攜的構件;JAX-WS 和 JAXB 模型不會產生不可攜的構件。
- Web 服務說明語言 (WSDL) 檔:符合 W3C 組織所發佈的 WSDL 建議的文件。
- (選用)配置檔:
- WSDL 到 Java 產生工具:規格說明所需的工具,而不是工具的名稱。一般而言,JAX-WS 和 JAXB 的工具是 wsimport,而 JAX-RPC 的工具是 wscompile 和 wsdeploy。
- 可攜式構件:這些是由 WSDL 到 Java 產生工具所產生的檔案,在 JAX-RPC 或 JAX-WS 和 JAXB(視何者適用而定)規格中有明確定義的對映。 這些構件不需修改即可在不同的 JAX-RPC 或 JAX-WS 和 JAXB 實作之間使用。
- 不可攜的構件:這些是由 WSDL 到 Java 產生工具所產生的檔案,在 JAX-RPC 或 JAX-WS 和 JAXB(視何者適用而定)規格中沒有明確定義的對映。 這些構件通常需要修改,才能在不同的 JAX-RPC 或 JAX-WS/JAXB 實作之間使用。
- 實作程式碼:這些是您為了符合實作的特定需求而需要的程式碼。
- 部署資訊:在特定環境中執行應用程式時所需的其他資訊。
- Web 保存檔 (WAR):以 SampleService 影像為例,WAR 檔這個保存檔包含將 Web 服務部署到特定環境所需的一切項目。這有時稱為處理過的 WAR 檔。
開發和執行時期環境
專業的開發環境會將許多工作自動化,以簡化 Web 服務移轉。 如果要開發基於 WebSphere 的應用程式,包括 Web 服務,您可以使用 WebSphere Application Server 隨附的組合工具。
- IBM WebSphere Application Server 6.1 版,此版本支援 JAX-RPC 規格。
- IBM WebSphere Application Server 6.1 版 Feature Pack for Web Services,此版本支援 JAX-WS 和 JAXB 規格。
範例
下列範例顯示將 JAX-RPC Web 服務移轉到 JAX-WS 和 JAXB Web 服務時,可能需要的一些程式碼變更。 尤其,從用戶端和伺服器端,強調您必須撰寫的實作程式碼中的變更。如果不會引進新的功能,則從 JAX-RPC 移至 JAX-WS 和 JAXB 所需的變更可能很少。
第一個範例是基於 JAX-RPC 的 Web 服務範例,從 WSDL 檔建立(由上而下開發);同一個 WSDL 檔用來產生基於 JAX-WS 和 JAXB 的服務。 此 WSDL 檔說明 Web 服務 SampleService,具有下圖所述的這些作業:
- xsd:int calcShippingCost(xsd:int, xsd:int)
- xsd:string getAccountNumber(xsd:string)
- xsd:dateTime calculateShippingDate(xsd:dateTime)
- xsd:string ckAvailability(xsd:int) 會建立 invalidDateFault
- Person findSalesRep(xsd:string)
<complexType name="Person">
<sequence>
<element name="name" type="xsd:string"/>
<element name="age" type="xsd:int"/>
<element name="location" type="impl:Address/>
</sequence>
</complexType>
Address 由下列片段定義:<complexType name="Address">
<sequence>
<element name="street" type="xsd:string"/>
<element name="city" type="xsd:string"/>
<element name ="state" type="xsd:string"/>
<element name="zip" type="xsd:int"/>
</sequence>
</complexType>
下圖也顯示作業 ckAvailability(xsd:int) 會產生 invalidDateFault 異常狀況。
檢閱工具所建立的服務程式碼。下列資訊包括檢查針對 JAX-RPC 執行時期建立什麼,以及針對 JAX-WS 和 JAXB 執行時期建立什麼。
對於 JAX-RPC,工具接受 WSDL 檔作為輸入,而且除了其他檔案,還產生 SampleService.java 和 SampleServiceImpl.java 介面。 SampleService.java 介面定義介面,在下列程式碼區塊中可以檢閱產生的程式碼。 SampleServiceSoapBindingImpl.java 介面提供實作的架構,通常可修改來加入您自己的邏輯。
/**
* SampleService.java
*
* This file was auto-generated from WSDL
* by the IBM web services WSDL2Java emitter.
* cf20633.22 v82906122346
*/
package simple;
public interface SampleService extends java.rmi.Remote {
public java.lang.Integer calcShippingCost(java.lang.Integer shippingWt,
java.lang.Integer shippingZone) throws java.rmi.RemoteException;
public java.lang.String getAccountNumber(java.lang.String accountName)
throws java.rmi.RemoteException;
public java.lang.String[] ckAvailability(int[] itemNumbers)
throws java.rmi.RemoteException, simple.InvalidDateFault;
public java.util.Calendar calculateShippingDate(
java.util.Calendar requestedDate)
throws java.rmi.RemoteException;
public simple.Person findSalesRep(java.lang.String saleRepName)
throws java.rmi.RemoteException; }
package simple;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by the JAX-WS SI.
* JAX-WS RI IBM 2.0_03-06/12/2007 07:44 PM(Raja)-fcs
* Generated source version: 2.0
*
*/
@WebService(name = "SampleService", targetNamespace = "http://simple") public interface SampleService {
/**
*
* @param shippingWt
* @param shippingZone
* @return
* returns java.lang.Integer
*/
@WebMethod
@WebResult(name = "shippingCost", targetNamespace = "")
@RequestWrapper(localName = "calcShippingCost", targetNamespace = "http://simple",
className = "simple.CalcShippingCost")
@ResponseWrapper(localName = "calcShippingCostResponse", targetNamespace = "http://simple",
className = "simple.CalcShippingCostResponse")
public Integer calcShippingCost(
@WebParam(name = "shippingWt", targetNamespace = "")
Integer shippingWt,
@WebParam(name = "shippingZone", targetNamespace = "")
Integer shippingZone);
/**
*
*
@param accountName
* @return
* returns java.lang.String
*/
@WebMethod
@WebResult(name = "accountNumber", targetNamespace = "")
@RequestWrapper(localName = "getAccountNumber", targetNamespace = "http://simple",
className = "simple.GetAccountNumber")
@ResponseWrapper(localName = "getAccountNumberResponse", targetNamespace = "http://simple",
className = "simple.GetAccountNumberResponse")
public String getAccountNumber(
@WebParam(name = "accountName", targetNamespace = "")
String accountName);
/**
*
* @param requestedDate
* @return
* returns javax.xml.datatype.XMLGregorianCalendar
*/
@WebMethod
@WebResult(name = "actualDate", targetNamespace = "")
@RequestWrapper(localName = "calculateShippingDate", targetNamespace = "http://simple",
className = "simple.CalculateShippingDate")
@ResponseWrapper(localName = "calculateShippingDateResponse", targetNamespace = "http://simple",
className = "simple.CalculateShippingDateResponse")
public XMLGregorianCalendar calculateShippingDate(
@WebParam(name = "requestedDate", targetNamespace = "")
XMLGregorianCalendar requestedDate);
/**
*
* @param itemNumbers
* @return
* returns java.util.List<java.lang.String>
* @throws InvalidDateFault_Exception
*/
@WebMethod
@WebResult(name = "itemAvailability", targetNamespace = "")
@RequestWrapper(localName = "ckAvailability", targetNamespace = "http://simple", className = "simple.CkAvailability")
@ResponseWrapper(localName = "ckAvailabilityResponse", targetNamespace = "http://simple",
className = "simple.CkAvailabilityResponse")
public List<String> ckAvailability(
@WebParam(name = "itemNumbers", targetNamespace = "")
List<Integer> itemNumbers)
throws InvalidDateFault_Exception
;
/**
*
* @param saleRepName
* @return
* returns simple.Person
*/
@WebMethod
@WebResult(name = "salesRepInfo", targetNamespace = "")
@RequestWrapper(localName = "findSalesRep", targetNamespace = "http://simple", className = "simple.FindSalesRep")
@ResponseWrapper(localName = "findSalesRepResponse", targetNamespace = "http://simple",
className = "simple.FindSalesRepResponse")
public Person findSalesRep(
@WebParam(name = "saleRepName", targetNamespace = "")
String saleRepName);
}
比較程式碼範例
@WebMethod
@WebResult(name = "shippingCost", targetNamespace = "")
@RequestWrapper(localName = "calcShippingCost", targetNamespace = "http://simple",
className = "simple.CalcShippingCost")
@ResponseWrapper(localName = "calcShippingCostResponse", targetNamespace = "http://simple",
className = "simple.CalcShippingCostResponse")
public Integer calcShippingCost(
@WebParam(name = "shippingWt", targetNamespace = "")
Integer shippingWt,
@WebParam(name = "shippingZone", targetNamespace = "")
Integer shippingZone);
但如果不看註釋,程式碼行就變成:public Integer calcShippingCost(
Integer shippingWt,
Integer shippingZone);
這幾行與針對 JAX-RPC 產生的程式碼幾乎相同。唯一的差別是 JAX-RPC 程式碼會產生如下的 java.rmi.RemoteException 錯誤:public java.lang.Integer calcShippingCost(java.lang.Integer shippingWt,
java.lang.Integer shippingZone) throws java.rmi.RemoteException;
按照此邏輯,這三個方法具有本質上相同的簽章:public Integer calcShippingCost(Integer shippingWt, Integer shippingZone)
public String getAccountNumber(String accountName)
public Person findSalesRep(String saleRepName)
這表示從 JAX-RPC 移轉到 JAX-WS 不會直接影響這些方法,在基於 JAX-RPC 的環境中成功執行的原始實作程式碼,可能不必修改即可用於這些方法。不過,這兩個方法有不同的簽章:
public java.util.Calendar calculateShippingDate(
java.util.Calendar requestedDate)
public java.lang.String[] ckAvailability(int[] itemNumbers)
throws java.rmi.RemoteException,
simple.InvalidDateFault
JAX-WS 和 JAXB:public XMLGregorianCalendar calculateShippingDate(
XMLGregorianCalendar requestedDate)
public List<String> ckAvailability(List<Integer> itemNumbers)
throws InvalidDateFault_Exception
- SampleServiceSoapBindingImpl.java
- SampleServiceImpl.java
- 從 XML 名稱到 Java 名稱的對映不同
對於 calculateShippingDate 方法,輸入參數和傳回參數都已從 java.util.Calendar 類型變更為 XMLGregorianCalendar 類型。 這是因為 WSDL 已將這些參數指定為 xsd:dateTime 類型,但 JAX-RPC 將這個資料類型對映到 java.util.Calendar,而 JAX-WS 和 JAXB 將它對映到 XMLGregorianCalendar。
- 從 XML 到 Java 的陣列對映不同
對於 ckAvailability 方法,此變更起因於 XML 陣列的資料對映。
JAX-RPC 對映到下列 WSDL 元素:
上面的元素對映到下列 Java 原始碼:<element maxOccurs="unbounded" name="itemNumbers" type="xsd:int"/> <element maxOccurs="unbounded" name="itemAvailability" type="xsd:string"/>
JAX-WS 和 JAXB 對映下列 WSDL 元素:int[] itemNumbers java.lang.String[] itemAvailability
List<Integer> itemNumbers List<String> ckAvailability
- 異常狀況的對映不同對於 ckAvailability 方法,JAX-RPC 程式碼產生下列錯誤:
JAX-WS 程式碼產生下列錯誤:simple.InvalidDateFault
這些異常狀況的建構子不同(名稱除外)。 因此,產生錯誤的實際 JAX-RPC 程式碼可能顯示為:InvalidDateFault_Exception
對於 JAX-WS,程式碼使用類似下列範例的指令:throw new InvalidDateFault("this is an InvalidDateFault");
在使用異常狀況的情況下,使用它的程式碼需要變更。throw new InvalidDateFault_Exception( "this is an InvalidDateFault_Exception", new InvalidDateFault());
移轉程式碼
現在已解釋過程式碼的差異,請檢閱需要變更的方法的原始程式碼,以及如何將此程式碼變更為能夠在 JAX-WS 和 JAXB 環境中運作。
public java.util.Calendar calculateShippingDate(
java.util.Calendar requestedDate) throws java.rmi.RemoteException {
// Set the date to the date that was sent to us and add 7 days.
requestedDate.add(java.util.Calendar.DAY_OF_MONTH, 7);
// . . .
return requestedDate;
}
您可以撰寫新的程式碼來直接使用新的類型,如下列範例所示:public XMLGregorianCalendar calculateShippingDate(
XMLGregorianCalendar requestedDate) {
try {
// Create a data type factory.
DatatypeFactory df = DatatypeFactory.newInstance();
// Set the date to the date that was sent to us and add 7 days.
Duration duration = df.newDuration("P7D");
requestedDate.add(duration);
} catch (DatatypeConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// . . .
return requestedDate;
}
public java.lang.String[] ckAvailability(int[] itemNumbers)
{
String[] myString = new String[itemNumbers.length];
for (int j = 0; j < myString.length; j++) {
. . .
if ( ... )
throw new simple.InvalidDateFault(“InvalidDateFault”);
. . .
if ( . . .)
myString[j] = “available: “ + jitemNumbers[j] ;
else
myString[j] = “not available: “ + jitemNumbers[j];
}
return myString;
}
上面的程式碼接受 int[] 作為輸入,並傳回 String[]。對於 JAX-WS 和 JAXB 版,這些分別是 List<Integer> 和 List<String> 元素。
如果處理這些陣列並排除 Exception 程式碼,則 JAX-RPC 程式碼類似如下:public java.lang.String[] ckAvailability(int[] itemNumbers)
{
String[] myString = new String[itemNumbers.length];
for (int j = 0; j < jitemNumbers.length; j++) {
. . .
if ( . . .)
myString[j] = “available: “ + itemNumbers.get(j);
else
myString[j] = “not available: “ + itemNumbers.get(j);
}
return myString;
}
如果使用 List 而不使用陣列,則可產生下列同等的 JAX-WS 和 JAXB 程式碼:List <String> ckAvailability(List <Integer> itemNumbers)
{
ArrayList<String> retList = new ArrayList<String>();
for (int count = 0; count < itemNumbers.size(); count++) {
. . .
if ( . . .)
retList.add(“available: “ + itemNumbers.get(j));
else
retList.add(“not available: “ + itemNumbers.get(j));
}
return retList;
}
由於從 XML 到 Java 的異常狀況對映不同,迫使 JAX-WS 程式碼使用 InvalidDateFault_Exception
而不是 InvalidDateFault。throw new InvalidDateFault_Exception( "test InvalidDateFault_Exception", new InvalidDateFault());
此方法的 JAX-WS 和 JAXB 最終實作可能類似下列程式碼:List <String> ckAvailability(List <Integer> itemNumbers)
{
ArrayList<String> retList = new ArrayList<String>();
for (int count = 0; count < itemNumbers.size(); count++) {
if ( . . . ) {
throw new InvalidDateFault_Exception(
"test InvalidDateFault_Exception",
new InvalidDateFault());
}
. . .
if ( . . .)
retList.add(“available: “ + itemNumbers.get(count));
else
retList.add(“not available: “ + itemNumbers.get(count));
}
return retList;
}
移轉程式碼時有多種方式可選擇。加以練習並使用有效的開發環境,從 JAX-RPC 移轉到 JAX-WS 就會很簡單。