PHP 开发者的 BlazeDS 和 JMS 指南,第二部分

阅读数:2969 2010 年 7 月 19 日 02:40

本系列的第1 部分主要描述了通过使用BlazeDS 将Java 服务、PHP 以及Adobe Flash Builder 的用户界面与JMS 相集成。文章展示了只需区区几处配置改动,您就可以使用BlazeDS 通过MessageBroker servlet 把Flash Builder 所建立的用户界面与JMS 消息队列相连接。文章还涉及了整合PHP 和Java 及JMS 的另一种方法:使用REST(Representational State Transfer)Web 服务把消息发送到JMS 队列。

本系列的第 2 部分则讨论整合 PHP 和 Java 的另外两个方法。第一种方法——桥接(bridging),让您可以在 PHP 中使用 Java 对象,从 PHP 中通过 Java 曝露的方法把消息发送到 JMS 消息队列。第二种集成的方法是利用 STOMP(Streaming Text Orientated Messaging Protocol)协议与 ActiveMQ(JMS 的实现之一)进行通信。当 ActiveMQ 接收到消息,ActiveMQ 会把消息放入 JMS 队列,Adobe Flex 应用程序则从队列中接收消息。

通过桥接与PHP集成

本系列的第 1 部分说明了如何使用 BlazeDS 和 JMS 在 Java 与 Flex 之间传递消息。其他技术也能够完成 PHP 与 Java 的集成,让您间接地把消息放入 JMS 队列。 Java/PHP 桥就是这些技术的一种,它允许您使用已配置好的、运行于 ActiveMQ 上的同一个 JMS 队列。使用 Java/PHP 桥,您可以写 PHP 脚本来调用 Java 类中的方法。此方法会把 JMS 消息放入消息队列。Flex 用户界面则使用 BlazeDS 来连接 JMS 队列,从队列中接收消息,正如它在 REST Web 服务中做的那样。

PHP/Java 桥的发布包里面有一个 JavaBridge.war 文件。解压此 WAR 包,把解压后的文件放进 ActiveMQ 的 webapps/JavaBridge 目录下,并在 conf/jetty.xml 配置文件中添加此应用程序。此 JavaBridge Web 应用程序包含了从 PHP 脚本接收消息的 Servlets,处理消息,然后运行 Java 类。

Java 示

由于 PHP 脚本和 Java 类之间的通信可能会很慢,因此需创建一个 Java 类,在一个方法中来处理。这种做法就是著名的 Fa?ade 设计模式——在所暴露的简单方法背后隐藏了复杂的功能。

下面是 Java 实例的 Fa?ade 类,类名为 MessageHelper,包名为 com.example.bridge。

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.util.IndentPrinter;

public class MessageHelper {
    
    private Destination destination;
    private String user = ActiveMQConnection.DEFAULT_USER;
    private String password = ActiveMQConnection.DEFAULT_PASSWORD;
    
    public void sendMessage(String url, String subject, String messageText) {
        
        Connection connection = null;            
  
        try {
            boolean isTopic = true;
            boolean isPersistent = false;
            
            System.out.println("Using URL:  <" + url + ">");
            System.out.println("Using Subject:  <" + subject + ">");
            System.out.println("Sending Message Text:  <" + messageText + ">");
            
            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);
            connection = connectionFactory.createConnection();
            connection.start();

            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

            if (isTopic) {
                destination = session.createTopic(subject);
            } else {
                destination = session.createQueue(subject);
            }

            // Create the producer.
            MessageProducer producer = session.createProducer(destination);
            if (isPersistent) {
                producer.setDeliveryMode(DeliveryMode.PERSISTENT);
            } else {
                producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
            }

            TextMessage message = session.createTextMessage(messageText);
            
            producer.send(message);

            System.out.println("Done.");

            // Use the ActiveMQConnection interface to dump the connection
            // stats.
            ActiveMQConnection c = (ActiveMQConnection)connection;
            c.getConnectionStats().dump(new IndentPrinter());

        } catch (Exception e) {
            System.out.println("Caught: " + e);
            e.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (Throwable ignore) {
            }
        }
        
    }
}

