打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Geronimo 中 JMS、MDB 和 ActiveMQ 的使用技巧



级别: 初级

Sing Li (westmakaha@yahoo.com), 作者

2005 年 8 月 08 日

Apache Geronimo 是具有开放架构且功能强大的开源 Java™ 2 平台企业版 (J2EE) 1.4 服务器容器,可以承载多种现有的服务器和服务。ActiveMQ 是一个经验证的最佳开源 Java 消息服务 (JMS) 引擎,该引擎还附带一个精巧多用的功能和连接选项库。二者结合使用时,会发生奇迹般的效果!Sing Li 将为您介绍这种共存关系,并提供示例代码,帮助您开始编写 JMS 应用程序并立即使用 Geronimo 创建 Message-Driven Beans (MDB)。

JMS API 是 J2EE 平台的整体组成部分,允许在松耦合组件之间进行基于消息的通信。通过客户机、Web 层组件、业务层企业 JavaBeans (EJB) 和企业信息系统 (EIS) 层服务之间的 J2EE 堆栈可以发送和接收消息。这些消息是异步发送的,消息发出后,发送方能够继续使用其他应用程序逻辑,消息代理 (message broker) 负责代表发送方传递消息。消息可以在特定的端点(发送方和接收方)之间发送和接收,或通过公共/订阅交互模式在生产者与消费者之间进行匿名传递。在 J2EE 架构内部,通过 JMS 进行通信的组件可以使用容器提供的安全和事物处理功能。Geronimo 通过集成名为 ActiveMQ 的开源项目来支持这个重要的 API。

本文将探讨如何在 Geronimo 中集成 ActiveMQ。您将了解 Geronimo 如何从集成中受益,以及 ActiveMQ 功能如何通过驻留在 Geronimo 内部得到增强。可以从本文下载一个操作示例,该示例揭示了如何对通过 JMS 进行通信的客户机和组件进行编码。另一个示例显示了如何在 Geronimo 内部创建万能的 MDB。

ActiveMQ:最佳的开源 JMS 实现

在 JMS 术语中,ActiveMQ 是一个成熟而又功能丰富的 JMS 服务器或消息代理。位于 Codehaus(请参阅 参考资料)中的 ActiveMQ 支持许多不同的传输(如 TCP、SSL、UDP、多点传送、内部 JVM 和 NIO)和客户机交互(如推、拉和发行/订阅)。以一定规模的现有用户为基础,ActiveMQ 服务器完全可以独立工作,而不依赖任何容器(J2EE 或其他容器),它还可以与 J2EE 服务器主机(如 Geronimo)结合使用。

当 ActiveMQ 在 Geronimo 中运行时,会对 MDB 提供支持,即使用 JMS 消息的 EJB。JMS 的异步特性允许容器随需应变地激活 MDB,代表客户机执行 J2EE 服务器内的任务。Geronimo 会从 ActiveMQ 的富客户机支持中得到很大好处。与会话或实体 EJB 不同,MDB 不是通过严格的 EJB 界面调用的。相反,客户机只需将 JMS 消息发送到目标,便可以调用 MDB 服务。这样大大简化了使用基于 EJB 服务的客户机的编码过程。事实上,Geronimo 继承了向非 J2EE 客户机提供服务的功能 —— 这些功能受到独立 ActiveMQ 服务器的支持。





回页首


在 GBean 中包装 ActiveMQ 消息代理

ActiveMQ 消息代理被设计成可嵌入式的。Geronimo 充分利用了这一功能。通过提供包装已嵌入消息代理的 GBean 容器,可以创建 ActiveMQContainerGBean。ActiveMQ 消息代理的其他组件也被包装到 GBeans 中,您很快就会看到这种情形。图 1 说明了 ActiveMQContainerGBean 是如何嵌入 ActiveMQ 消息代理的。


图 1. ActiveMQContainerGBean

通过将 ActiveMQ 消息代理包装到 GBean 中,该代理的生命周期由 Geronimo 管理。具体地说,您可以使用 Geronimo 部署程序和管理工具部署、启动和管理 ActiveMQ 消息代理实例。消息代理的可配置属性是作为 GBean 属性公开的。在 图 1 中,ActiveMQContainerGBean 对 ActiveMQ 消息代理进行了包装,并提供了生活周期管理和配置服务。除了提供生命周期和配置支持外,Geronimo 还对持久消息提供持久支持。目前的 ActiveMQ 服务器配置使用默认 Derby RDBMS 实例来处理持久消息。

