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 環境。

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)
另外,假設 Person 是由 WSDL 檔中的下列綱目片段定義:
<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 介面提供實作的架構,通常可修改來加入您自己的邏輯。

JAX-RPC 版的 SampleService.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; }   
對於 JAX-WS 和 JAXB,工具接受 WSDL 檔作為輸入,而且就像 JAX-RPC 的情況一樣,也會產生 SampleService.java 和 SampleServiceImpl.java 介面。 如同 JAX-RPC 一樣,SampleService.java 介面也定義如下列程式碼區塊所示的介面。 SampleServiceImpl.java 介面提供實作的架構,通常可修改來加入您自己的邏輯。
註: 工具已在程式碼中添加註釋。
JAX-WS 和 JAXB 版的 SampleService.java 介面:
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);  

}   

比較程式碼範例

乍看之下,兩個介面似乎沒什麼相似之處。 如果不看為 JAX-WS 和 JAXB 添入的其他註釋資訊,則程式碼範例就很相似。例如,在 JAX-WS 和 JAXB 版的 calcShippingCost 方法中,有下列幾行程式碼:
@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 的環境中成功執行的原始實作程式碼,可能不必修改即可用於這些方法。

不過,這兩個方法有不同的簽章:

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 
註: SampleServiceImpl.java 中沒有註釋,可能較容易比較架構實作檔案:
  • 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 元素:
    <element maxOccurs="unbounded" name="itemNumbers" type="xsd:int"/>
    <element maxOccurs="unbounded" name="itemAvailability" type="xsd:string"/>
    上面的元素對映到下列 Java 原始碼:
    int[] itemNumbers
    java.lang.String[] itemAvailability
    JAX-WS 和 JAXB 對映下列 WSDL 元素:
    List<Integer> itemNumbers 
    List<String> ckAvailability
  • 異常狀況的對映不同
    對於 ckAvailability 方法,JAX-RPC 程式碼產生下列錯誤:
    simple.InvalidDateFault
    JAX-WS 程式碼產生下列錯誤:
    InvalidDateFault_Exception
    這些異常狀況的建構子不同(名稱除外)。 因此,產生錯誤的實際 JAX-RPC 程式碼可能顯示為:
    throw new InvalidDateFault("this is an InvalidDateFault");
    對於 JAX-WS,程式碼使用類似下列範例的指令:
    throw new InvalidDateFault_Exception( "this is an InvalidDateFault_Exception", new InvalidDateFault()); 
    在使用異常狀況的情況下,使用它的程式碼需要變更。

移轉程式碼

現在已解釋過程式碼的差異,請檢閱需要變更的方法的原始程式碼,以及如何將此程式碼變更為能夠在 JAX-WS 和 JAXB 環境中運作。

假設原始 (JAX-RPC) 實作程式碼類似如下:
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;
}
移轉 ckAvailability 方法的程式碼時,需要變更,因為 JAX-RPC 和 JAX-WS 會以不同方式從 XML 對映陣列和異常狀況。 假設原始 JAX-RPC Web 服務程式碼類似下列範例:
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 simple.InvalidDateFault(“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 就會很簡單。

指出主題類型的圖示 參照主題



時間戳記圖示 前次更新: last_date
http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=rwbs_migjaxrpc2jaxws
檔名:rwbs_migjaxrpc2jaxws.html