作者:Vikram Vaswani
Zend Server 是一个高性能的、企业就绪的 PHP 系统,通过内置的“Java 桥”可实现 PHP 与 JEE 应用程序及服务的交互。本文阐释其工作原理。
下载:
Oracle WebLogic Server
Zend Server
无论您拥有 Java 经验还是 PHP 经验,或者两种经验都有,对企业 Web 应用程序开发人员来说,能够将这两种开发环境相集成始终是一种“两全其美”的境况。PHP 是最通用的 Web 应用程序开发环境之一,可通过 PECL 和 PEAR 信息库及 Zend Framework 免费获得大量扩展和附加库。在 PHP 环境中,开发人员可以轻松执行各种任务,包括访问云服务、通过编程方式生成 PDF 和处理 XML。
在 Java 环境中,Java 开发人员可获得 Java 安全性、多线程功能和完全 Unicode 实现带来的各种好处。这些好处与开发工具和框架(Java Server Pages (JSP)、Enterprise JavaBeans (EJB)、JavaServer Faces (JSF)、Hibernate 和 Spring)相结合,使 Java 成为应用程序开发的热门选择。
那么,为何不让 PHP 与 Java 进行交互,直接在 PHP 应用程序内部访问 Java 方法和对象呢?如果您已经拥有基于 Java 的基础架构并且需要围绕它开发一个 Web 界面,那这样做尤为方便。从 PHP 访问现有的 Java 架构,可减少代码重复并节省移植和测试代码的时间;而使用 PHP 构建 Web 界面,则可利用 PHP 的易用性、对现代 Web 技术的支持以及大量附加库。
现在,乍一看,实现 PHP 和 Java 的交互似乎很困难。然而,有一个简单的方法可以做到:Zend Server 是一个高性能的、企业就绪的 PHP 系统,可以在 Windows 和 Linux 上运行,通过内置的“Java 桥”可实现 PHP 与 JEE 应用程序及服务的交互。本文将向您进行详细介绍,演示使用 Zend Server 实现 PHP 应用程序与部署在 Oracle WebLogic 中间件上的 Java 类的交互是多么简单。
首先,简单介绍一下。Zend Server 是一个现成的 PHP 系统,可以简化基于 Windows 和 UNIX 的环境中 PHP 应用程序的开发和运行。它包括一个 PHP 更新版本、对众多数据库系统(包括 Oracle 和 MySQL)的支持,以及许多用于改进 PHP 性能和诊断的特定于 Zend 的附加软件。还附带一个内置的“Java 桥”,可用于实现 PHP 应用程序与部署在 Oracle WebLogic 中间件上的 Java 类的交互。
目前提供两种版本的 Zend Server:社区版和商业版。两种版本都包括 Zend Java Bridge,通过它可轻松实现 PHP 和 Java 应用程序的交互。该组件作为后台程序或后台服务实现,它实例化自己的 Java 虚拟机 (JVM) 并为 PHP 开发人员提供访问该 JVM 的 API。PHP 开发人员通过该 API 可直接访问 Java 对象和服务,Zend Java Bridge“转换”两种语言之间的请求和响应。您很快就会看到,使用 Zend Server 的点击式界面可以很轻松地对该系统进行配置,而且该系统对开发人员非常友好。
Zend Java Bridge 为企业或高访问量环境提供了一些关键优势:
单一 JVM 实例:Zend Java Bridge 对所有 PHP 进程均使用一个 JVM,从而减少了内存占用并提高了操作效率。
松散耦合:Zend Java Bridge 是 Zend Server 的一个组件,可以在不影响系统其余部分的情况下启用或禁用它。通过 Zend Server 基于 Web 的配置界面可轻松启用或禁用该组件。
支持和培训:每个 Zend Java Bridge 版本都经过测试和认证,可以与 PHP 和其他相关工具配合使用。开发人员和管理员也可以获得在自己的 IT 环境中部署 Zend Java Bridge 的培训和商业支持。
值得注意的是,Zend Java Bridge 并不是唯一的选择。PHP 对 XML 和 JSON 格式的内置支持意味着即使没有 Zend Java Bridge,仍然可以从 PHP 访问作为 Web 服务公开的 Java 资源。PHP 4.x 还包括一个 Java 扩展和一个 Java Servlet SAPI,前者允许直接从 PHP 实例化 Java 类,后者可以使 PHP 集成到 servlet 环境中。
还有其他开源工具也可以获得相同的结果。例如,Quercus 是 PHP 的 100% Java 实现,而 PHP/Java Bridge 是一个 Java Web 应用程序,允许使用基于 XML 的协议在 PHP 和 Java 之间进行双向方法调用。本文不讨论 Quercus 和 PHP/Java Bridge,但您可以在 Gary Horen 发表的文章中了解有关使用 PHP/Java Bridge 的更多信息。
在本文中,我将假设您拥有最新版本的 Oracle WebLogic Server 和 JRE 1.5 或更高版本,安装了所有示例并激活了 Samples 域。如果未安装,您可以从 OTN 的 Oracle WebLogic 主页下载适合您开发环境的 Oracle WebLogic Server 版本。请注意,系统不会自动安装代码示例,您必须执行自定义安装来获得代码示例。本文的示例都在安装了 Zend Server 5.0.1 和 PHP 5.3 的 Linux 平台上使用带有 JDK 1.6 的 Oracle WebLogic Server 11g(10.3.2 版)进行过测试。
我还假设您在开发系统上已经安装了最新版本的 Zend Server,并且运行正常。如果未安装,您可以下载一个最新版本;提供了适用于 Linux、Microsoft Windows、Mac OS X 和 IBM i 的版本。您将在 Zend Server 安装指南以及这篇 OTN 文章中找到有关如何安装和配置 Zend Server 的详细说明。
成功安装 Zend Server 后,您可以通过 URL http://localhost:10081/ZendServer 访问它。首次访问该 URL 时,将要求您接受软件许可协议,并输入管理员口令和许可密钥。请记住,您可以获得试用许可密钥。如果没有许可密钥,Zend Server 将以社区模式运行,此时许多主要特性都被禁用。
输入要求的信息后,您会看到服务器管理控制台,在此可以概览当前服务器状态。屏幕显示如下所示:
在 Windows 系统上,Zend Server 安装程序包包括 Zend Java Bridge,安装例程将自动检测系统 JDK,然后安装和激活该组件。在 Linux 系统上,通常需要手动下载和配置 Zend Java Bridge。所以,导航到 Server Setup -> Components 页面,检查是否安装和激活了 Zend Java Bridge。
如果 Zend Java Bridge 的“Status”是“ON”,则意味着已安装了 Zend Java Bridge。如果 Zend Java Bridge 的“Status”是“OFF”,可通过单击“Action”栏的“Turn on”链接并重启 PHP 来激活 Zend Java Bridge。在某些平台上,您必须先下载并安装桥。Oracle Unbreakable Linux Network 的订阅者可以安装 zend-server-repo RPM,然后使用 yum 安装 Zend Java Bridge,如下所示:
shell> up2date zend-server-repo shell> yum install php-5.3-java-bridge-zend-server
基于 Debian 的发布版本用户可以使用 aptitude 安装该组件,如下所示:
shell> aptitude install php-5.3-java-bridge-zend-server
无论哪种情况,包管理器都会将 Zend Java Bridge 下载并安装到 /usr/local/zend/ 目录层次结构中。安装完程序包后,系统将提醒您输入系统的 Java 可执行文件的完整路径。如果不知道系统 JDK 的位置,您可以安全地使用 Oracle WebLogic Server 附带的 JDK。在 Linux 系统上,可以稍后通过执行 /usr/local/zend/bin/setup_jb.sh shell 脚本来更改此路径。
如果一切顺利,Zend Java Bridge 现在将启动,在控制台上显示一条成功消息。如果您导航回 Server Setup -> Components 页面,应该会看到 Zend Java Bridge 处于活动状态。如果 Zend Java Bridge 没有处于活动状态,您可能需要手动启动 Zend Java Bridge 服务器后台程序;Zend Server 安装指南中提供了有关如何使用包安装和控制脚本完成这一操作的详细信息。启动服务器后台程序后,可通过 Server Setup -> Components 页面启用 Zend Java Bridge。
如果您已尽力,但仍无法激活 Zend Java Bridge,那么可以进一步对该问题进行故障排除:
检查 Zend Java Bridge 服务器后台程序是否运行。
检查 Java Bridge 组件 (javamw.jar) 所需的 JAR 文件是否包含在 Java CLASSPATH 中。
检查 Java CLASSPATH 是否包括正确的 JRE 版本。
使用 Server Setup -> Components -> Zend Java Bridge -> Directives 选项显示可用配置选项的列表,这样可以更改 Zend Java Bridge 的配置。下面提供了可用设置的列表:
您能控制的内容包括:Java Bridge 监听请求的服务器端口、将数据从 PHP 传输到 Java 时使用的编码,以及是否应该将 Java 对象转换为基元。除非您有充分的理由更改上述内容,否则请保留其默认值。
安装完所有组件后,试运行一下 Zend Java Bridge。之前提到过,Zend Java Bridge 为 PHP 开发人员提供了一个 API,用于从 PHP 脚本内部访问 Java 类。为了说明这一功能,我们来看看下面这个 Java 类,它使用 java.util.GregorianCalendar 类执行简单的日期添加:
import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; class MyClass { public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm aaa"); // set calendar to 1 Jan 2010 Calendar calendar = new GregorianCalendar(2010,Calendar.JANUARY,1); String date = sdf.format(calendar.getTime()); System.out.println("Starting date is: " + date); // add 4 months 3 days System.out.println("Adding 4 months 3 days... "); calendar.add(Calendar.MONTH, 4); calendar.add(Calendar.DAY_OF_MONTH, 3); // print ending date value date = sdf.format(calendar.getTime()); System.out.println("Ending date is: " + date); } }
现在,尝试使用 PHP 执行相同的操作,其方法是通过 Zend Java Bridge 使用 java.util.GregorianCalendar 类:
<?php // initialize calendar object $cal = new Java("java.util.GregorianCalendar"); $cal->set(2010,$cal->JANUARY,1); // initialize date object $date = new Java("java.text.SimpleDateFormat", "d MMM yyyy hh:mm aaa"); // format and print starting date echo "Starting date is: " . $date->format($cal->getTime()) . '<br/>'; // add 4 months 3 days echo "Adding 4 months 3 days...<br/>"; $cal->add($cal->MONTH, 4); $cal->add($cal->DAY_OF_MONTH, 3); // format and print ending date echo "Ending date is: " . $date->format($cal->getTime()); ?>
将该脚本保存到 Zend Server 文档根目录中,该目录在 Enterprise Linux 和 Red Hat Linux 上通常为 /var/www/html,在 Windows 上为 C:\Program Files\Zend\Apache2\htdocs\。
该脚本首先初始化一个新的 Java 对象,将要初始化的 Java 类名称传递给对象构造函数 — 在本例中,是 java.util.GregorianCalendar 对象的一个实例。通过 Zend Java Bridge 将该请求传输给 JVM;然后,JVM 创建 GregorianCalendar 对象并将其返回给调用 PHP 脚本。然后,使用对象的 set() 方法将日历初始化为特定的日期和时间。
接下来,再创建一个 Java 对象,这次是一个 java.text.SimpleDateFormat 类的实例,将要使用的日期格式作为另一个参数传递给 SimpleDateFormat 对象构造函数。然后,使用生成对象的 format() 方法设置由 GregorianCalendar 对象的 getTime() 方法返回的日期和时间的格式,并使用 PHP 的 echo 语句将结果输出到显示器。
为了进一步演示 PHP 脚本中 Java 类方法的使用,接下来的几行代码使用 GregorianCalendar 对象的 add() 方法执行简单的日期添加 — 在本例中,将日历向后移 4 个月零 3 天 — 然后再通过调用 Java 对象方法输出修改后的日期和时间。
输出如下所示:
为了更好地了解如何通过 Zend Java Bridge 将对 Java 对象的请求从 PHP 环境传输到 Java 环境,我们来看看下面这个图,它描述了两种环境之间的信息流:
您可以访问的不仅仅是内置的 Java 对象。您还可以通过 Zend Java Bridge 从 PHP 脚本内部访问用户定义的 Java 类,只要这些类包括在 Java 类路径中。
要查看其运行情况,请按如下所示创建一个简单的 Java 类 /tmp/Menu.java:
public class Menu { private static String[] myArray = {"eggs", "hamburgers", "tomato soup", "chicken pot pie", "spaghetti bolognese", "ice cream", "chocolate chip cookies", "grilled sole"}; public static void main(String[] args) { Menu m = new Menu(); System.out.println(m.getMenu()); } public static String getMenu() { String menu = "Today's menu is: "; for (int i = 0; i < 3; i++) { int r = (int)(Math.random() * (myArray.length - 1)); menu += myArray[r]; if (i != 2) { menu += ", "; } } return menu; } }
过程一点也不复杂 — 该类公开一个 getMenu() 方法,这个方法从一个数组中随机选择食品项,然后将这些项返回给调用者。使用 Java 编译器保存并编译该类,如下所示:
shell> javac Menu.java
假设编译后的输出保存为 /tmp/Menu.class,下一步是将已编译类的位置添加到 Java 类路径。可以通过编辑相应的配置文件来修改 Zend Java Bridge 使用的类路径,配置文件为 /usr/local/zend/etc/watchdog-jb.ini 文件(Linux 平台)或 C:\Program Files\Zend\Zend Server\etc\java_bridge_server.ini(Windows 平台)。下面是一个类路径配置的示例:
zend_wd.env.java_daemon=CLASSPATH=.:/usr/local/zend/bin/javamw.jar:/tmp/Menu.class:
更改后,重启 Zend Server 使更改生效。然后,创建以下 PHP 脚本并将其保存到 Web 服务器文档根目录中:
<?php // initialize custom Java class and invoke class method $m = new Java("Menu"); echo $m->getMenu(); ?>
该脚本初始化一个自定义 Java 类的实例,然后调用该类的 getMenu() 方法。通过浏览器请求该脚本时,您会看到以下输出:
大多数情况下,Zend Java Bridge 会正确识别 Java 数据类型并将其转换为原生 PHP 结构。我们来看看下面对之前示例的修改,其中将 Menu.getMenu() 方法修改为返回一个数组,而非字符串:
public class Menu { private static String[] myArray = {"eggs", "hamburgers", "tomato soup", "chicken pot pie", "spaghetti bolognese", "ice cream", "chocolate chip cookies", "grilled sole"}; public static void main(String[] args) { Menu m = new Menu(); System.out.println(m.getMenu()); } public static String[] getMenu() { String[] menuArray; menuArray = new String[3]; for (int i = 0; i < 3; i++) { int r = (int)(Math.random() * (myArray.length - 1)); menuArray[i] = myArray[r]; } return menuArray; } }
更新 PHP 脚本以显示转储的返回值:
<?php // initialize custom Java class and invoke class method $m = new Java("Menu"); print_r($m->getMenu()); ?>
您将看到以下内容:
默认情况下,Zend Java Bridge 将 Java 对象转换为基元。不过,可以在 Zend Server 基于 Web 的控制面板中使用 zend_jbridge.use_java_objects 配置指令修改此行为,无需先将 Java 对象转换为基元,即可切换为“按原样”返回 Java 对象。
然而,值得重点注意的是,通过其他方式将数据从 PHP 传递到 Java 时,Zend Java Bridge 不会对您的对象和值进行动态数据分类。一般来说,如果您的输入参数没有正确映射到 Java 预期的数据类型,Zend Java Bridge 将抛出异常。为了避免此类异常,通常最好的做法是:在将数据从 PHP 传递到 Java 时显式指定数据类型。您将在 Zend Server 参考手册以及接下来的示例中看到有关如何执行该操作的详细示例。
如果出现错误,请记住您始终可以使用 Zend Server 的代码跟踪 (Monitor -> Code Tracing) 特性跟踪整个请求并确定错误的根源。服务器错误日志对故障排除和调试也非常有帮助,您可以通过基于 Web 的控制面板的 Monitor -> Logs 选项卡访问这些日志。
Zend Java Bridge 不但允许您访问自定义 Java 类,它还简化了对部署在 Java 应用服务器上的其他资源(如 Enterprise JavaBeans)的访问。可以使用 Java 命名和目录接口 (JNDI) 从 PHP 脚本访问这些 bean。
为了进行说明,假设 Oracle WebLogic Server 附带了多个可演示不同服务器功能的示例 bean。其中一个 bean 是 AccountHome bean,它提供了一个 API,用于创建银行账户、存取款以及检索当前账户余额。有了 JNDI,通过 Zend Java Bridge 从 PHP 脚本访问该 bean 就变得非常简单。下面的示例演示了如何访问 Oracle WebLogic Server 上部署的 EJB,以及如何使用它以交互方式创建新账户并执行各种账户操作:
<html> <head></head> <body> <h2>Bank Account Administration</h2> <?php if (isset($_POST['submit'])) { // set environment $config = array( 'java.naming.factory.initial' => 'weblogic.jndi.WLInitialContextFactory', 'java.naming.provider.url' => 't3://localhost:7001' ); // initialize Java class $context = new Java('javax.naming.InitialContext', $config); // look up bean using JNDI name $obj = $context->lookup('ejb20-beanManaged-AccountHome'); // assume input is properly filtered and validated $acc = (int) $_POST['acc']; $amt = (int) $_POST['amt']; $op = $_POST['op']; // check if requested account is present // if not, create and set opening balance of $0 try { $account = $obj->findByPrimaryKey($acc); $messages[] = 'Found account #' . $acc . ' with current balance: $' . $account->balance(); } catch (Exception $e) { $account = $obj->create($acc, 0); $messages[] = 'Account not present, creating new account #' . $acc . ' with opening balance: $0'; } // check requested operation // withdraw or deposit funds as required switch ($op) { case 'D': $account->deposit($amt); $messages[] = 'Added: $' . $amt; break; case 'W': $account->withdraw($amt); $messages[] = 'Subtracted: $' . $amt; break; } // get new account balance $messages[] = 'New account balance is: $' . $account->balance(); // print messages echo 'MESSAGES <br/>'; echo implode($messages, '<br/>'); } ?> <form method="post"> <select name="op"> <option value="D">Deposit</option> <option value="W">Withdraw</option> </select> amount: <input type="text" size="4" name="amt" /> to/from account #: <input type="text" size="10" name="acc" /> <input type="submit" name="submit" value="Go!" /> </form> </body> </html>
该脚本生成一个 Web 表单,要求用户输入账号以及要从该账户存取的金额。提交表单后,脚本通过指定 WebLogic 实现和提供程序 URL 来创建新的初始上下文。该上下文用于查找 bean 的 JNDI 名称,并将其作为对象返回。现在,可以将该对象的方法作为本地 PHP 方法进行访问,Zend Java Bridge 负责在 PHP 脚本和 Oracle WebLogic Server 之间传输参数和返回值。
该 bean 可用于 PHP 后,第一步是查找指定的账号,查看数据库中是否存在该账号。所有账户数据都在一个数据库中维护;当您激活 Oracle WebLogic Server 的 Samples 域时,该数据库将自动启动。该 bean 公开了一个 findByPrimaryKey() 方法,可用于查找账户并返回代表该账户的对象;如果该方法失败,则表示请求的账户不存在,那么脚本将使用 bean 的 create() 方法创建拥有指定账号的新账户并将其期初余额设为 0。
下一步是向账户添加资金或从账户删除资金,这取决于用户所选择的操作。可通过账户对象的 withdraw() 和 deposit() 方法完成此操作,这两个方法均由 PHP 脚本调用,将金额指定为一个方法参数。现在,可通过调用账户对象的 balance() 方法获得修改后的账户余额,并利用 PHP 的 echo 语句将结果输出到输出设备上。
该示例还演示了在通过 Zend Java Bridge 将数据从 PHP 脚本传递到 Java 对象的过程中如何处理数据类型。将该示例中的表单值提交给 PHP 脚本时,是将其作为字符串数据提交的。然而,bean 的 create()、withdraw() 和 deposit() 方法都要求整数值,所以需要在 bean 方法中使用账号和金额之前将其转换为整数。
下图说明了 Zend Java Bridge 如何处理请求:
在运行该示例之前,您必须确保 WebLogic 类文件,尤其是 weblogic.jar,出现在 Zend Java Bridge 配置文件指定的类路径中。如果没有包含在类路径中,则更改类路径并重启 Zend Server 使更改生效。
下面是一个初始表单的示例:
下面是执行部分账户操作后显示的数据的示例:
Zend Java Bridge 是一种非常健壮的实现,使用单个 JVM 实例减少开销,从而提高系统架构效率。这种集中实现还简化了调试,使开发人员可将其性能优化工作集中在单个点上。
另外,适用于任何 Java 应用程序的调优参数也同样适用于 Zend Java Bridge。例如,一个 JVM 实例未使用的内存会自动由 Java 的常规垃圾收集机制进行处理。有关更多信息,请参阅文档 Java 调优白皮书。
为了优化 Zend Java Bridge 的性能,Zend 团队建议监视 Zend Java Bridge 在不同使用情形下的性能,并根据并发负载分析来调高或调低工作线程的数量。如果有可以预测服务器上可能负载情况的用例,也可以主动配置 Zend Java Bridge 以应对高水平负载,从而确保性能达到预期水平。可以使用 zend.javamw.threads 变量在 Zend Java Bridge 配置文件中设置工作线程的数量。现在市场上有多种监视工具,例如 jconsole,可帮助您监视和管理 JVM。有关更多信息,请参阅文档监视和管理 Java 平台。
如本文中的示例演示,Zend Server 附带了高性能的松散耦合 JVM 实现、安全强健的 PHP 体系、内置缓存和作业排队,以及基于浏览器的管理。对于希望将 PHP 应用程序与现有 Oracle WebLogic Server 环境相集成的开发人员来说,Zend Server 所有这些特性使其成为一个不错的选择。
Vikram Vaswani 是 Melonfire(一家在开源工具和技术方面具有专业技能的咨询公司)的创始人和 CEO。他编写了四本有关 PHP 和 MySQL 的书,并且经常向 Zend Developer Zone、IBM DeveloperWorks 和其他社区网站投稿。
版权所有 Zend Technologies,2010。