由 Geronimo 提供的默认 J2EE 服务器集集成了一个 ActiveMQ 消息代理实例。这个集合在服务器启动时启动消息代理,通常使用的命令行如下:

java -jar bin\server.jar                        

要查看此实例是如何配置的,可以检查 Geronimo 源代码分配的计划目录中的 system-activemq-plan.xml 部署计划。您将看到 ActiveMQContainerGBean 和其他相关的 ActiveMQ 组件是如何配置的。清单 1 显示了此部署计划中的相关字段。


清单 1. 在 system-activemq-plan.xml 部署计划中配置 ActiveMQContainerGBean
<?xml version="1.0" encoding="UTF-8"?>                        <configuration                        xmlns="http://geronimo.apache.org/xml/ns/deployment"                        configId="org/apache/geronimo/ActiveMQServer"                        parentId="org/apache/geronimo/SystemDatabase">                        ...                        <gbean name="ActiveMQ"                        class="org.activemq.gbean.ActiveMQContainerGBean">                        <attribute name="brokerName">possibly-unique-broker</attribute>                        <reference name="persistenceAdapter">                        <gbean-name>geronimo.server:j2eeType=JMSPersistence,name=ActiveMQ.cache,*</gbean-name>                        </reference>                        </gbean>                        <gbean name="ActiveMQ.cache"                        class="org.activemq.store.cache.SimpleCachePersistenceAdapterGBean">                        <attribute name="cacheSize">10000</attribute>                        <reference name="longTermPersistence">                        <gbean-name>geronimo.server:j2eeType=JMSPersistence,name=ActiveMQ.journal,*</gbean-name>                        </reference>                        </gbean>                        <gbean name="ActiveMQ.jdbc"                        class="org.activemq.store.jdbc.JDBCPersistenceAdapterGBean">                        <reference name="dataSource">                        <gbean-name>geronimo.server:J2EEApplication=null,J2EEServer=geronimo,                        JCAResource=org/apache/geronimo/SystemDatabase,j2eeType=JCAManagedConnectionFactory,                        name=SystemDatasource</gbean-name>                        </reference>                        </gbean>                        <gbean name="ActiveMQ.tcp.${PlanServerHostname}.${PlanActiveMQPort}"                        class="org.activemq.gbean.ActiveMQConnectorGBean">                        <attribute name="url">tcp://${PlanServerHostname}:${PlanActiveMQPort}</attribute>                        <reference                        name="activeMQContainer">                        <gbean-name>geronimo.server:j2eeType=JMSServer,name=ActiveMQ,*</gbean-name>                        </reference>                        </gbean>                        <gbean name="ActiveMQ.vm.localhost" class="org.activemq.gbean.ActiveMQConnectorGBean">                        <attribute name="url">vm://localhost</attribute>                        <reference name="activeMQContainer">                        <gbean-name>geronimo.server:j2eeType=JMSServer,name=ActiveMQ,*</gbean-name>                        </reference>                        </gbean>                        

清单 1 中,第一个 GBean 对 ActiveMQ 消息代理进行设置。该 GBean 的属性引用了一个永久适配器并指向第二个 GBean。第二个 GBean 包装了一个 ActiveMQ 内存缓存的实例,并且它引用了 ActiveMQ 的永久组件,以便支持永久性的信息。第三个 GBean 连接 JDBC 数据源,以便提供永久支持,并指向 SystemDatasource —— 一个 Derby RDBMS 实例。最后两个 GBeans 设置了以下两个传输,用于访问消息代理:

  • TCP 传输,默认情况下,位于本地主机,端口为 61616。
  • in-VM 传输,默认情况下,被映射到 vm://localhost。

如果需要更改此实例的配置,可以修改这个 system-activemq-plan.xml 文件,然后重新部署 org/apache/geronimo/ActiveMQServer 配置。





回页首


通过 JCA 1.5 资源适配器访问消息代理

