积木首页 | 500多种网页特效 | 函数手册 | 广播电台 | 高清晰图片素材 | 服务器合租 | 万年历 | 网友最新浏览记录
程序开发 网页设计 搜索引擎 特效代码 操作系统 防范病毒 黑客技术 图形图象 电脑硬件 网络技术 服 务 器 数 据 库 网文精粹
您的位置:积木首页 >> 程序开发频道 >> J2EE >> 正文:
标题:实现独立于供应商的 JMS 解决方案
时间:2002-9-19 来源:不详 浏览数:
                        实现独立于供应商的 JMS 解决方案
Java 消息服务(JMS)规范制定了基于 Java 点到点(P2P)和发布/订阅(P/S)消息传递的标准。Sun 目前列出了 12 个经过许可的 JMS 实现者和 16 个未经许可的实现者。就体系结构而言,JMS 与 Java 数据库互连(Java Database Connectivity (JDBC))API 相似,因为它们都只定义了少数几个类而定义了很大集合的接口。这些接口有待实现,而符合这些接口的实现的行为是一样的。

对大多数数据库而言,这种行为的相似性因 JDBC 接口实现而丧失。与 SQL 符合性级别的差异和专有过程型 SQL 扩展(如 Oracle 的 PL/SQL 和 Sybase 的 Transact-SQL)的使用会使得为访问和使用不同的数据库服务而编写的代码有很大的差异。

而 JMS 就不是这样。只要最少的工作并遵循我在本文中所推荐的过程,您就可以使您的 JMS 客户机代码顺畅地运行,而丝毫觉察不到所使用的供应商实现的差异。虽然我假设您对 JMS 消息处理有基本的了解,但我们还是将对基本概念和术语进行简短回顾,来开始这次讨论。

JMS 体系结构
发送和接收消息的基础是连接,它负责分配 JVM 之外的资源。JMS 供应商通常至少为 P2P 事务实现一个 QueueConnection,至少为 P/S 事务实现一个 TopicConnection。这些连接提供一个 Session,它是管理消息发送和接收的结构。

P2P 事务管理的基本结构是 QueueSender 和 QueueReceiver。用于 P/S 事务管理的基本结构是 TopicSubscriber 和 TopicPublisher。Topic 和 Queue 对象封装了导向每条消息的目标和源的特定信息。这一层次结构如图 1 所示:

图 1. JMS 类层次结构


其它特定于应用程序服务器的结构(如请求/响应支持类)和特性可以在 JMS 标准中找到(请参阅参考资料)。

连接设置的不同类型
因为连接是和 JMS 服务器交互的入口点,因此连接接口的每个实现必须知道如何与它自己的 JMS 服务器的一个实例连接。因为底层连接协议的详细信息往往因供应商不同而有所不同,所以设置活动连接所需的信息也会因供应商而异。

大多数供应商允许动态地设置连接。也就是说,他们将连接类的构造器定义成公共的(public),允许程序员定义所需连接信息。大多数供应商提供在调用后可以返回一个连接的工厂类。

就连接工厂而言,工厂类可以返回一个已预先装入专有连接信息的连接。供应商定义的工厂类将提供方法以允许程序员设置连接参数。这些连接参数指示工厂返回的连接的性质。

供应商 API 的差别所在
为了使所有这些更加具体,让我们看看 QueueConnection 和 QueueConnectionFactory 的几个实现的构造器、连接工厂和设置方法。(请注意,有些情况下会有许多重载的构造器;对每种情况我只举例说明一个构造器。)

IIT SwiftMQ 2.1.3 QueueConnectionFactory 构造器参数

java.lang.String socketFactory:套接字工厂的类名称
java.lang.String hostname:JMS 服务器的主机名
int port:JMS 服务器端口
long keepalive:保持活动的间隔
下面的代码显示如何创建 SwiftMQ QueueConnectionFactory 对象:

 
QueueConnectionFactory qcf = (QueueConnectionFactory) new 
com.swiftmq.jms.ConnectionFactoryImpl
  ("com.swiftmq.net.PlainSocketFactory", "myhost",4001,60000);

 

Progress SonicMQ 3.5 QueueConnection 构造器参数

