使用 IIOP.NET 从 .NET 访问 EJB:一个示例






4.91/5 (22投票s)
2003 年 8 月 27 日
12分钟阅读

169772

1381
使用 IIOP.NET 从 .NET 访问 EJB:一个示例
引言
Enterprise Java Beans (EJB) [1] 是在 Java 平台上实现软件组件的成熟技术;有大量软件依赖于它们。许多 IT 关键参与者都提供托管 EJB 的应用服务器,特别是 IBM 的 WebSphere、BEA 的 WebLogic 以及开源的 JBoss。EJB 可用于创建分布式对象系统,并依赖 Java RMI/IIOP 进行消息交换;EJB 也可以作为 Web 服务公开。
尽管使用 Web 服务进行互操作目前很流行,但它们也有其局限性。Web 服务在集成异构的松散耦合系统方面非常出色,但它们不支持远程对象引用。实际上,它们是无状态的,更接近远程方法调用而不是分布式对象系统。此外,SOAP 和 XML 绝非压缩格式,而且往往非常冗长。
在上一篇文章 [2] 中,我展示了如何使用 IIOP.NET 从 Java 访问 .NET 组件;本文呈现的是反方向:如何使用 IIOP.NET 远程通道从 .NET 客户端访问 Java EJB 服务。为此目的,无需对 EJB 端进行任何修改。
关于 IIOP.NET
IIOP.NET [3] 是一个基于 IIOP 协议的 .NET 远程通道,与 Java 的RMI/IIOP [4] 使用的协议相同。IIOP 是CORBA 标准 [5] 的一部分。IIOP.NET 充当 ORB(CORBA 对象请求代理):它使 .NET 应用程序中定义的对象的其他远程 ORB 可访问,反之亦然。Java RMI/IIOP 实现了 CORBA 类型系统的一个子集(由于 Java 类型系统的一些限制),并大致为 J2EE 平台提供了与 IIOP.NET 相同的功能。
图 1:基于 IIOP 的分布式对象系统概述
使用 IIOP.NET 几乎与使用 .NET 内置的远程处理一样简单。IIOP.NET 是一个托管在 sourceforge 上的开源项目(http://iiop-net.sourceforge.net/)。它由 Dominic Ullmann 作为其在ETH-Z 的硕士论文的一部分而开发;后续工作现在由他目前的雇主ELCA Informatique SA [6] 赞助,在那里 IIOP.NET 用于使其 Java 和 .NET 版本的LEAF 框架 [7] 能够互操作。
毫不奇怪,IIOP.NET 并不是您为此目的可以使用的唯一软件。开源项目Remoting.Corba [8] 在目标上非常相似,但目前不支持 EJB;Borland 的Janeva [9] 也声称可以做到这一点,但不是免费的。
示例:基于 EJB 的聊天室
为了向您展示如何从 .NET 访问 EJB,我们将使用一个简单但非平凡的示例:一个聊天服务。该服务是一个 EJB,它允许用户注册和取消注册用于接收提交到聊天室的消息的监听器;EJB 管理客户端列表并将消息分派给所有已注册的客户端。以下 Java 接口和类用于与服务通信;它们将被转换为 IDL 文件,以便可以从其他 CORBA 客户端访问。
一个 `Message` 包含发送者的姓名和消息本身。该消息被映射到 CORBA 值类型(valuetype),即一个对象,它被序列化并发送到远程机器,而不是被远程访问。
public class Message implements Serializable {
private String m_originator;
private String m_msg;
public Message() { ... }
public Message(String msg, String originator) { ... }
public String getMsg() { ... }
public String getOriginator() { ... }
}
希望接收聊天消息的所有聊天室客户端都必须实现 `MessageListener` 接口。该接口扩展了 `java.rmi.Remote`,因为客户端是远程的,并且通信将使用 RMI/IIOP 进行。
public interface MessageListener extends java.rmi.Remote {
/* notify the listener, that a new message has arrived. */
public void notifyMessage(Message msg) throws java.rmi.RemoteException;
}
最后,`Chatroom` 接口定义了无状态 Bean 支持的功能,该功能允许发送消息和管理监听器列表。Home 接口仅包含创建新组件的调用,此处不显示。
public interface Chatroom extends EJBObject {
/* post message in chat-room */
public void broadCast(Message msg)
throws java.rmi.RemoteException;
/* register a client, interested in chatroom messages */
public void registerMe(MessageListener listener, String forUser)
throws java.rmi.RemoteException, AlreadyRegisteredException;
/* unregister the client with the name userName. */
public void unregisterMe(String userName)
throws java.rmi.RemoteException, NotRegisteredException;
}
聊天室根据以下 UML 序列图工作
图 2:聊天室序列图
在附带的示例中,EJB 是为WebSphere 5.0 [10] 实现和配置的;IIOP.NET 版本还包含一个适用于WebLogic 6.1 [11] 的版本以及适用于JBoss 3.2.1 [12] 的版本。虽然是为 WebSphere 编写的,但我们将尽量使本文中的代码保持通用,假设您对自己的应用服务器足够了解,可以为其配置 EJB。
我们现在开始实现 EJB 聊天室服务器和 .NET 客户端。这需要 6 个步骤。
步骤 1:安装 IIOP.NET
要构建和执行此示例,您至少需要 Java SDK 1.3、一个应用服务器(我们假设是 IBM WebSphere 5.0)、Microsoft .NET Framework SDK 1.0 或 1.1 [13]、Microsoft J# SDK 版本 1.0 或 1.1,以及 IIOP.NET(至少 1.3.1)。安装前四个超出了本文的范围。要安装 IIOP.NET,请从sourceforge 下载最新版本。
IIOP.NET 项目包含几个目录
- IIOPChannel 包含通道代码
- CLSToIDLGenerator 包含一个生成器,用于从 .NET 程序集创建 IDL 定义文件
- IDLToCLSCompiler 包含一个生成器,用于从 IDL 定义创建 CLS 文件(.NET 多模块程序集)
- Examples 包含示例和教程。本文介绍的示例是EJBChatRoom\WebSphere_5;WebLogic 版本位于EJBChatRoom\WLS6.1;JBoss 版本位于Examples\EJBChatRoom\JBoss3.2.1\
在构建 IIOP.NET 之前,将 Java SDK 目录中的文件 *lib\ir.idl* 和 *lib\orb.idl* 复制到 IIOP.NET 的 `IDL` 目录,并将环境变量 WAS_HOME 设置为 WebSphere 应用服务器目录。通过执行 `nmake` 来编译所有内容。
步骤 2:实现 EJB
给定前面的定义,EJB 的实现非常直接。我们创建 `Message` 类,以及包含上述定义的 `MessageListener` 和 `Chatroom` 接口。
Bean 实现提供了三个文件:Bean Home 接口位于 `ChatroomHome` 接口中,Bean 本身位于 `ChatroomBean` 中,客户端管理实现位于 `ChatroomServer` 中。管理在一个单独的单例类中实现,因为这必须与每个 Bean 相同(而每个客户端都获得一个不同的 Bean 实例),并且对结构的访问必须是同步的;此模式不遵循推荐的 EJB 可伸缩性实践(避免同步),但“正确”的实现对于如此小的示例来说太复杂了。Bean 将调用转发给客户端管理类
public void registerMe(MessageListener listener,
String forUser) throws AlreadyRegisteredException {
ChatroomServer server = ChatroomServer.getSingleton();
server.addClient(listener, forUser);
}
public void unregisterMe(String userName) throws NotRegisteredException {
ChatroomServer server = ChatroomServer.getSingleton();
server.removeClient(userName);
}
public void broadCast(Message msg) {
ChatroomServer server = ChatroomServer.getSingleton();
MessageListener[] listeners = server.getClients();
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].notifyMessage(msg);
} catch (Exception e) {
System.err.println("error sending msg: " + e);
System.err.println("--> removing listener");
server.removeListener(listeners[i]);
}
}
}
消息的广播在 Java Bean 本身中完成,以最大程度地减少在聊天室中花费的时间(在此期间聊天室被锁定)。发送消息可能需要很长时间,尤其是在客户端不可用时。这里理想的解决方案将是 CORBA 的单向异步调用,但这在 EJB 中是不可能的;建议的实现方法需要使用Java Message Service (JMS) [14],这超出了本文的范围;因此,为简单起见,我们将让每个 Bean 发送消息。
`ChatroomClient` 的实现由一个处理客户端列表的单例实例组成
public class ChatroomServer {
private static ChatroomServer s_chatroomServer = new ChatroomServer();
private Hashtable m_clients = new Hashtable();
private ChatroomServer() { super(); }
public static ChatroomServer getSingleton() { return s_chatroomServer; }
public synchronized void addClient(MessageListener ml, String forUser)
throws AlreadyRegisteredException {
if (!m_clients.containsKey(forUser)) {
m_clients.put(forUser, ml);
} else {
throw new AlreadyRegisteredException(
"a message listener is already registered for user: "
+ forUser);
}
}
public synchronized void removeClient(String forUser)
throws NotRegisteredException {
if (m_clients.containsKey(forUser)) {
m_clients.remove(forUser);
} else {
throw new NotRegisteredException(
"no message listener registered for the user: " + forUser);
}
}
public synchronized void removeListener(MessageListener listener) {
m_clients.values().remove(listener);
}
public synchronized MessageListener[] getClients() {
MessageListener[] result = new MessageListener[m_clients.size()];
result = (MessageListener[])(m_clients.values().toArray(result));
return result;
}
}
注意 `getClients()` 方法的实现,它返回监听器列表的副本以避免同步问题。
步骤 3:生成 IDL 文件
服务实现后,下一步是生成 IDL 文件,这些文件使用 CORBA 模型描述服务接口。
每个应用服务器提供自己的 IDL 生成方式,因为每个应用服务器使用不同版本的 EJB 规范,而这些规范又有不同的接口。
有关生成 IDL 文件的确切过程,请参阅您的应用服务器文档。
步骤 4:从 IDL 文件生成 CLS 模块
从 IDL 文件,`IDLToCLSCompiler` 工具生成一个 CLS 多模块程序集,其中包含访问基于 EJB 的服务所需的类和接口。
为什么生成器创建 netmodule 而不是 C# 存根?嗯,有几个原因,但最重要的是简单性和可移植性。首先,使用 .NET 的反射发射接口可以非常容易地生成 netmodule;生成源代码将需要一些漂亮的打印算法。其次,netmodule 包含 CLS 形式的定义,这被所有 .NET 兼容语言理解,因此无论您的代码是 C# 还是 Visual Basic(甚至是 Oberon),都没关系。
指定输出目录(-o)和要使用的 IDL 文件来调用生成器。
IDLToCLSCompiler.exe -o ..\bin chatroom
ch\elca\iiop\demo\ejbChatroom\Chatroom.idl
ch\elca\iiop\demo\ejbChatroom\ChatroomHome.idl
生成器会提醒您必须为某些类提供实现:这些是实现 CORBA 值类型的类;在 .NET 中,这些类使用 `Serializable` 属性定义(不要将 CORBA 的值类型与 .NET 的值类型类混淆)。这些类的内容被克隆到目标系统,因此它们提供的方法也必须在远程系统上可用。由于 IDL 仅包含接口定义,因此您必须提供此代码(别担心!这项工作通常仅限于类的构造函数)。
在我们的示例中,需要实现的类是我们的服务定义的 `NotRegisteredException`、`AlreadyRegisteredException` 和 `Message`;以及(由 Java、RMI 或 EJB 定义的)`Throwable`、`_Exception`、`CreateException`、`RemoveException`(由 Java、RMI 或 EJB 定义)。一些 EJB 可能需要定义其他类。
我们在 `ExceptionImpl.cs` 和 `MessageImpl.cs` 中实现了这些类。
using System;
namespace ch.elca.iiop.demo.ejbChatroom {
///<SUMMARY>
/// Implementation of the CORBA value type Message
/// </SUMMARY>
[Serializable] public class MessageImpl : Message {
public MessageImpl() { }
public MessageImpl(string originator, string msg) {
m_originator = originator;
m_msg = msg;
}
public override string fromUser {
get { return m_originator; }
}
public override string msg {
get { return m_msg; }
}
}
}
请记住,IIOP.NET 将查找类 `ClassImpl` 作为 CORBA 值类型 `Class` 的实现。因此,需要提供的类名是 `NotRegisteredExceptionImpl`、`AlreadyRegisteredExceptionImpl` 等等。
步骤 5:实现 C# 客户端
客户端提供一个用户界面来收集用户的消息、调用服务并显示由服务发送的信息。示例中使用了简单的 GUI;与用户界面无关,有几件重要的事情要做。首先,注册 IIOP.NET 通道,连接到 EJB,并获取服务实例。
// register IIOP.NET channel
IiopChannel channel = new IiopChannel(callbackPort);
ChannelServices.RegisterChannel(channel);
// get the naming service
RmiIiopInit init = new RmiIiopInit(ejbNameServiceHost,
ejbNameServicePort);
NamingContext nameService = (NamingContext)init.GetService(
"NameServiceServerRoot");
NameComponent[] name = new NameComponent[] {
new NameComponent("demo", ""),
new NameComponent("chatroomHome", "") };
// get the chatroom home interface
ChatroomHome home = (ChatroomHome) nameService.resolve(name);
Chatroom chatroom = home.create();
`ejbNameServiceHost` 和 `ejbNameServicePort`,以及组件名称在很大程度上取决于应用服务器以及服务在那里如何配置和注册。
为了能够接收消息,客户端必须注册一个监听器,即一个实现 `MessageListener` 接口的可远程对象。
m_listener = new MessageListenerImpl(m_usernameTextbox.Text, this);
m_chatroom.registerMe(m_listener, m_listener.userName);
现在聊天室已准备就绪。向房间发送消息很简单
MessageImpl msg = new MessageImpl(m_listener.userName,
m_messageTextbox.Text);
try {
m_chatroom.broadCast(msg);
} catch (Exception ex) {
Console.WriteLine("exception encountered, while broadcasting: " + ex);
MessageBox.Show("an exception occured, while broadcasting!");
}
此代码执行同步方法调用,即它等待服务器完成广播并返回。但是,这不是强制性的,因为调用不返回任何结果,并且客户端无需与服务器同步。因此,也可以进行异步调用
delegate void BroadCastDelegate(Message msg);
MessageImpl msg = new MessageImpl(m_listener.userName,
m_messageTextbox.Text);
try {
BroadCastDelegate bcd = new BroadCastDelegate(m_chatroom.broadCast);
// async call to broadcast
bcd.BeginInvoke(msg, null, null);
// do not wait for response
} catch (Exception ex) {
Console.WriteLine("exception encountered, while broadcasting: " + ex);
MessageBox.Show("an exception occured, while broadcasting!");
}
每当有新消息可用时,EJB 服务将调用监听器的 `notifyMessage()` 方法。此方法实现客户端处理传入调用。
步骤 6:运行示例
运行示例是最后一步。在服务器端,托管在 WebSphere 上的 EJB 服务的 `Makefile` 会自动将其注册到应用服务器,因此您只需启动服务器(如果它尚未运行)。
在客户端,启动应用程序;然后设置您的名字,连接到服务器,您就可以发送和接收消息了。
图 3:.NET 客户端 UI
问题和陷阱
提出的示例在概念上很简单。尽管如此,一些陷阱可能会毁掉您的一天并阻止代码运行。大多数问题都与各种应用服务器及其繁琐的配置有关(许多错误仅在运行时出现,导致单调乏味且耗时的尝试-更正循环)。
在 IIOP.NET 中,CORBA 值类型阻止完全自动生成代理。当调用远程对象上的方法时,调用由远程基础结构转发到拥有对象实例的机器。值类型被克隆,所有调用都在本地处理,因此它们的实现也必须在本地可用。由于 IDL 仅包含接口定义,因此实现必须手动提供。在 .NET 到 .NET 远程处理时不需要这样做:包含定义的 DLL 也包含类的实现。
同步是此示例的另一个特定问题。使用图形用户界面时,每个操作(即单击按钮)都会在关联代码执行期间锁定整个框架。当向服务器发送消息时,服务器显然会将同一消息转发给客户端,而传入消息在不同的线程中到达,并且框架锁定阻止其传递(导致经典的死锁)。有多种解决方案:对自己的消息进行特殊处理是一种方法,异步调用是另一种方法:可以从客户端发送或接收消息时进行异步调用,并且可以在服务器端调度消息时进行异步调用。在此示例中,我们在接收消息时实现了异步调用;此外,客户端允许在同步和异步消息发送之间进行选择。
结论
本文介绍了一个示例,其中 IIOP.NET 通道用于从基于 .NET 的客户端提供对 Java EJB 的访问。介绍了并详细说明了所涉及的各个步骤。用于访问对象的 IIOP 协议非常成熟,与更花哨但功能较弱的 Web 服务相比,效率很高。示例本身相当详尽:它包含了 .NET 和 J2EE 平台,以及 CORBA 技术,以使它们能够互操作。
开发此类应用程序会遇到一些问题,这些问题通常是由托管 EJB 服务的各种应用服务器的复杂且非平凡的配置和使用引起的。特别是,服务命名和位置在不同服务器之间可能差异很大。
与各种应用服务器的交互真是一次大开眼界:我们发现了对同一 IIOP 规范的许多不同解释,比我们 wildest dreams 想象的要多得多!非常感谢快速发展的 IIOP.NET 社区,他们在短短几周内就帮助我们在许多不同的服务器上测试了该通道。
尽管如此,IIOP.NET 已在最常见的应用服务器上进行了测试;代码和配置示例可用于 WebSphere 5、WebLogic 6.1 和 JBoss 3.2.1。
链接和参考文献
[1] | Java EJB http://java.sun.com/products/ejb/ |
[2] | Patrik Reali; Building a distributed object system with .NET and J2EE using IIOP.NET; July 2003; The Code Project https://codeproject.org.cn/csharp/dist_object_system.asp |
[3] | IIOP.NET http://iiop-net.sourceforge.net/ |
[4] | Java RMI/IIOP http://java.sun.com/products/rmi-iiop/ |
[5] | CORBA http://www.corba.org/ |
[6] | ELCA Informatique SA http://www.elca.ch/ |
[7] | ELCA LEAF and LEAF.NET http://www.elca.ch/Home/Solutions/Technology_Frameworks/LEAF/index.php |
[8] | Remoting.Corba http://remoting-corba.sourceforge.net/ |
[9] | Borland Janeva http://www.borland.com/janeva/ |
[10] | IBM WebSphere Application Server http://www.ibm.com/software/webservers/appserv/ |
[11] | BEA WebLogic http://www.bea.com/products/weblogic/server/ |
[12] | JBoss https://jboss.com.cn/ |
[13] | Microsoft .NET Framework http://msdn.microsoft.com/netframework/ |
[14] | Java JMS http://java.sun.com/products/jms/ |