利用 JMS 的应用程序组件(如 servlet、JSP 或 EJB)必须通过实现 JMS API 的库才能访问消息代理。这个 API 是由 Geronimo 提供的。若要用某种方法实现这个可以与任何 JMS 提供者(如消息代理)一起使用的 API,那么 Geronimo 必须支持 J2EE Connector (JCA) 1.5 规范。JCA 1.5 规范详细说明了应用服务器 (Geronimo) 和资源适配器 (RA) 之间所需的契约 —— 由 ActiveMQ 提供的一个驱动程序。(请参阅 参考资料,阅读关于 JCA 1.5 的文章。)Geronimo 中驻留的管理应用程序组件只有通过此 RA 才能访问 ActiveMQ 消息代理。这是 ActiveMQ 提供给表的另一个主要好处 —— 它含有符合 JCA 1.5 且可以与之集成的 RA 实现。图 2 显示了符合 ActiveMQ JCA 1.5 的 RA。


图 2. 符合 ActiveMQ JCA 1.5 的 RA

图 2 中,可以看到 ActiveMQ RA 通过已定义好的 JCA 1.5 系统契约与 Geronimo 的安全和事物处理系统发生的交互。ActiveMQ RA 支持出站连接(JMS 向外调用消息代理)和入站连接(用 ActiveMQ 初始的调用来调用 MDB)。对于出站连接,消息发送应用程序可以将提供者(本例中为 ActiveMQ)注册为分布式事物处理的一部分,也可以包括其他资源管理程序(如 RDBMS)。对于入站连接,一般先通过激活 MDB,Geronimo 才能启动事物处理。在目前的 JCA 1.5 规范以及 Geronimo 实现中,入站连接不与容器的安全子系统交互。

WorkManager 契约是 JCA 1.5 的一部分。它允许 RA 向应用程序服务器提交工作,以便于执行。这样允许服务器 —— Geronimo —— 对符合 RA 的线程管理和工作分配进行控制。对于 ActiveMQ,此功能用于管理入站连接的线程。有关此功能的更多信息,请参阅“JCA 1.5, Part 3: Message inflow”(developerWorks,2005 年 6 月)。

清单 2 显示了 system-jms-plan.xml 部署计划的一个片段。此计划对 RA 实例的组件进行了配置,正如 JCA 1.5 规范中部署描述符详细描述的那样。就 ActiveMQ RA 而言,这些组件包括 RA 实现类、出站连接工厂、主题和队列(管理的对象)。

清单 2 中部署计划的配置如下:

  • 一个 ActiveMQ RA 实例,它使用 TCP 传输协议来访问 清单 2 中所配置的 ActiveMQ 消息代理。
  • 一个 JCA 1.5 WorkManager 实现,由 Geronimo 提供,用于 ActiveMQ RA 实例。
  • 一个 JMS 连接工厂,可用于创建队列连接(通过 QueueConnectionFactory 界面)或主题连接(通过 TopicConnectionFactory 界面)。
  • 一个管理对象,它包含一个名为 MDBTransferBeanOutQueue 的 JMS 队列。
  • 一个管理对象,它包含一个名为 SendReceiveQueue 的 JMS 队列。

如果需要修改这个 JMS RA 的默认配置(例如,添加其他队列),则需要在 system-jms-plan.xml 部署计划中进行适当的更改。要使任何更改有效,首先要取消对 org/apache/geronimo/SystemJMS 配置的部署,然后,重新部署该配置。对于其他配置 ActiveMQ RA 实例的部署计划,请参阅侧栏 部署 ActiveMQ RA

部署 ActiveMQ RA

除了在 system-jms-plan.xml 中进行服务器启动的部署以外,还可以使用下列方法之一部署 ActiveMQ RA 实例:

  • 独立方法,使用 ra.xml 部署计划。
  • 使用企业应用程序 aRchive (EAR),在 geronimo-application.xm 部署计划中的 <module> 元素内进行部署。
  • 使用 Web 应用程序 aRchive (WAR),在 geronimo-web.xml 部署计划中的 <resource> 元素内进行部署。




回页首


应用程序客户机 JMS 访问

Geronimo 客户机应用程序容器支持的 J2EE 应用程序客户机可以访问 ActiveMQ 消息代理。图 3 显示了一些客户机访问配置。


图 3. J2EE 和非 J2EE 客户机对消息代理的访问

图 3 中上部的配置显示 J2EE 客户机应用程序对客户机范围内部署的 RA 的访问情况。这个 RA 实例被配置成可以访问服务器端的消息代理。图 3 中的第二个配置显示另一个 J2EE 客户机,该客户机附带一个客户机范围内部署的 RA,但是也可以访问在客户机上部署的消息代理实例。(这种情况在非连续的操作中非常有用,例如,偶尔连接到企业网络的笔记本电脑。)在第三个配置中,非 J2EE 客户机可以通过任何配置的传输直接访问 ActiveMQ 消息代理。该操作示例演示了如何创建这样一个独立的、非 J2EE ActiveMQ 应用程序客户机。