java.lang.String brokerURL:URL(格式为 [protocol://]hostname[:port])
java.lang.String connectID:标识连接的标识字符串
java.lang.String username:缺省用户名
java.lang.String password:缺省密码
下面是创建 Progress SonicMQ QueueConnectionFactory 对象的样本代码:


progress.message.jclient.QueueConnection queueConnection = new
progress.message.jclient.QueueConnection("tcp://myhost:2506", 
    "ServiceRequest", "username", "password");

 

MQSeries (MA88)

我们要查看的最后一个示例是 IBM MQSeries 实现。MQSeries 不使用连接构造器。取而代之的是,要动态创建连接,必须构造一个连接工厂,然后该工厂再提供产生连接的方法。创建无参数构造器的代码如下:


MQQueueConnectionFactory = new
MQQueueConnectionFactory();

 

连接工厂的构造器是无参数的,因此工厂有变异方法可以被调用,以控制工厂将提供的连接的特性。

setTransportType(int x):将传送类型设置为下列选项之一:
JMSC.MQJMS_TP_BINDINGS_MQ:当 MQSeries 服务器和客户机在同一主机上时使用
JMSC.MQJMS_TP_CLIENT_MQ_TCPIP:当 MQSeries 服务器和客户机不在同一主机上时使用
setQueueManager(String x):设置队列管理器名称
setHostName(String hostname):仅用于客户机,设置主机名称
setPort(int port):设置客户机连接端口
setChannel(String x):仅用于客户机,设置要使用的通道

下面是用于创建 MQseries QueueConnectionFactory 和获取特定队列管理器连接的样本代码:


com.ibm.mq.jms.MQQueueConnectionFactory factory = new 
com.ibm.mq.jms.MQQueueConnectionFactory();
factory.setQueueManager("QMGR");
com.ibm.mq.jms.MQQueueConnection connection = 
  factory.createQueueConnection();

 

JNDI 在创建独立于供应商的代码时所扮演的角色
正如我们的简短回顾所示,每个供应商都使用自己独特的连接参数集。那么,如何在代码中透明地支持所有这些参数集呢?标准的解决方案是使用命名服务以持久存储预先配置的 ConnectionFactory。在运行时,代码可以检索 ConnectionFactory,而从中返回的连接将能够透明地连接到 JMS 服务器。您无需维护和重建代码,只需简单地在命名服务中维护正确配置的连接工厂即可。

Java 命名和目录接口(JNDI)是与命名服务进行相互操作的最常见方式。JNDI 与 JMS 的相似之处在于它只是定义了一组有待实现的接口。可以用一个标准的 API 访问实现 JNDI 的所有命名服务。

JNDI 是编写与供应商无关的代码的关键,因为它提供了访问命名服务的独立于供应商的方式。这样,我们就只需关注编写从命名服务检索正确对象的代码,无需担心前面一节中所概括的任何专有实现。

连接到 JMS 服务器
通过创建连接工厂,预先配置它然后将它绑定到命名服务,您可以在消息传递服务中隐藏特定于供应商的连接参数。就代码而言,您正在使用通用的 javax.jms.Connection 对象。供应商实现隐藏在该接口背后。

JMS 规范将那些由管理员创建并且包含由 JMS 客户机使用的配置信息的对象称为 JMS 受管的对象。受管的对象并不依赖于 JNDI,但暗示它们可以绑定到 JNDI 名称空间并可以在其中查询它们。

清单 1 和 2 显示连接到 JMS 服务器的两种不同方法(本例中为 SwiftMQ):一个使用依赖于供应商的代码而另一个使用独立于供应商的代码。

清单 1. 依赖于供应商的连接方法 
1.QueueConnectionFactory queueConnectionFactory = 
(QueueConnectionFactory) new 
com.swiftmq.jms.ConnectionFactoryImpl
  ("com.swiftmq.net.PlainSocketFactory", "localhost",4001,60000);
2.QueueConnection queueConnection = 
queueConnectionFactory.createQueueConnection();

 

清单 2. 与供应商无关的连接方法 
1.Properties p = new Properties();
2.p.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.swiftmq.jndi.InitialContextFactoryImpl");
3.p.put(Context.PROVIDER_URL,"smqp://localhost:4001");
4.ctx = new InitialContext(p);
5.qcf = (QueueConnectionFactory)ctx.lookup("MyQCF");
6.oQueueConnection queueConnection = 
queueConnectionFactory.createQueueConnection();

 


剖析代码
首先您会注意到独立于供应商的代码稍稍多几行。这是因为我们必须连接到命名服务。然而请记住,在整个程序中您可能只要连接到命名服务一次即可,因此额外多几行是值得的。(只要确保在每次需要建立连接时,重用命名服务而不是实例化远程上下文。)

与命名服务的交互和对命名服务的准备是编写独立于供应商的消息传递代码的关键。在依赖于供应商的代码示例中,我们使用了 SwiftMQ 实现的 QueueConnectionFactory 构造器,来创建将为我们提供连接的工厂。对于这一实现,我们不仅必须包含供应商专有类,还必须向 QueueConnectionFactory 构造器传递特定于供应商的参数,如清单 1 第一行所示。

在独立于供应商的示例中没有特定于供应商的代码,但我们必须知道初始的上下文工厂和命名服务的供应商 URL,以及 QueueConnectionFactory 的绑定名称。对于绑定名称,适当的命名服务维护将允许您将对象从任一供应商绑定到您的 JNDI 树,因而尽管供应商可能改变,但绑定名称却不必改变。至于 JNDI 上下文,通常的做法是将参数字符串(清单 2 中第二和第三行)存储在特性文件中然后在需要时读取。用这种方法,改变 JMS 供应商只需改变特性文件即可。

还有一件有趣的事情要注意:这种技术给予您关于命名服务的灵活性和可移植性。许多 JMS 供应商(如 Fiorano 和 SwiftMQ)提供他们自己的 JNDI 服务,但是您可能想将命名服务与 JMS 服务分开。(例如,您可能想把连接工厂存储在集中式 LDAP 服务器中。)

以下是可以产生不同 JNDI 连接的特性文件项示例。

SwiftMQ JNDI 服务

java.naming.provider.url=smqp://myhost:4001
java.naming.factory.initial=com.swiftmq.jndi.InitialContextFactoryImpl

IBM WebSphere JNDI 服务

java.naming.provider.url=iiop://myhost:9001
java.naming.factory.initial= com.ibm.websphere.naming.WsnInitialContextFactory

iPlanet 目录服务器(LDAP)

java.naming.provider.url=ldap://myhost:389
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory

BEA WebLogic JNDI 服务

java.naming.provider.url=t3://myhost:7001
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory

文件系统 JNDI 服务

java.naming.provider.url=file:/tmp/stuff
java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory

请注意,尽管您的代码可能没有直接引用供应商类,但供应商类也按名称被动态装入 JVM,因此在运行时它们一定要在您程序的类路径(classpath)中。这对 JNDI 和 JMS 类都适用。

设置特性文件
那么,至此我们已经知道如何连接到 JNDI 服务以及如何从不同的 JNDI 和 JMS 实现获取连接而不必重编译我们的代码。让我们把到目前为止所知道的东西集中在一起,来看看如何设置特性文件以及它在启用 JNDI 连接中所起的作用。

进行 JNDI 连接的基类是 javax.naming.InitialContext。尽管有一些特定于目录操作的 InitialContext 子类(如 InitialDirContext),但通用类将完成此任务。构造 InitialContext 后,它可以从环境(系统特性或 applet 参数)派生 JNDI 参数值或查找特定 jndi.properties 文件。

下面是 J2SE 1.3.1 javadoc 中对该操作的解释:


JNDI 通过合并来自下列两个源的值来确定每个特性的值,其先后顺序如下:
构造器环境参数、(对于适当的特性)applet 参数和系统特性中首次出现的特性


应用程序资源文件(jndi.properties)

附加特性
迄今为止我们只考虑了两个参数:供应商 URL 和 InitialContext 工厂名。实际上可能要提供更多的属性。除了我们已经考虑的两个之外,最常用的是用户名和密码,它们验证您对可能受保护的 JNDI 存储的访问权限。这些参数是:

java.naming.security.principal(用户名)
java.naming.security.credentials(密码)
装入文件
建议您将应用程序所有的运行时配置参数都放在一个应用程序特性文件中并将 JNDI 参数包含在该文件中。将所有参数放在一个地方可以消除不确定性。然后您有几个选项来装入应用程序特性文件。我列出了两个作为示例。可以将该文件作为资源束装入,或者您可以将属性文件的名称和位置作为命令行参数传入。这两种方法有不同的好处。

将文件位置作为命令行参数传入是配置代码最简单的方法。简单的修改一下应用程序的启动就可以改变参数。

将文件作为资源束装入有两个好处:

可以根据 JVM 的语言环境装入不同的资源束。例如,application_en_US.properties 文件可能指向位于纽约的 JNDI 服务,而 application_fr.properties 文件指向位于巴黎的 JNDI 服务。


从资源束装入特性是一种与体系结构和平台无关的装入特性文件的方式。因为资源束从类路径装入,所以代码并不依赖于能够读取 JVM 命令行参数的能力。此外,有些组件(如 EJB 组件)不能直接使用文件 I/O,因此资源束或许提供了一种更方便的装入特性文件内容的方法。

为避免与环境设置的混淆,我始终用从特性文件读出的 JNDI 值来设置特性实例。

特性文件初始化与 JNDI 查询
这一节的代码清单演示了特性初始化的两种类型(命令行参数和资源束)以及通用 JNDI 查询。首先,我们看看名为 PropertiesManagement.properties 的样本配置文件,如清单 3 所示:

清单 3. PropertiesManagement.properties 
java.naming.provider.url=smqp://localhost:4001
java.naming.factory.initial=com.swiftmq.jndi.InitialContextFactoryImpl
java.naming.security.principal=admin
java.naming.security.credentials=secret
com.nickman.neutraljms.QueueConnectionFactory=myQueueConnectionFactory
com.nickman.neutraljms.TopicConnectionFactory=myQueueConnectionFactory
com.nickman.neutraljms.Queue=testqueue@router1
com.nickman.neutraljms.Topic=testtopic

 

文件前四项是 J

[1] [2] [3] 下一页


(责任编辑:笑虎)
关于本站 | 广告服务 | 联系我们 | 版权申明 | 强强联盟 | 投稿热线 | 网站地图 | 申请链接
Copyright ©2005-2006 Gimoo.net All rights reserved. 积木网 版权所有
E-mail:gimoohr@gmail.com 京ICP备05050695号