zoukankan      html  css  js  c++  java
  • Java Web 应用程序转换为 ASP.NET

    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 工厂方法创建 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 中使用的简单重载,我们就能与正则表达式结合,用搜索和替换来完成批量转换过程。

    日志记录是非常耗费资源的过程,需要谨慎使用,以确保它不会影响应用程序的性能。最后,在应用程序中搜索和替换日志调用只需要几小时。

     

    集合

    .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# 应用程序就可以编译并运行,但遵守约定始终是最佳做法。选择何时重构被转换的代码以遵守约定是个困难的决定。但是,有两个因素使完成这件工作应当宜早不宜迟。

    由于重构代码以遵守约定对于应用程序正常工作并不是绝对重要的(更不用说有时它很乏味),因此,人们可能认为随后在项目中不需要这一步骤。有很多事情会使它成为低优先级的任务,因此有可能永远不去做这项工作。但是,如果有一个开发团队负责该项目,则通过重构代码使它看起来很熟悉,将会帮助团队提高效率和生产力。不要低估这些任务的重要性。这种重构与在转换过程中为了确保获得成功结果所涉及的其他任务一样重要。

     

    目录布局和命名空间

    命名和目录约定是主观性过程,但仍然有一般性准则。对于我们的项目,假设有一个用于自定义 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# 属性并没有简单的方法。正则表达式搜索和替换将会非常复杂,并且 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 能力的某些问题。当然,每次具体的应用程序移植都会遇到特有的问题,本文无法解决所有这些问题。但是,本文提供的一些技巧和方法应当可以帮助您解决可能遇到的任何问题。

     

  • 相关阅读:
    JavaScript设计模式_11_中介者模式
    JavaScript设计模式_10_职责链模式
    JavaScript设计模式_09_享元模式
    JavaScript设计模式_08_模板方法模式
    JavaScript设计模式_07_组合模式
    JavaScript设计模式_06_命令模式
    JavaScript设计模式_05_发布订阅模式
    PyTorch Notes | PyTorch 编程实践笔记
    Linux Notes | Linux常用命令行笔记
    CAS邮箱的Express配置
  • 原文地址:https://www.cnblogs.com/tewuapple/p/1759432.html
Copyright © 2011-2022 走看看