此 Java 类只有一个方法——sendMessage(),该方法有三个参数:JMS 的目的地 URL 地址、所使用的 Topic 名及消息内容。

把以上写好的类放入一个 JAR 文件中,然后把此 JAR 文件放入 JavaBridge Web 应用程序的 WEB-INF/lib 文件夹下面。之所以要把该 JAR 文件放在 JavaBridge Web 应用程序的类路径(lib 目录)下,是因为 JavaBridge Web 应用程序实际负责实例化 JAR 包中的 Java 类。MessageHelper 类的这一方法与 Web Server 是在同一个虚拟机中执行的——在本例中,就是执行 Jetty 实例和 ActiveMQ 的那个虚拟机。

PHP 示

下面的 PHP 脚本使用了 Java.inc 文件,它是 PHP/Java 桥自带的文件。然后 PHP 脚本创建 Java 对象并调用其方法:

<?php
require_once ("java/Java.inc");

java_autoload('messageHelper.jar');

$helper = java('com.example.bridge.MessageHelper');

$url = 'tcp://localhost:61616';
$subject = '/topic/MyTopic/';
$message = 'Hello, world!!';

$helper->sendMessage($url, $subject, $message);

echo "Message sent";
?>

在 PHP 脚本中,java_autoload() 方法加载 JAR 文件。java() 方法使用 Java 对象全名来载入 Java 对象。 Java 对象加载后,PHP 脚本通过为 Java/PHP 桥配置的端点调用 SendMessage() 方法。

为 Java/PHP 桥配置端点需要在 java 目录的 java.inc 文件中进行配置。通过改变端点,您可以在不同的 Web 服务器中执行 PHP 脚本。如果您已经有一个配置了 PHP 的 Web 服务器,您可以把该校本放在一个 Web Document 目录下并执行它。要让您的 PHP 脚本运行正常,您还必须将发布包中的 java 目录放在与 PHP 脚本相同的目录下(查看 PHP/Java Bridge 以了解详情)。

在上例中,桥接只建于 PHP 和 Java 之间,使得 PHP 代码可以调用 Java 对象。而例子中不需要从 Java 到 PHP 的桥接。

实战例子:用BlazeDS发送和接收消息

要看实战的例子,先要启动 Flex 应用程序。在另一个浏览器中,运行连接到 PHP/Java 桥的 PHP 脚本。您会在 Flex 用户界面上看到“Hello, world”消息。

使用STOMP协议与PHP集成

STOMP 是另一种技术,您可以用它来弥合 PHP 和 ActiveMQ JMS 之间的鸿沟。在这个例子中,ActiveMQ 使用 STOMP 协议从 PHP 脚本接收消息,然后把消息路由到 JMS Topic,Flex 应用程序接收消息并在界面上显示消息。

STOMP概述

STOMP 是一个协议,它可以使用多种不同的客户端发送消息。尽管 ActiveMQ 已内建了对 STOMP 的支持,你还是可以使用类似 StompConnect 这样的 STOMP 类库与 JMS 通信。

下面的例子说明了如何在 ActiveMQ 中使用内建的 STOMP 协议支持。与 STOMP 一起工作的消息代理(message broker)需要在 conf/activemq.xml 配置文件中打开:

<transportConnectors>
    <transportConnector name="openwire" uri="tcp://0.0.0.0:61616" />
    <transportConnector name="stomp" uri="stomp://0.0.0.0:61613" />
</transportConnectors>

添加了 STOMP 代理的配置后,重启 ActiveMQ。当 ActiveMQ 启动时,它会把“STOMP 连接器已经启动”的消息写入日志文件:

INFO | Connector openwire Started

INFO | Listening for connections at: stomp://silverstreak.local:61613

