跨语言编程: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]
希望本文能帮助开发者更好地理解和应用这些跨语言集成技术,在开发中取得更好的效果。
超级会员免费看
9

被折叠的 条评论
为什么被折叠?