回页首


创建 JMS 应用程序:servlet 生产者和本地 ActiveMQ 消费者

这里的第一个示例显示 servlet 如何通过 JMS 与外部非 J2EE 应用程序通信。该示例使用了称为 SendReceiveQueue 的全局服务器范围的队列(参见 清单 2)。它包括 JMS 消息发送方(生产者)和 JMS 消息接收方(消费者)。生产者是一个 servlet,称为 SenderServlet,它在 Geronimo 内部运行。调用程序是一个独立的 ActiveMQ 客户机,不使用 Geronimo 对 JMS 提供支持。图 4 显示了这个示例中的交互情况。


图 4. SenderServlet 和 ActiveMQ 接收方应用程序

图 4 中,应用程序流程如下:

  1. 用户通过 Web 浏览器访问 SenderServlet。
  2. SenderServlet 由 Geronimo 承载,向用户显示一个数据项窗体。
  3. 用户输入文本消息,然后单击 Send
  4. SenderServlet 处理窗体提交的信息,并使用 JMS 将文本消息发送给 SendReceiveQueue。
  5. 独立的、非 J2EE ActiveMQ 客户机读取 SendReceiveQueue 并显示收到的消息。

本示例的 Web 应用程序源代码位于代码下载文件夹的 war_only 目录中(请参阅下面 参考资料 的下载部分)。客户机代码位于 mqclient 子目录中。

如果只是想尝试使用此示例,可以在 war_only/dist 子目录中找到 sender.war 文档,只需将下面的应用程序文档部署在 Geronimo 服务器上即可:

java -jar bin/deployer.jar sender.war                        

出现提示时,请输入用户名 system 和密码 manager。按照 readme.txt 文件中的说明来构建客户机。接下来,请转到 mqclient 目录,并使用 run.bat 文件运行客户机应用程序。这时将启动客户机,并等待传入消息。

通过在浏览器地址栏输入 http://localhost:8080/sender/sendform.cgi 可以访问数据项 servlet。

将文本消息输入到字段中,然后单击 Send。您会注意到 ActiveMQ 客户机会立即收到该消息并将其打印出来。

浏览 清单 3,可以看到一些 SendServlet.java 代码,在 war_only/src 目录中,可以找到完整的源代码。

清单 3 中,servlet 针对 HTTP GET 请求生成输入窗体,并根据 HTTP POST 请求处理窗体提交的信息。实际的 doPost() 方法用于解析输入的消息、创建队列连接、启动会话和将消息发送到队列。

通过 Java 命名和目录界面 (JNDI),servlet init() 方法可以查找连接工厂和队列。Geronimo 提供 JNDI 映射服务。您将看到队列是使用 java:comp/env/dwSendReceiveQueue 查找的。队列的名称将映射到 Web 应用程序的 web.xml 部署描述符中。清单 4 再现了 web.xml 的相关片段。


清单 4. web.xml 中的 JNDI 映射和资源引用
<resource-ref>                        <res-ref-name>DefaultActiveMQConnectionFactory</res-ref-name>                        <res-type>javax.jms.QueueConnectionFactory</res-type>                        <res-auth>Container</res-auth>                        </resource-ref>                        <message-destination-ref>                        <message-destination-ref-name>dwSendReceiveQueue</message-destination-ref-name>                        <message-destination-type>javax.jms.Queue</message-destination-type>                        <message-destination-usage>Produces</message-destination-usage>                        <message-destination-link>SendReceiveQueue</message-destination-link>                        </message-destination-ref>                        

清单 4 中,<message-destination-ref> 通过 <message-destination-link> 子元素将名称 dwSendReceiveQueue 映射到系统范围的队列中。遗憾的是,J2EE 1.4 规范不支持 web.xml 中 <resource-ref> 元素中的 <resource-link> 子元素。因此,必须使用连接工厂的实际资源名 (DefaultActiveMQConnectionFactory),或者必须创建一个自定义部署计划(如 geronimo-web.xml)以映射该引用。

对 ActiveMQ 消息接收方客户机进行编码

接收方是本地 ActiveMQ 客户机,它不需要任何 Geronimo 客户机支持。它通过 TCP 传输和 URL tcp://localhost:61616 访问 Geronimo 中驻留的 ActiveMQ 消息代理。此客户机 JMSReceiver 的部分源代码如 清单 5 所示。在 war_only/clientsrc 目录中,可以看到完整的源代码。