从 PHP 脚本经由 STOMP 发送的消息被 ActiveMQ 接收,接收到的消息被放入 JMS 主题或 JMS 队列,由相同的消费者接收。经由 STOMP 发送消息还使得 Flex 界面无需修改代码或配置就能接收同样的消息。在这种方式下,利用 STOMP 将消息发送到 ActiveMQ,对应用程序的其它部分来说,是相当透明的。

PHP代码

您可以使用下面的 PHP 脚本利用 STOMP 来把消息发送到与前面例子相同的 MyTopic JMS 主题(本文一直在使用)。代码使用的 Stomp.php 类来自于 FuseSource 。Stomp.php 类提供了一个从 PHP 发送消息的简单方法。要使用这个类,你必须下载该发布包并将其文件复制到示例脚本相同的目录中:

try {
    // include a library
    require_once("Stomp.php");
    // make a connection
    $con = new Stomp("tcp://localhost:61613");
    // connect
    $con->connect();
    // send a message to the queue
    $con->send("/topic/MyTopic", "Hello, world");
    echo "Sent message with body 'test'\n";
    $con->disconnect();

} catch (Exception $e) {
    var_dump($e->getMessage());
}

?>

此代码连接到 ActiveMQ 服务器的 61613 端口,并发送一个很短的文本消息(“Hello, world”)。然后,它从 STOMP 连接器断开连接。

ActiveMQ 把消息路由到相应的 JMS 主题。Flex 应用程序能够接收到这个消息,因为 Flex 订阅了此主题。

实战例子

要看到实战的例子,请再次打开 Flex 应用程序。另外打开一个浏览器,运行您刚才创建的 PHP 脚本,“Hello, world”消息将会显示到 Flex 应用程序的界面上。

使用JNDI

为简单起见,本系列的所有例子均直接使用了队列名和主题名(比如:/topic/MyTopic)。然而,在使用 JMS 的生产环境中,JMS 主题名或队列名一般是使用 JNDI(Java Naming and Directory Interface)进行配置的,而不会采用硬编码的方式。

JNDI 是一个 Java 标准,它允许你抽象 JMS 队列名、主题名、甚至是主机名的实际名称。JNDI 有点像域名系统(DNS),使您能够使用主机名而不是 IP 地址来识别主机。使用 JNDI 可以让您随时调整服务器上的 JMS 主题或队列的位置和实现,而不必修改您的代码。

除了抽象主题名和队列名之外,JNDI 还提供了另一个有用的功能:能够使用 Java EE 的 JMS 对象接口来编写代码。使用接口而不是具体的实现,可以使您的 Java 代码不依赖于特定的供应商。

看看下面的 Java 代码例子:

javax.naming.Context ctx = new javax.naming.InitialContext();
javax.jms.ConnectionFactory factory = (javax.jms.ConnectionFactory)ctx.lookup("ConnectionFactory");
javax.jms.Connection conn = factory.createConnection();
javax.jms.Destination destination = (javax.jms.Destination)jndiContext.lookup("MyTopic");
javax.jms.Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
// send the message...
javax.jms.MessageProducer producer = session.createProducer(destination);
javax.jms.TextMessage message = session.createTextMessage();
message.setText("Hello, world!");
producer.send(message);
conn.close();

以上 Java 代码没有使用 ActiveMQConnectionFactory 的具体实现,而是使用了 InitialContext 对象来“查找(lookup)”指定的连接器工厂实现。然后,代码使用 InitialContext 对象上相同的查询方法(lookup)来获取 JMS 主题的引用。这样,本例的代码中就不会包含对 ActiveMQ 的直接引用。

使用 JNDI 的缺点——除了要学习如何在 Java 代码中使用 JNDI 之外,还需要提供一些额外的配置,以获取初始的 JDNI 上下文。对于前者,ActiveMQ 提供了一个简单的 JNDI InitialContextFactory 以方便使用。(请参阅 ActiveMQ 支持页面以获取更多信息)。至于需要额外的配置,无需修改代码即可在不同厂商提供的应用服务器上部署和执行这一优点可以抵消掉这一瑕疵。如果您的环境有可能切换到不同的供应商,您可以考虑使用 JNDI 来帮助抽象细节。

