18、跨语言编程:Java、Ruby与.NET的融合之道

跨语言编程:Java、Ruby与.NET的融合之道

1. Java与Ruby代码混合

1.1 问题提出

Java是现代企业环境和开源世界中应用广泛的编程语言,拥有无数可解决各种编程任务的库。而Ruby虽发展迅速,但在很多基础功能上仍不及Java成熟。若能将Java解决方案嵌入Ruby程序,将极大提升Ruby的功能。

1.2 所需工具

  • 安装Ruby Java Bridge(RJB):
$ gem install rjb
  • 下载并解压Apache Xerces XML解析器。
  • 下载并解压JDOM。

1.3 解决方案

以验证XML文档为例,在企业中使用Ruby时,缺少对XML文档的验证功能。Java有许多优秀的验证XML解析器,可支持如DTD、XML Schema等标准,而Ruby目前还缺乏成熟的验证解析器。

1.3.1 XML示例

以下是一个表示短消息的XML Schema:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="sms">
<xs:complexType>
<xs:sequence>
<xs:element name='sender' type='xs:token'/>
<xs:element name='receiver' type='xs:token'/>
<xs:element name='content' type='xs:string'/>
</xs:sequence>
<xs:attribute name="created-at" type="xs:dateTime"/>
</xs:complexType>
</xs:element>
</xs:schema>

对应的XML实例:

<sms created-at='2008-10-18T13:40:00'>
<sender>12345678</sender>
<receiver>987654321</receiver>
<content>Hello, world!</content>
</sms>
1.3.2 Java验证代码

使用Apache Xerces XML解析器验证XML文档:

import java.io.StringReader;
import java.io.File;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;

public class SchemaValidator {
    public static Document isValid(
        String xmlFileName,
        String xmlSchemaFileName)
    {
        try {
            final SAXBuilder builder =
                new SAXBuilder("org.apache.xerces.parsers.SAXParser", true);
            builder.setFeature(
                "http://apache.org/xml/features/validation/schema" ,
                true
            );
            builder.setFeature(
                "http://apache.org/xml/features/validation/" +
                "schema-full-checking",
                true
            );
            builder.setFeature(
                "http://xml.org/sax/features/validation",
                true
            );
            builder.setFeature(
                "http://xml.org/sax/features/namespaces",
                false
            );
            builder.setProperty(
                "http://apache.org/xml/properties/schema/" +
                "external-noNamespaceSchemaLocation",
                new File(xmlSchemaFileName).toURL().toString()
            );
            return builder.build(xmlFileName);
        }
        catch (Exception e) {
            System.err.println("Document is invalid: " + e.getMessage());
            return null;
        }
    }
    public static void main(String[] args) {
        String xmlFile = args[0];
        String schemaFile = args[1];
        if (isValid(xmlFile, schemaFile) != null) {
            System.out.println(xmlFile + " is valid.");
        } else {
            System.out.println(xmlFile + " is invalid.");
        }
    }
}
1.3.3 使用RJB在Ruby中集成Java代码
require 'rjb'
classpath = '.:lib/jdom.jar:lib/xercesImpl.jar'
Rjb::load(classpath)
SchemaValidator = Rjb::import('SchemaValidator')
xml_file, xml_schema_file = ARGV[0 .. 1]
doc = SchemaValidator.isValid(xml_file, xml_schema_file)
if doc.nil?
    puts "#{xml_file} is invalid."
else
    puts "#{xml_file} is valid."
    puts "Receiver: " + doc.getRootElement.getChild('receiver').getText
end
1.3.4 使用JRuby在Ruby中集成Java代码
require 'java'
include_class 'SchemaValidator'
xml_file, xml_schema_file = ARGV[0 .. 1]
doc = SchemaValidator.is_valid(xml_file, xml_schema_file)
if doc.nil?
    puts "#{xml_file} is invalid."
else
    puts "#{xml_file} is valid."
    puts "Sender: " + doc.get_root_element.get_child('sender').get_text
end

1.4 方法选择

  • RJB :以RubyGem形式存在,能快速集成到项目中。适用于在Ruby项目中急需Java功能的情况,但不支持多线程,无法集成如Swing库等Java代码。
  • JRuby :是用Java重写的Ruby解释器,由Sun Microsystems积极支持。支持Ruby方法名约定,能自动将Java迭代器转换为 each() 方法,是Rails项目的优秀平台。若应用的关键部分由Java代码组成,JRuby是不错的选择。