清单 5. JMSReceiver.java —— 非 J2EE JMS 消息接收方客户机
package com.ibm.dw.geronimo.jms;                        import javax.jms.*;                        import org.activemq.ActiveMQConnectionFactory;                        public class JMSReceiver {                        protected Queue queue;                        protected String queueName = "SendReceiveQueue";                        protected String url = "tcp://localhost:61616";                        protected int ackMode = Session.AUTO_ACKNOWLEDGE;                        public static void main(String[] args) {                        JMSReceiver msgReceiver = new JMSReceiver();                        msgReceiver.run();                        }                        public void run() {                        try {                        ActiveMQConnectionFactory connectionFactory =                        new ActiveMQConnectionFactory(url);                        QueueConnection connection =                        (QueueConnection)                        connectionFactory.createConnection();                        connection.start();                        MessageConsumer consumer = null;                        Session session = connection.createQueueSession(                        false,                        Session.AUTO_ACKNOWLEDGE);                        queue = session.createQueue(queueName);                        consumer = session.createConsumer(queue);                        System.out.println("Waiting for message (max 5)");                        for (int i = 0; i < 5; i++) {                        Message message = consumer.receive();                        processMessage(message);                        }                        System.out.println("Closing connection");                        consumer.close();                        session.close();                        connection.close();                        } catch (Exception e) {                        ...                        }                        }                        public void processMessage(Message message) {                        try {                        TextMessage txtMsg = (TextMessage) message;                        System.out.println("Received a message: " + txtMsg.getText());                        } catch (Exception e) {                        ...                        }                        }                        }                        

清单 5 中,JMSReceiver 完全独立于 J2EE 代码。无需执行 JNDI 查找或目标映射,因为 ActiveMQ 本地库会自动执行等同的操作。

通过混合使用 JMS API 和 ActiveMQ 支持库,对这个非 J2EE 消息消费者进行编码非常简单。使用 Geronimo 的支持对 J2EE 消息消费者进行编码也很简单。下一个示例将显示如何构建有用的 J2EE 消息消费者:MDB,该程序可以将产品类别添加到 Really Big Pet Store 示例中。





回页首


创建用于数据更新的 MDB

第二个操作示例将显示如何构建 MDB。这个示例使用与第一个示例完全相同的 SendServlet 代码,但是,消息消费者现在是同一企业应用程序内的 MDB(绑定在同一 EAR 中)。图 5 举例说明了这个示例的操作过程。


图 5. MDB 示例的操作过程

这个示例使用的代码来自文章“Geronimo! 第 2 部分:驯服 J2EE 1.4 这匹野马”(developerWorks,2005 年 5 月)。这篇文章还包含关于 Really Big Pet Store 操作的更多信息。

图 5 中,初始 Web 应用程序的操作过程被完好地保留下来。购物者可以使用浏览器访问该店铺的基于 JSP 的用户界面。StoreController servlet 使用称为 CategoriesBean 的无状态会话 EJB 的 getCats() 方法获取产品类别信息。

对 CategoriesBean 进行修改,以便使用新的 helper 类 CategoryData 来获得类别列表。

MDB 又称为 CategoriesMDB。在将消息放入 SendReceiveQueue 时,EJB 会被激活。CategoriesMDB 提取此消息内容,并通过它将产品类别添加到 CategoryData 中。因为 CategoriesBean 的会话 EJB 可以使用 CategoryData 获得每个屏幕更新,所以购物者会立即看到新的类别。

在生产环境中,代表类别的实体 bean 可以代替 CategoryData helper 类。在这里,有意地避免了使用实体 bean,以简化这个示例的配置和设置。

对 CategoriesMDB 进行编码

SenderServlet 的代码与第一个示例的版本几乎一样。请参阅 ear_ejb/src/SenderServlet.java,以获得完整的源代码。

CategoriesMDB 的代码如 清单 6 所示,您可以在 ejb/CategoriesMDB.java 找到完整的源代码。