网络考虑因素

此集成解决方案提供了 PHP 和 Flex 客户端通过 Java/JMS 技术在网络上进行通信的功能。因为您可以把不同的内容部署到不同的 Web 服务器,在评估集成技术时还需考虑网络访问的一些因素。

如果在 PHP、Flex 和 Java/JMS 服务之间存在防火墙,防火墙开放的端口可能会影响您选择用于集成的方法。由于 BlazeDS 是通过 MessageBroker Servlet 将 Flex 界面连接到 JMS 的,因此服务器只需标准的 80 端口(或 Web 应用服务器绑定的任何端口)能够访问即可。

ActiveMQ 使用非标准的端口来监听消息,但如果 ActiveMQ 与 BlazeDS 作为 Web 应用服务器运行在同一台机器上时,端口(“openwire”连接器的 61616 端口)问题只受限于本地接口,而不是公开暴露出来。

您的 Web 应用服务器可以在 80 端口暴露 REST Web 服务,使得跨网络和服务器进行集成相对简单。基于这个原因,许多人会选择 Web Services。

PHP/Java 桥使用了部署在 JavaBridge Web 应用程序中的一个 Servlet,通过 XML 流机制暴露 Java 类给 PHP。因为这个桥接是作为一个 Servlet 安装的,所以也可以从 Web 服务器的标准端口访问。

但是,使用 STOMP 协议需要在 PHP 脚本和(ActiveMQ 的)STOMP 消息代理之间开放一个非标准的端口。上面例子中开放的端口是 61613 端口。如果限制了从网络访问这个端口,那么 STOMP 或许并不是集成 PHP 脚本、Flex 界面及 Java/JMS 服务一个好的解决方案。

小结

JMS 是一个消息传递服务,支持主题和队列,其很多特性使其成为稳定消息传递的一个很好选择。BlazeDS 使得您无需付出很多工作量即可让 Flex 应用程序从 Flex 客户端发送消息到 JMS。

本系列的第1 部分介绍了JMS 的消息传递,并示范了如何配置BlazeDS 与JMS 队列进行通信。文章还介绍了使用Java 实现的REST Web 服务来集成PHP 应用程序和JMS 的方法,让您的Flex 用户界面可以从PHP 脚本收到消息。

本文则介绍了从 PHP 发送消息到 JMS 主题或队列的另外两种方法。PHP/Java Bridge 是一个开源项目,提供了一个从 PHP 脚本调用 Java 类的框架。通过编写单个的 Java 类——即所谓的 Fa?ade 模式,您的 PHP 脚本就能够将消息发送到 JMS,并且从 Flex 界面接收到此消息。STOMP 是一个协议,它有多种语言的支持库。利用 STOMP,您可以在 Flex 应用中与 JMS 消息代理通信并接收消息。

总之,这些不同的技术提供了整合 PHP、Flex 或 Java 应用的不同选项。整合现有的应用程序而不是重新开发它们,可以节省您的时间和精力,而且您也能自由选择适合您需要的最佳实现。

关于作者

Nathan A. Good 居住在 Minnesota 的 Twin 城地区。其专长是软件开发、软件架构和系统管理。平时不编写软件时,他喜欢组装 PC 和服务器、阅读和使用新技术,并鼓励他的朋友转用开源软件。他是许多书籍和文章的作者或合著者,其中包括《Professional Red Hat Enterprise Linux 3》、《Regular Expression Recipes: A Problem-Solution Approach》以及《Foundations of PEAR: Rapid PHP Development》。

查看英文原文: BlazeDS and JMS for PHP Developers, Part 2


译者简介:李强,计算机硕士,毕业于电子科技大学,目前在四川长虹电器股份有限公司技术中心从事研发工作,主要研究领域是 SOA、ESB、Web 服务以及分布式应用等。

感谢宋玮对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评论

发布