2. 使用RMI服务

2.1 问题提出

90年代后期,许多公司使用Java Remote Method Invocation(RMI)创建了大量Java服务。虽现在有更好的替代方案,但仍有一些关键组件只能通过RMI客户端使用,而RMI客户端通常需用Java编写。Ruby没有原生的RMI绑定,本部分将介绍如何在Ruby和Rails应用中使用RMI服务。

2.2 所需工具

安装Ruby Java Bridge(RJB):

$ gem install rjb

2.3 解决方案

以公司的中央用户账户管理器为例。

2.3.1 服务接口
package com.example;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface AccountManager extends Remote {
    User authenticate(
        String username,
        String password) throws RemoteException;
}
2.3.2 用户类
package com.example;
public class User implements java.io.Serializable {
    public User(String forename, String surname) {
        this.forename = forename;
        this.surname = surname;
    }
    public String getForename() {
        return this.forename;
    }
    public String getSurname() {
        return this.surname;
    }
    public String toString() {
        return this.forename + " " + this.surname;
    }
    private String forename;
    private String surname;
}
2.3.3 账户管理服务器
package com.example;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Server implements AccountManager {
    public User authenticate(String username, String password) {
        User user = null;
        if (username != null && username.equals("maik"))
            if (password != null && password.equals("t0p$ecret"))
                user = new User("Maik", "Schmidt");
        return user;
    }
    public static void main(String args[]) throws Exception {
        AccountManager manager =
            (AccountManager)UnicastRemoteObject.exportObject(
                new Server(), 0
            );
        Registry registry = LocateRegistry.getRegistry();
        registry.bind("AccountManager", manager);
        System.out.println("Started Account Manager.");
    }
}
2.3.4 编译和启动服务器
mschmidt> mkdir classes
mschmidt> javac -d classes src/com/example/*.java
mschmidt> cd classes
mschmidt> rmiregistry &
mschmidt> java com.example.Server
2.3.5 Java客户端
package com.example;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
    public Client() throws Exception {
        Registry registry = LocateRegistry.getRegistry();
        this.accountManager =
            (AccountManager)registry.lookup("AccountManager");
    }
    public User authenticate(
        String username,
        String password) throws Exception
    {
        return this.accountManager.authenticate(username, password);
    }
    private AccountManager accountManager;
}
2.3.6 使用RJB在Ruby中集成Java客户端
require 'rjb'
classpath = 'classes'
Rjb::load(classpath)
Client = Rjb::import('com.example.Client')
client = Client.new
username, password = ARGV[0 .. 1]
user = client.authenticate(username, password)
puts user ? user.toString : "Could not authenticate #{username}."

2.4 总结

RJB通过Java Native Interface(JNI)访问Java代码,能自然地将Java代码映射到Ruby,反之亦然。适用于重用现有Java代码的情况,但由于Ruby的线程模型,无法使用Java的原生线程代码。

以下是Java与Ruby、RMI服务集成的流程图:

graph LR
    A[问题提出] --> B[准备工具]
    B --> C[编写Java代码]
    C --> D[使用RJB或JRuby集成到Ruby]
    D --> E[测试运行]

3. 使用IronRuby混合Ruby和.NET

3.1 问题提出

在Microsoft .NET平台上开发了大量软件,希望在新应用中使用Ruby,并利用之前创建的库和.NET核心类。

3.2 所需工具

  • 在RubyForge上找到IronRuby的二进制发行版,解压并复制到Programs文件夹,将 ironruby/bin 添加到系统路径。
  • 下载并安装Microsoft Visual C# Express Edition。
  • 下载并安装Oracle数据库服务器或至少安装Windows的Oracle客户端。

3.3 解决方案

以实现一个小的报告生成器为例,从Oracle数据库读取统计信息,并在Windows窗口中输出。

3.3.1 数据库表定义
CREATE TABLE orders (
    id NUMBER(10) NOT NULL PRIMARY KEY,
    product VARCHAR2(100),
    state VARCHAR2(30),
    created_at DATE
);
3.3.2 C#代码实现
using System;
using System.Data.OracleClient;

namespace Report {
    public class ReportData {
        public int totalOrders;
        public int closedOrders;
    }
    public class StandardReport {
        public StandardReport(string user, string password) {
            connection = new OracleConnection();
            connection.ConnectionString = GetConnectionString(user, password);
            connection.Open();
        }
        public ReportData Create() {
            ReportData reportData = new ReportData();
            OracleCommand command = connection.CreateCommand();
            command.CommandText = "select count(*) from orders";
            OracleDataReader reader = command.ExecuteReader();
            reader.Read();
            reportData.totalOrders = reader.GetInt32(0);
            command.CommandText =
                "select count(*) from orders where state='closed'";
            reader = command.ExecuteReader();
            reader.Read();
            reportData.closedOrders = reader.GetInt32(0);
            command.Dispose();
            return reportData;
        }
        private string GetConnectionString(string user, string password) {
            return "User ID=" + user + ";Password=" + password +
                ";Unicode=True";
        }
        private OracleConnection connection;
    }
}
3.3.3 生成.NET程序集

使用Microsoft Visual Studio Edition for C#创建一个新的类库项目(命名为Report),添加上述代码,并添加对 System.Data.OracleClient 的引用。在项目属性中,选择“Sign the assembly”选项,然后构建项目,在 bin\Release 目录下会生成 Report.dll 文件。

3.3.4 在IronRuby中使用DLL
require 'Report.dll'
sr = Report::StandardReport.new('maik', 't0p$ecret')
report_data = sr.create
puts "Total orders: #{report_data.totalOrders}"
3.3.5 DLL导入说明

如果DLL在IronRuby的库路径中,可以直接使用 require 语句导入。如果是全局安装的库程序集,需要更详细地指定:

require 'Report, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=44371d941e7ae83f'

需要传递的属性包括:
- 程序集名称(无文件扩展名)。
- 程序集版本。
- 文化(最好为中性)。
- 用于签署程序集的私钥对应的公钥的64位哈希值。

3.3.6 解析XML配置文件
require 'mscorlib'
require 'System, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089'
require 'System.Xml, Version=2.0.0.0,
Culture=neutral,PublicKeyToken=b77a5c561934e089'

Xml = System::Xml
doc = Xml::XmlDocument.new
doc.load('config.xml')

def doc.get_first_element(name)
    get_elements_by_tag_name(name).item(0).inner_text
end

user = doc.get_first_element('user')
password = doc.get_first_element('password')
3.3.7 生成报告数据
require 'Report.dll'
StandardReport = Report::StandardReport
ReportData = Report::ReportData
class ReportData
    def to_s
        "total: #{totalOrders}/closed: #{closedOrders}"
    end
end
sr = StandardReport.new(user, password)
report_data = sr.create
puts report_data
3.3.8 在窗口中显示报告
require 'PresentationFramework, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35'
require 'PresentationCore, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35'

Window = System::Windows::Window
Application = System::Windows::Application
Button = System::Windows::Controls::Button
StackPanel = System::Windows::Controls::StackPanel
Label = System::Windows::Controls::Label
Thickness = System::Windows::Thickness

window = Window.new
window.title = 'Fancy .NET Report'
stack = StackPanel.new
stack.margin = Thickness.new 15
window.content = stack

["Here's our Report:",
"Total orders: #{report_data.totalOrders}",
"Closed orders: #{report_data.closedOrders}"].each do |message|
    label = Label.new
    label.font_size = 24
    label.content = message
    stack.children.add label
end

button = Button.new
button.content = 'Close'
button.font_size = 24
button.click { |sender, args| Application.exit }
stack.children.add button

app = Application.new
app.run window

3.4 总结

IronRuby是用C#编写的Ruby解释器,目标是CLR,能将Ruby代码与任何.NET代码混合。虽然目前处于早期开发阶段,存在功能不完整和一些小问题,但它有很大的发展潜力,有望成为另一个成熟的Ruby平台。

以下是IronRuby混合Ruby和.NET的流程图:

graph LR
    A[问题提出] --> B[准备工具]
    B --> C[编写C#代码]
    C --> D[生成.NET程序集]
    D --> E[在IronRuby中使用程序集]
    E --> F[解析配置文件]
    F --> G[生成报告数据]
    G --> H[显示报告]

综上所述,通过RJB、JRuby和IronRuby等工具,我们可以实现Java、Ruby和.NET之间的代码混合,充分发挥不同语言和平台的优势,为开发带来更多的可能性。在选择具体的集成方式时,需要根据项目的需求和特点进行综合考虑。

4. 方法对比与选择

4.1 不同集成方式的对比

集成方式 优点 缺点 适用场景
RJB 以RubyGem形式存在,可在一分钟内集成到项目中,能自然地将Java代码映射到Ruby 不支持多线程,无法集成如Swing库等使用Java原生线程的代码 在Ruby项目中急需Java功能,且对多线程无要求的情况
JRuby 由Sun Microsystems积极支持,支持Ruby方法名约定,能自动将Java迭代器转换为 each() 方法,是Rails项目的优秀平台 需要单独安装,是一个Java程序 应用的关键部分由Java代码组成的情况
IronRuby 能将Ruby代码与任何.NET代码混合,支持Ruby和.NET的命名约定 处于早期开发阶段,重要库缺失,未完全赶上Ruby 1.9 在Microsoft .NET平台上开发,希望在新应用中使用Ruby并利用之前创建的库和.NET核心类的情况

4.2 选择建议

  • 短期需求 :如果只是在Ruby项目中临时需要一些Java功能,且对多线程没有要求,RJB是一个不错的选择。它可以快速集成到项目中,满足紧急需求。
  • 长期Java依赖项目 :如果应用的核心部分将由大量Java代码构成,JRuby更适合。它提供了许多便利的特性,并且有良好的社区支持。
  • .NET平台项目 :在Microsoft .NET平台上开发,且希望使用Ruby编写新应用,同时利用现有的.NET库,IronRuby是首选。虽然它目前还不够成熟,但发展潜力巨大。

5. 常见问题与解决方法

5.1 RJB相关问题

5.1.1 多线程问题

由于Ruby的线程模型,RJB无法使用Java的原生线程代码。如果在项目中遇到需要多线程的情况,建议考虑使用JRuby。

5.1.2 类路径问题

在使用RJB时,需要正确设置类路径。如果出现类找不到的错误,检查 Rjb::load(classpath) 中的 classpath 是否正确指向了所需的Java库和类文件。

5.2 JRuby相关问题

5.2.1 安装问题

JRuby需要单独安装,确保按照官方文档的步骤进行安装,并将其添加到系统路径中。

5.2.2 性能问题

在某些情况下,JRuby的性能可能不如C版本的Ruby。可以通过优化代码和调整JVM参数来提高性能。

5.3 IronRuby相关问题

5.3.1 功能缺失问题

由于IronRuby处于早期开发阶段,可能会存在一些重要库缺失的情况。在使用之前,需要评估项目所需的功能是否已经得到支持。

5.3.2 版本兼容性问题

确保IronRuby与项目中使用的Ruby版本和.NET库版本兼容。如果出现版本不兼容的问题,尝试升级或降级相关组件。

6. 未来展望

6.1 RJB的发展

RJB作为一种快速集成Java代码到Ruby项目的工具,在短期内仍将有一定的应用场景。但随着JRuby和其他技术的发展,其市场份额可能会逐渐减少。不过,对于一些简单的Java功能集成需求,RJB仍然是一个方便的选择。

6.2 JRuby的发展

JRuby得到了Sun Microsystems的积极支持,并且在Rails项目中表现出色。未来,它有望成为Ruby最流行的平台之一。随着Java技术的不断发展,JRuby也将不断完善,提供更多的功能和更好的性能。

6.3 IronRuby的发展

IronRuby虽然目前处于早期开发阶段,但具有很大的发展潜力。随着Microsoft .NET平台的广泛应用,IronRuby将为Ruby开发者提供更多在.NET平台上开发的机会。预计在未来,它将不断完善,解决目前存在的问题,成为一个成熟的Ruby平台。

7. 总结

通过本文介绍的RJB、JRuby和IronRuby等工具,我们可以实现Java、Ruby和.NET之间的代码混合,充分发挥不同语言和平台的优势。在实际开发中,我们可以根据项目的需求和特点选择合适的集成方式。

同时,我们也需要注意不同集成方式可能带来的问题,并采取相应的解决方法。随着技术的不断发展,这些集成工具也将不断完善,为开发者提供更多的便利和可能性。

以下是选择集成方式的决策流程图:

graph LR
    A[项目需求] --> B{是否在.NET平台}
    B -- 是 --> C{是否需要利用现有.NET库}
    C -- 是 --> D[选择IronRuby]
    C -- 否 --> E{是否有大量Java代码依赖}
    E -- 是 --> F[选择JRuby]
    E -- 否 --> G[选择RJB或JRuby]
    B -- 否 --> H{是否有大量Java代码依赖}
    H -- 是 --> F[选择JRuby]
    H -- 否 --> G[选择RJB或JRuby]

希望本文能帮助开发者更好地理解和应用这些跨语言集成技术,在开发中取得更好的效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值