Java Web 應用程式轉換為 ASP.NET
Brian Jimerson
本文討論: · 資源定位 · I/O 流 · 日誌記錄和集合 · 重構 |
本文使用了以下技術: ASP.NET、JLCA 和 C# |
下載本文中所用的代碼: JLCA2007_05.exe (157 KB)
瀏覽線上代碼
目錄
關於 JLCA
定位資源
處理輸入/輸出 API
日誌記錄
集合
篩選器和 HTTP 處理常式
源樹和命名規範
何時重構約定
目錄布局和命名空間
屬性
Pascal 大小寫方法名稱
總結
典型的軟體開發週期遵循簡單的模型:收集要求、設計應用程式、編寫代碼、測試軟體和部署軟體。但是,有時新的開發項目是基於客戶想用來部署應用程式的平台而啟動的。在這種情況下,可以將現有應用程式的基本代碼轉換或移植到預期的平台。
在本文中,我將全面介紹如何將 Java Web 應用程式轉換為以 C# 實現的 ASP.NET 應用程式。本文基於我所參與的實際項目。在該項目中,我們有現成的基於 Java 的應用程式,而客戶希望採用它的 ASP.NET 版本。我首先介紹 Microsoft Java Language Conversion Assistant (JLCA),並示範在兩個平台中沒有直接對應項的常見開發範例,例如:
· 輸入/輸出
· 資源解析
· 源樹布局和命名規範
· 利用執行環境
此應用程式作為符合 SOAP 的一項 Web 服務來實現,並採用傳統的關聯式資料庫非揮發性儲存體。我不會討論實際的 Web 服務展示層和 SOAP 介面,而是介紹支援它的應用程式。本文的範例程式碼可供下載。
關於 JLCA
JLCA 工具用於將 Java 應用程式轉換成 C# 應用程式。自 Visual Studio .NET 2003 開始,該工具隨 Visual Studio 一起提供。目前的版本的 JLCA 3.0 包含在 Visual Studio 2005 中,也可以從 JLCA 首頁免費下載。
3.0 版包含對以下方面的轉換的增強:諸如 Servlets 和 Java Server Pages (JSP) 這樣的 Java 項目,以及使用 Swing 或抽象視窗工具包 (AWT) 的胖用戶端應用程式。實際上,JLCA 是很好的用於啟動轉換的工具,但它不會成功完成整個過程。因此,不要期望這個過程是完全自動的,使用該工具之後,仍然需要對被轉換的項目進行一些手動解析。
若要在 Visual Studio 2005 中開始 JLCA 的入門學習,請單擊“檔案”|“開啟”|“Convert”以啟動嚮導。嚮導的螢幕可一看就明白。您需要輸入的關鍵資訊部分( 1 所示)是現有 Java 項目的根目錄。
圖 1 在 JLCA 嚮導中輸入 Java 根目錄 (單擊該映像獲得較大視圖)
然後,JLCA 開始將 Java 原始碼轉換為 C#。該過程所用時間不會太長。我們包含大約 100 個類檔案的基本代碼用了不到 10 分鐘就完成了轉換,正好夠喝杯咖啡的時間。當然,對於不同項目和系統,此數字將有所差異。
轉換過程完成後,JLCA 將為錯誤和警告建立 HTML 報告。這些項還將作為有問題的成員的注釋輸入所產生的 C# 代碼中。為給您提供協助,每一項還包含有關解決問題的詳細資料的超連結。
很多警告都可以安全忽略。它們只是為了記錄 Java 和 C# 行為的差異,例如,警告內容:“基元類型之間的強制類型轉換可能有不同的行為”。但仍然應當查看每個警告,以確保這些不同的行為不會影響應用程式。
第一次查看轉換報告時,所報告的問題似乎可能太多。在本例中,有 816 個錯誤和 16 個警告。但大多數錯誤都可以歸類為三種類別,並且非常容易解決。它們所屬的三種類別是:
· 沒有 C# 或 Microsoft .NET Framework 等效項的成員。
· 在 .NET Framework 中沒有直接等效項(例如,Hibernate 和 log4j)的流行的第三方 Java 庫。
· 處理類載入或資源解析的項。
另外值得注意的是,JLCA 似乎不會試圖解析它找不到的匯入包(或使用命名空間語句),而是直接將它們傳遞給產生的 C# 代碼。如果嘗試編譯新的 C# 應用程式,則可能會得到比轉換報告中更多的編譯器錯誤。
但是,不用擔心。前面提到過,這些錯誤大部分屬於相同的重複模式,很容易一起解決。我將在以下幾節中介紹這些常見錯誤的解決辦法,並討論為了使轉換後的應用程式成為真正的 C# 應用程式而需要執行的其他任務。
定位資源
Java 中的資源定位過程(具體來說,是指找到並負載檔案資源)與 C# 中的資源定位有很大不同。Java 使用類載入程式在運行時載入和分析類定義。類載入程式的部分責任是管理類的上下文,並為它載入的類提供環境便利。在 Java 中,類載入程式使用一種稱為 classpath 的特殊類型的環境變數來定位資源。classpath 與路徑環境變數的相似之處在於:它定義了類載入程式應當在哪裡尋找其他類和資源。希望載入另一個類或資源的應用程式可以通過將相對於 classpath 的檔案位置告訴類載入程式來實現載入。
Java 中非常常見的資源解析方法是使用稱為屬性檔案的特殊類型的檔案來獲得可配置資訊,例如,串連或主機資訊、其他資源的路徑、本地化字串和用於身分識別驗證的憑據。屬性檔案包含 name=value 對,並以換行分隔。此格式非常類似於 INI 檔案,但沒有節。
此外,不管資源是否實際位於檔案系統中,都始終使用檔案系統標記法執行資源定位。這將緩解開發人員必須知道應用程式如何部署的負擔。其他類型的資源(例如,映像和二進位檔案)以相同方式進行定位和載入。下面是在 Java 中定位和使用屬性檔案的樣本:
複製代碼
InputStream is = this.getClass().getResourceAsStream(
“/application.properties”);
Properties properties = new Properties();
properties.load(is);
String myValue = properties.getProperty(“myKey”);
相反,可以用兩種不同方式部署和載入 .NET Framework 中的資源:作為二進位資源嵌入程式集中,或作為本地檔案系統中的檔案。
訪問資源的合適技巧取決於資源的位置。例如,如果它是檔案系統資源,則可以像下面這樣做:
複製代碼
string fileName = Path.Combine(Path.GetFullPath(
@”..\config\”), “properties.xml”);
Stream fileStream = File.Open(fileName, FileMode.Open);
//Do something with the stream
但如果資源嵌入程式集中,則更有可能這樣做:
複製代碼
Assembly assembly = Assembly.GetExecutingAssembly();
Stream fileStream = assembly.GetManifestResourceStream(
GetType(), “properties.xml”);
//Do something with the stream
我的團隊編寫的應用程式有一個假設,就是要進行解析不必知道資源如何部署。由於在整個應用程式中要載入很多資源,因此分析每個載入的資源、確定資源如何部署然後修改代碼以正確載入它會耗費大量精力。因此,我們建立了稱為 ResourceLocator 的公用程式類。
設計 ResourceLocator 是為了類比 Java 類載入程式的基於 classpath 解析資源的能力。由於對載入資源的所有調用都是用此方法編寫的,因此它似乎是侵入性最低的轉換方法。編寫 ResourceLocator 之後,我們需要做的只是將調用從 Class.getResourceAsStream 更改為 ResourceLocator.LocateResource。在 Visual Studio 中使用簡單的尋找和替換,即可實現。
從根本來講就是,ResourceLocator 獲得要尋找的資源的名稱和相對路徑,然後嘗試通過遍曆可用程式集和本地檔案系統來找到該資源。還有可提供更精細控制的重載方法,例如,指定搜尋地區的順序,以及選擇只搜尋程式集或檔案系統。MSDN 雜誌網站上的程式碼範例包含 ResourceLocator 的原始碼。
您可能認為通過尋找所有可用位置來找到資源的成本非常高,事實確實如此。但是,在應用程式中定位和載入的所有資源隨後將被緩衝。這意味著在應用程式執行期間每個資源只載入一次,因此會減少這一開銷(但它確實會增加記憶體使用量量,如果要載入大量資源,這會是個問題)。因此,考慮到它使我們減少了很多代碼更改,我們認為這樣的取捨是可接受的。還可以隨著時間推移修改這些類型的更改,首先利用簡單的解決方案使連接埠快速運行,然後緩慢而可靠地更改實施,以更好地符合基於 .NET 的應用程式的設計和實施準則。
處理輸入/輸出 API
Java 中的 I/O API 與 .NET 中轉換後需要處理的 I/O API 有幾處差異。一個重要的差異是 .NET I/O 流是雙向的,而 Java I/O 流是單向的。這意味著在 .NET 編程中,理論上可以對同一個流執行讀取和寫入。但在 Java 中,只能對流執行讀取或寫入,但不能對同一流同時執行這兩個操作。此差異在轉換期間不會造成很大的困難,因為它是擴大的差異,並且 .NET 流至少提供了與其 Java 對應項一樣多的功能。
如果需要保持單向流的安全性,那麼可以利用 .NET I/O 讀取器和寫入器。它們會將基礎流打包,以提供讀取或寫入功能。結果將使 I/O 操作在編程上類似於 Java I/O 操作。
對於我們的應用程式,直接存取流就已足夠。JLCA 可以正確轉換 I/O 操作,因此不必為執行編譯而進行任何更改。但是,我們小心檢查了轉換後的代碼,因為在這些低層級操作中很容易出現邏輯錯誤。
日誌記錄
日誌記錄所注重的是在代碼中的特定執行點上將訊息(如,捕獲的異常、可能需要用到調試資訊的邏輯方面以及被載入的配置資訊)寫入目標的能力。Java 和 .NET Framework 都為記錄資訊提供了功能強大的架構,但其設計和實現有很大差異。
在 Java 中,通常通過與最新版本的 Sun Microsystems Java 開發套件 (JDK) 一起分發的 Apache Software Foundation (ASF) log4j 架構或 Java 日誌 API 來完成應用程式記錄檔記錄功能。Java 日誌 API 在執行上非常類似於 log4j,因此,出於此討論目的可以交替應用這兩個架構。對於基於 .NET 的應用程式,Microsoft Enterprise Library 為日誌記錄提供了強大的應用程式塊,稱為日誌應用程式塊(請參閱 Microsoft Logging Application Block homepage)。
Java 和 .NET 中的標準日誌架構都提供了強大功能,並且支援設計時配置、日誌目標宿主(例如,資料庫、檔案和電子郵件收件者)等等。但請注意,API 設計各不相同,因而在轉換期間需要您進行一些手動幹預。
為了更好理解 API 的差異,在這裡以圖 2 所示的兩個程式碼片段為例進行說明。它們示範了在 Java 和 C# 中執行的常見日誌方案。使用 log4j 的程式碼片段通過 getLog Factory 方法建立 Log 對象的靜態執行個體,並將它賦給 MyClass 類別。然後,該代碼在調試層級將一些資訊列印到 Log。C# 程式碼片段(使用日誌應用程式塊)建立了一個 LogEntry 類的新執行個體。它表示目標日誌中的一個條目。然後,代碼為日誌條目分配優先順序 2,並將類別設定為 Debug,然後提供了一條訊息。最後,它被寫入 Logger 類。
Figure 2 Java 和 C# 中的日誌記錄
使用 log4j 的 Java
複製代碼
private static final Log log = Logger.getLog(MyClass.class);
...
//Somewhere else in the class
log.debug(“Printing some debug information.”);
使用日誌應用程式塊的 C#
複製代碼
Logger.Write(”Printing some debug information.”, “Debug”);
務必注意,兩個平台中的日誌記錄都是由外部配置進行控制的。諸如日誌記錄目標、選擇性日誌訊息篩選和日誌條目格式設定等資訊都包含在此配置中。我故意在此樣本中刪除了配置,因為它與我們的討論無關。
查看這兩個樣本就會看到,它們執行非常相似的功能,但以不同方式實現。log4j 樣本使用類別來確定訊息是否已記錄(基於它的層級)以及它所記錄到的目標位置,而日誌應用程式塊則使用優先順序和類別的篩選器的組合來確定要記錄的內容。
Logger 類提供了幾個重載的 Write 方法,每個方法具有不同的靈活性層級(其中包括一個重載,它允許您提供一個 LogEntry 執行個體,其中擁有大量用於調整如何記錄資訊的控制設定)。但是,假如是圖 2 中使用的簡單重載,我們就能與Regex結合,用搜尋和替換來完成批量轉換過程。
日誌記錄是非常耗費資源的過程,需要謹慎使用,以確保它不會影響應用程式的效能。最後,在應用程式中搜尋和替換日誌調用只需要幾小時。
集合
.NET Framework 和 Java 都提供了強大的集合 API。二者都有非常好的可擴充性,並且涵蓋了使用集合時可能遇到的大多數情形。下面詳細介紹兩個常用的集合類型:列表和詞典。
列表是可以按索引訪問的集合。它們是有順序的集合,可以被當作一種一維數組。另一方面,詞典則是名稱與值對的集合。名稱是用於訪問集合中的值的鍵。注意,在詞典中不能保證內容是有順序的。
圖 3 列出了列表和詞典的等效 C# 和 Java 實現。JLCA 可以很好地將這些 Java 集合類轉換成其 .NET 等效項,並且轉換後要進行的處理很少。在這裡將產生警告,表明這些常見集合的行為在兩個平台上不同,因此應當對轉換進行驗證。但是,轉換過程的絕大部分應當是成功和有效。
Figure 3 在 .NET 和 Java 中的等價集合類
|
.NET |
Java |
列表介面 |
IList, IList<T> |
List |
常見列表類 |
ArrayList, List<T> |
ArrayList, Vector |
詞典介面 |
IDictionary, IDictionary<TKey,TValue> |
Map |
常見詞典類 |
Hashtable, Dictionary<TKey,TValue> |
HashMap, HashTable |
實際上,我們在轉換集合時,在原始 Java 應用程式中使用專門集合的地方遇到了問題。例如,使用 Java LinkedHashSet 類時就會遇到這種問題。按照 Java API 文檔,LinkedHashSet 類可確保 HashSet(它是一種詞典類型)中的條目具有一致的順序,同時避免了其他排序詞典產生的開銷。雖然 LinkedHashSet 的定義簡單易懂,但它在應用程式中的使用意義並不明確。(編寫代碼的人員已經離開了團隊,並且關於為何使用它沒有留下任何文檔記錄。)此外,使用它的上下文不能為我們提供任何資訊,因此不清楚使用此類的目的是旨在修複問題,還是處於其他原因。
在通查代碼時,我們發現沒有使用它的正當理由,因而我們有三種選擇:假設在原始應用程式中實際上不需要它、為 .NET 應用程式編寫我們自己的實現、在 .NET Framework 中選取最接近的合適的集合。我們假設專門的 LinkedHashSet 實現是不需要的,因為沒有跡象表明在其他任何地方使用了排序名稱值對。同樣,我們替換了基本 .NET Hashtable 類,並且我們使用現有單位和整合測實驗證了正確的行為。但是,如果我們發現了效能或功能問題,則可能已經替換了 .NET Framework 2.0 中的 SortedDictionary<TKey, TValue> 類,該類表示按鍵進行排序的鍵/值對的集合;在內部,它的實現使用基於紅 — 黑樹資料結構的集合。
在我們的項目中,只有四個地方使用了專門的 Java 集合類,並且它們全部呈現相似的環境。它們提供的其他功能是沒有用的,而且使用較為一般的 .NET 對應項完成了手邊的任務。
我應當注意到,我們的 Java 應用程式是使用 1.4 版的 Java 編寫的。直到 1.5 版,泛型集合才被引入 Java,泛型集合提供對集合成員強大的鍵入功能。因此,我們不必深究泛型集合的轉換。由於不僅需要轉換集合,而且要轉換其鍵入的條目,因此預計在 Java 1.5 版中轉換集合將使轉換過程更加複雜。
篩選器和 HTTP 處理常式
篩選器是 J2EE Web 應用程式中使用的常見範例,用於有選擇地截獲請求和響應,以執行處理前和處理後操作。篩選器的一些常見用途是日誌記錄、使用方式審核和安全性。
Java 篩選器實現了篩選器介面,後者用於定義特定生命週期事件。通過使用 URL 映射將 Filter 類映射到完整或部分 URL,篩選器被應用程式伺服器調用。建立匹配後,應用程式伺服器將實現所映射的篩選器的生命週期事件,從而將控制代碼傳遞給請求和響應。
ASP.NET 通過 IHttpHandler 介面的形式提供了類似的功能。篩選器和 HTTP 處理常式都有非常簡單的介面,但它們的功能非常功能強大。在我們的應用程式中,我們有兩種不同類型的篩選器:一種使用 GZip 壓縮來壓縮響應,另一種截獲請求以確定請求是否來自已知的使用者代理程式。
在 Java 應用程式中實現壓縮篩選器是為了改進效能。它擷取每個響應流,並檢查用戶端是否支援 GZip 壓縮,如果支援,則對響應進行壓縮。通常,這不是必需的,因為大多數現代 HTTP 伺服器都提供此功能,而不需要自訂的代碼。但是,我們的應用程式是作為 Servlet 應用程式套件組合含在其中的,它並不要求一定使用 HTTP 伺服器,因此,壓縮功能是增值模組。
第二個篩選器基於使用者代理程式標題的值拒絕請求,更值得關注。我們的很多客戶都希望實現某個層級的身分識別驗證,即能夠將 HTTP 使用者代理程式標題與允許的代理列表進行比較。如果傳入請求的使用者代理程式值不是允許的使用者代理程式,則應當拒絕該請求(通常返回 HTTP 未授權傳回值)。
可以通過很多方式完成這種類型的請求/響應篩選器。但大多數解決方案都需要大量注入代碼或屬性。幸運的是,J2EE 和 .NET 提供了非常簡單的機制,實現了在應用程式層級截獲、修改和調整請求和響應。另外,類似這樣的 HTTP 接聽程式不是代碼普及的,就是說,它們並不是每個類中的程式碼。相反,它們是通過應用程式伺服器託管和注入的單獨的類,因此修改功能相對於執行全域搜尋和替換將更為容易。
JLCA 3.0 還提供了協助器類,用於協助將篩選器從 Java 遷移到 ASP.NET 應用程式。圖 4 顯示了用 Java 實現的樣本篩選器(它用於對伺服器處理進行計時),還顯示了 JLCA 如何試圖將針對 ASP.NET 轉換此篩選器。雖然在理想情況下,在移植到 .NET 實現時,可能需要從頭重新編寫篩選器作為一個 HTTP 處理常式,但 JLCA 提供的支援類 SupportClass.ServetFilter 通過重新實現幫您完成了大部分工作。ServletFilter 可以類比 Java 提供的生命週期。雖然它沒有解決所有問題,但它可以使移植實現變得很容易。
Figure 4 使用 ServletFilter 進行 JLCA 轉換
Java
複製代碼
import javax.servlet.*;
import java.io.*;
public final class TimerFilter implements Filter
{
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException
{
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
long stopTime = System.currentTimeMillis();
System.out.println(“Time to execute request: “ +
(stopTime - startTime) + “ milliseconds”);
}
public void destroy() {}
public void init(FilterConfig fc) {}
}
C#
複製代碼
using System;
using System.Web;
// UPGRADE_TODO: Verify list of registered servlet filters.
public sealed class TimerFilter : SupportClass.ServletFilter
{
public override void DoFilter(HttpRequest request,
HttpResponse response, SupportClass.ServletFilterChain chain)
{
long startTime =
(System.DateTime.Now.Ticks - 621355968000000000) / 10000;
chain.doFilter(request, response);
long stopTime =
(System.DateTime.Now.Ticks - 621355968000000000) / 10000;
Console.Out.WriteLine(“Time to execute request: “ +
(stopTime - startTime) + “ milliseconds”);
}
public void destroy() {}
// UPGRADE_ISSUE: Interface ‘javax.servlet.FilterConfig’
// was not converted.
public void init() {}
}
實際上,我們的團隊採用了將 Java 篩選器功能手動轉換為 HTTP 處理常式的方法。為了引導您完成此過程,我應當比較兩個介面。J2EE 篩選器有三個方法:init、destroy 和 doFilter。init 和 destroy 方法是具有一定重要性的生命週期方法,但這超出了本文的討論範圍。而 doFilter 方法在篩選器中執行絕大部分工作。
在我們的方案中,doFilter 方法從傳入請求獲得使用者代理程式標題變數,並對照可配置的已知使用者代理程式列表對該變數進行檢查。在轉換中,最困難的方面是作為參數傳遞給可操作方法的對象存在差異。
在 Java Filter.doFilter 方法中傳遞了三個參數:請求對象、響應對象和篩選器鏈對象。相反,IHttpHandler 類的 ProcessRequest 方法只有一個參數:HttpContext 變數。HttpContext 變數引用 HttpRequest 和 HttpResponse 對象,而且,通過訪問 HttpContext 的成員,可以完成大多數相同功能。
Java FilterChain 對象很有趣。它表示 HTTP 請求中的篩選器鏈。篩選器可能將請求傳遞給該鏈中的下一個篩選器,從而實現以連續方式委派責任。FilterChain 不常使用,但如果要經常使用,則可以用 IHttpModule 實現來獲得相似的行為。
有了 FilterChain,將 Java 篩選器轉換為 .NET IHttpHandler 會變得非常容易。首先,需要建立 IHttpHandler 的實現。然後,需要將篩選器的 doFilter 方法中的邏輯重新構建到該實現的 ProcessRequest 方法中,以便利用 HttpContext 的成員,而不是所傳遞的請求和響應對象。最後,需要在實現中定義 IsReusable 方法;為簡單起見,它可以只返回 false(這是另一種情況,稍後您可以編寫更多代碼,以確定同一處理常式執行個體實際上是否可以被隨後的請求複用,以改進效能)。
源樹和命名規範
雖然 Java 和 C# 語言相似,但在詞典、源樹布局和命名規範方面有差異。在這裡,我想詳細說明這些差異中的一部分。
Java 包(等同於 C# 中的命名空間)遵守反向網域名稱約定,後跟應用程式或模組名稱,然後是功能。這些包通常嵌套得很深(通常深度為四或五級)。相反,C# 命名空間通常按功能上的說明性名稱進行分組,並且通常嵌套很淺(通常一到四級)。此外,Java 包通常採用小寫或 Camel 大小寫,而 C# 命名空間通常採用 Pascal 大小寫。同樣,Java 方法通常採用 Camel 大小寫,而 C# 方法和屬性通常採用 Pascal 大小寫。
C# 介面通常以大寫的 I 開頭,表示介面(這完全是約定,並非正確功能所必需)。按照舊的 Java 約定,介面名稱應當以“able”結尾,以表示能夠做某事。此約定已幾乎不再採用,現在,介面的名稱與類的名稱通常沒有任何差別。
C# 使用屬性來實現對私人成員的訪問和變異。屬性是中繼資料套件裝器,用於獲得和設定訪問器方法。但 Java 沒有屬性。通常,私人成員的訪問器和變異器是作為方法 getter 或 setter 來實現的。換句話說,名為“name”的私人欄位的訪問器將是 getName。
最後,Java 類必須位於與它聲明的包相匹配的目錄中(相對於源檔案(即 classpath)的根)。C# 沒有此限制。
何時重構約定
儘管不用解決這些差異被轉換的 C# 應用程式就可以編譯並運行,但遵守約定始終是最佳做法。選擇何時重構被轉換的代碼以遵守約定是個困難的決定。但是,有兩個因素使完成這件工作應當宜早不宜遲。
由於重構代碼以遵守約定對於應用程式正常工作並不是絕對重要的(更不用說有時它很乏味),因此,人們可能認為隨後在項目中不需要這一步驟。有很多事情會使它成為低優先順序的任務,因此有可能永遠不去做這項工作。但是,如果有一個Team Dev負責該項目,則通過重構代碼使它看起來很熟悉,將會協助團隊提高效率和生產力。不要低估這些任務的重要性。這種重構與在轉換過程中為了確保獲得成功結果所涉及的其他任務一樣重要。
目錄布局和命名空間
命名和目錄約定是主觀性過程,但仍然有一般性準則。對於我們的項目,假設有一個用於自訂 XML 分析的類,並且它位於 com.mycompany.myapplication.xml.util 命名空間(和目錄,轉換之後)中。C# 約定建議該類應當位於 Xml.Util 命名空間中。在重構之前,我們的分類樹外觀類似圖 5 所示。Visual Studio 中的檔案拖放功能允許完成檔案的物理移動,從而使分類樹像圖 6 一樣。
圖 5 Java 源樹
圖 6 C# 源樹
但是,C# 並未規定檔案的目錄位置要與其聲明的命名空間匹配。因此,系統不會更新類的命名空間,以匹配檔案系統位置。在 Visual Studio 中,要將多個類移動到不同的命名空間中沒有自動化的途徑,我發現實現這一點的最佳辦法是在整個解決方案中執行尋找和替換, 7 所示。當然,有一個前提假設,就是要將一個命名空間中的所有類都移動到同一目標中。
圖 7 尋找和替換命名空間聲明 (單擊該映像獲得較大視圖)
屬性
C# 中的屬性的構造與 Java 有很大不同。可以將屬性視為儲存類狀態(或在 UML 術語中,類的屬性)的常見欄位。但 Java 沒有屬性構造,而是用稱為 getter 和 setter 的方法來表示屬性。
假如有一個類,它有一個稱為“name”的執行個體變數。您不希望將此變數設定為公用變數,因為這樣會丟失修改此變數的所有控制權。在 Java 中,訪問此變數的標準方法是使用 getter 和 setter,如此命名是因為按照約定,為變數名添加首碼“get”或“set”可得到方法名稱。因此,如果 name 變數是字串,則 Java getter 和 setter 可能像下面這樣:
複製代碼
public String getName() {
return this.name;
}
protected void setName(String name) {
this.name = name;
}
C# 提供屬性構造以完成同樣的功能。儘管可以將它們用於應用程式邏輯,但其最初目的是為了對私人實現的細節提供受保護的訪問,如前面所述。因此,相同訪問的 C# 實現會像下面這樣:
複製代碼
public String Name
{
get {return this.name;}
protected set {this.name = value;}
}
當 C# 屬性完成同一目標時,我發現它們更為清晰 — 在 Java 中,不修改類的狀態的方法可能以 get 或 set 開頭,這會導致混淆。因此,我建議重構 Java getter 和 setter,使其成為 C# 屬性。
實際上,將 Java getter 和 setter 轉換為 C# 屬性並沒有簡單的方法。Regex搜尋和替換將會非常複雜,並且 JLCA 只是按原樣遷移 Java getter 和 setter 方法,因為無法分辨方法是在修改類的狀態,還是在執行某些其他功能。我們解決此問題的方法是使用更切實可行的解決方案。Visual Studio 為以屬性封裝私人成員提供了嚮導。這將產生期望的屬性,而不用刪除被轉換的 Java getter 和 setter。
我們的團隊在出於其他原因處理代碼時,在 Visual Studio 中還產生了 C# 屬性,並刪除了相應的 getter 和 setter 方法。此步驟之後,開發人員會使用 Visual Studio 2005 尋找引用功能來提供特定方法的所有調用網站的列表。這使他們能夠引用舊的 getter 和 setter 方法,因此,可以方便地更改對新屬性的引用。這不是最好的解決方案,但它的效果出奇得好。
應當強調,這是移植過程中非常重要的步驟,因為屬性是 C# 和 .NET 固有的核心特性,而我們的目標是要建立 C# 應用程式。(請記住,此應用程式最後會由另一個團隊提供支援,而他們希望得到一個 C# 應用程式。)
Pascal 大小寫方法名稱
前面提到過,按照約定 Java 方法名稱採用 Camel 大小寫。換句話說,這些名稱開頭是小寫字母,而每個後續單詞邊界採用大寫。C# 約定則要求方法和其他成員使用 Pascal 大小寫。Pascal 大小寫類似於 Camel 大小寫,只是名稱中的第一個字母也是大寫。
這對您意味著什嗎?您可能應當將所有方法重新命名,使其以大寫字母而不是小寫字母開頭。例如,Java 方法
複製代碼
public void getResponseCode()
應當重構為 C# 方法:
複製代碼
public String GetResponseCode()
與將 getter 和 setter 轉換成屬性一樣,要遍曆所有被轉換的代碼並更新成員以遵守 C# 命名規範,沒有簡單的方法。但是,我們認為這是非常重要的任務,因為被轉換的成員不同於 C# 成員,而且再次強調,我們的團隊和我們的客戶希望得到正確的 C# 應用程式。
理論上講,可以編寫代碼轉換器以執行此任務,並且我們考慮自己做這件事。但是,我們決定採取與處理屬性相同的途徑,即更新成員名稱部分。採取手動方式有幾個原因。
對於初學者而言,我們的開發人員已分析過所有代碼,以更新其他元素,例如 getter 和 setter。該過程可能是逐漸增加的,因為如果不更改成員的名稱,就不會影響編譯能力或功能。而且,如果由於某個原因而丟失了一個或兩個成員,應用程式也不會中斷。這個過程所佔用的時間沒有您想象的那麼長 — 主要問題是該任務很乏味。
Visual Studio 2005 有內建的重構支援。重構支援的一部分工作包括:安全重新命名成員、確定怎樣才能重新命名成員、以及更新對該成員的所有引用。遍曆要更新的所有成員佔用了一點時間,但這是有效解決方案。
總結
本文介紹了一個將 Java Web 應用程式轉換到 ASP.NET 的真實案例。就我們來說,多數繁重的工作是由 JLCA 完成的,但有幾項任務需要手動幹預。不過,JLCA 是功能強大的工具,它使我們的應用程式可以快速移植到實際 .NET 應用程式。
本文旨在示範將 Java 應用程式轉換成 .NET 的可行性,並指出通常需要解決而超出 JLCA 能力的某些問題。當然,每次具體的應用程式移植都會遇到特有的問題,本文無法解決所有這些問題。但是,本文提供的一些技巧和方法應當可以協助您解決可能遇到的任何問題。