清单 6. CategoriesMDB —— 使用 JMS 消息并添加类别
import javax.ejb.*;                        import javax.jms.*;                        import com.ibm.dw.reallybigpet.ejb.CategoryData;                        public class CategoriesMDB                        implements MessageDrivenBean,MessageListener {                        private transient MessageDrivenContext mdc = null;                        public CategoriesMDB() {                        }                        public void setMessageDrivenContext(MessageDrivenContext mdc) {                        this.mdc = mdc;                        }                        public void ejbCreate() {                        }                        public void onMessage(Message inMessage) {                        TextMessage msg = null;                        try {                                                        if (inMessage instanceof TextMessage) {                        msg = (TextMessage) inMessage;                        CategoryData.getInstance().addCat(msg.getText());                                                }                        } catch (Exception e) {                        e.printStackTrace();                        }                        }                        public void ejbRemove() {                        }                        }                        

MDB 代码非常简单。清单 7 中突出显示的行是执行任务的位置。队列收到 JMS 消息时,Geronimo 将执行以下操作:

父配置和类装载

确保将 geronimo-application.xml 中的应用程序元素的 parentId 属性设为 org/apache/geronimo/SystemJMS。这样可以确保您正在使用 JMS RA 的类装载器。相反,如果将 parentId 设为 org/apache/geronimo/System,那么您将遇到类似 java.lang.ClassNotFoundException: org.activemq.ra.ActiveMQActivationSpec 的类装载问题。这是使用 Geronimo 部署 MDB 时可能出现的最常见错误。

  1. 获取该消息。
  2. 激活这个 MDB 实例。
  3. 调用 MDB 的 onMessage() 方法,并将收到的消息作为该方法的一个参数传递。

您需要在部署描述符 ejb-jar.xml 中使用关联的 JMS 目标(或队列)配置 MDB。在 清单 7 中可以看到它的一个示例。

清单 8 中,SendReceiveQueue 通过 <message-driven> 元素中的 <activation-config> 子元素与 MDB 关联,要选择将要使用的 JMS RA,请创建与 清单 8 类似的自定义 openejb=jar.xml。


清单 8. 自定义部署计划 openejb-jar.xml 来选择 JMS RA
<?xml version="1.0"?>                        <openejb-jar xmlns="http://www.openejb.org/xml/ns/openejb-jar"                        configId="catmdb"                        parentId="org/apache/geronimo/SystemJMS"                        >                        <enterprise-beans>                        <message-driven>                        <ejb-name>CatMDB</ejb-name>                        <resource-adapter>                        <resource-link>ActiveMQ RA</resource-link>                        </resource-adapter>                        </message-driven>                        </enterprise-beans>                        </openejb-jar>                        

清单 8 中,默认的系统范围的 RA 使用 <resource-link> 元素按名称进行选择。请参阅侧栏 父配置和类装载,以了解 geronimo-application.xml 部署计划所需的信息。

要尝试使用这个示例,请部署 reallybigpet.ear 应用程序,它位于 ear_ejb/dist 目录中。要访问该店铺,请将浏览器指向 http://localhost:8080/ReallyBigPetStore/store.cgi。要访问添加类别的窗体,请将浏览器指向 http://localhost:8080/ReallyBigPetStore/sendform.cgi。





回页首


结束语

ActiveMQ 为 Geronimo 提供了高质量的 JMS 服务,同时,Geronimo 为 ActiveMQ 提供了基于 JDBC 的持续性、生命周期管理(通过遵从 JCA 1.5)、安全性、传输和工作管理。这种共存关系使 Geronimo 用户获得了两者的优点:可以访问通过 ActiveMQ 的丰富传输和客户机支持增强的标准化了的 J2EE 1.4 JMS 和 MDB 工具。





回页首


致谢

作者衷心感谢 Geronimo 团队的 David Jencks,在本文评审期间,他给予了专业帮助。






回页首





参考资料



关于作者

Sing 是一位顾问和多产的作家,具有二十多年的行业工作经验,编写了Beginning JavaServer PagesProfessional Apache Tomcat 5Pro JSP - Third Edition、Early Adopter JXTA、Professional Jini、Beginning J2ME: From Novice to Professional, Third Edition和其他许多书籍。他定期向技术杂志投稿并参加了开源社区。他还是开源、VOIP 和 P2P 发展的积极倡导者。可以通过 westmakaha@yahoo.com 与 Sing 联系。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ActiveMQ 实践之路(前言)
用JMS进行企业消息传递
利用 WAS CE v2.1 创建基于 JMS 的应用
几款开源ESB总线的比较
J2EE学习中一些值得研究的开源项目 - CSDN Java频道
ActiveMQ 即时通讯服务 浅析(一)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服