Java Web 应用程序和 Spring 的
DispatcherServlet
结构为应用程序的 PHP 部分提供了一个极好的模型。与将这样出色的面向对象抽象抛出窗口所不同的是,您将快速地获得有助于模拟 Spring 的属性注入和 MVC 分派功能的一组类。您将编写一个负责读取多个属性文件的
Properties
类,一个使您从那些文件中向类实例注入属性的
Injectable
类,以及一个根据请求变化将其分派到正确的控制器类的
ActionDispatcher
类。
从多个属性文件中读取数值
要保持应用程序的持续性,您将使用一组属性文件和一个读取它们的类
Properties
<?php
class Properties {
private $props;
public function __construct ($propertiesFilePaths) {
$this->props = array();
foreach ($propertiesFilePaths as $path) {
$this->loadProperties($path);
}
}
private function loadProperties($propertiesFilePath) {
$lines = file($propertiesFilePath);
foreach ($lines as $line) {
$trimmed = trim($line);
if (strlen($trimmed) > 0 && strchr($trimmed, 0, 1) != "#") {
$split = split("=", trim($line));
$key = $split[0];
$value = $split[1];
$this->props[$key] = $value;
}
}
}
public function get($key) {
if (! isset($this->props[$key])) {
throw new Exception ("Properties: unknown key $key");
}
return $this->props[$key];
}
}
您为构造器提供一组属性的文件路径,构造器将他们逐个读入,并且集合到
loadProperties()
之中。采用一组属性的文件路径而不是一个属性的文件路径,使您可以分割那些依赖于环境的属性,例如:数据库连接值等。这样做使得您从一个环境向另一个环境复制代码变得更加容易,并且不需要重写那些值;在复制期间,您并不需要复制特定环境的文件,或者设置那些不能被您重写的许可。举例来说,这个应用程序将使用两个属性文件,它们分别是 app.properties 和 db.properties,其中 app.properties 包含诸如 Facebook API Key 和 Secret 这样的约束条件,而 db.properties 包含数据库登录信息。
使用 PHP 5 魔法方法提供类似 Spring 属性的注入
对于 PHP 5 魔法来说,为了提供一些更加类似 Spring 的东西,您可以从一个配置文件中将属性注入到一个对象之中。所有需要这样的注入属性的类,都需要继承
Injectable
基类。
class Injectable { protected $properties; private $prefix; public function Injectable($properties=null, $propPrefix=null) { $this->prefix = $propPrefix == null ? get_class($this) . '/' : $propPrefix; $this->setProperties ($properties); } public function setProperties($properties) { $this->properties = $properties; } public function __get($property) { return $this->getProperty($property); } public function getProperty($property) { return $this->properties->get($this->prefix . $property); } }
njectable
类的关键就是
__get()
方法,这是一个 PHP 5 魔法方法。魔法方法对于所有的 PHP 5 对象都适用,PHP 通过它们赋予对象特殊的核心行为;任何以
__
(两个下划线)开头的 PHP 变量和方法名称都将被作为魔法被处理。每当您访问一个对象的示例变量时,如果那个变量不能在对象中被找到,那么 PHP 就将调用对象的
__get()
方法,提供被请求的变量的名称作为一个字符串。
Injectable
类的
__get()
方法在
Properties
对象上查找该属性,将属性名称冠以唯一的前缀,默认情况下
Injectable
对象的类名称前面都有一条斜线(注意:使用点将导致 PHP 剖析器出现问题)。这样做使得从外部将属性注入到数据库连接类这样的对象之中变得很普通了,您马上就会看到这一点。这些被注入的属性能够作为正常的实例变量,被提交到一个目标对象中,就好像它们已经在代码中被设置了一样。
现在,您可以通过指定一个外部属性文件中的属性值,将任何您所需要的属性注入到一个对象之中,类似于 Spring 的 spring-servlet.xml 行为。这显然是 Spring 中一个非常小的功能,但是对于保持 Java 和 PHP 代码的相似和清晰的结构来说却是十分有用的。
编写 ActionDispatcher
编写 ActionDispatcher
为了维护同 Spring 的 MVC 架构的对称,您将像在 Spring 之中那样使用控制器类,每个请求对应一个,并且使用同样的抽象将 URI 键码映射到控制器的类名,从而 URI 能够为每一个请求指定一个键码,并且
ActionDispatcher
将创建和调用正确的控制器对象来响应它。
class ActionDispatcher extends Injectable {
public function __construct($properties) {
parent::__construct($properties);
}
public function dispatchKey($controllerKey) {
try {
$controllerName = $this->getProperty($controllerKey);
if (! $controllerName) {
throw new Exception ("ActionDispatcher.createController
-- unknown controller key: $controllerKey");
}
// load the controller class
require_once("$controllerName.php");
// create an instance of the action's controller
$controller = eval("return new $controllerName();");
// give it the application-wide properties file
$controller->setProperties($this->properties);
// let the response call back into this dispatcher to respond
$response = $controller->execute();
$response->respond($this);
} catch (Exception $e) {
echo $e;
exit;
}
}
}
ActionDispatcher
继承
Injectable
类,所以您能够注入属性(请求键码,稍后显示)。
dispatchKey()
在
Properties
对象中查找给定的键码(明确地,而不是通过
__get()
魔法方法,这是由于此时您已经将属性的名称变成一个字符串了),获得控制器类的名称。它使用
eval()
创建那个控制器的一个实例(您也可以使用 Zend 2 反映类来完成这一操作),在那个控制器上面设置
Properties
对象,并且调用控制器的
execute()
方法,获得一个响应对象。然后,它调用响应对象的
respond()
方法,在响应对象需要时传递它自己。这种来来往往的方法调用序列,使您能够根据需要添加响应类型,而无需修改
ActionDispatcher
—— 面向对象的编程在运转。
响应对象将决定如何呈递该响应
响应对象将决定如何呈递该响应
响应对象将决定如何呈递该响应:在 ActionDispatcher.php 为每个类型创建一个类:
class ModelAndView {
public function __construct($viewURI, $model=null) {
$this->viewURI = $viewURI;
$this->model = $model;
}
public function respond($dispatcher) {
$model = $this->model;
include($this->viewURI);
}
}
class ControllerForward {
public function __construct($controllerKey) {
$this->controllerKey = $controllerKey;
}
public function respond($dispatcher) {
$dispatcher->dispatchKey($this->controllerKey);
}
}
ModelAndView
类提供了和 Spring 的
ModelAndView
同样的行为。您的控制器的
execute
方法返回一个
ModelAndView
对象,其中包含视图的文件路径以及一个可选的模型对象。当
ActionDispatcher
调用其
respond()
方法的时候,
respond()
通过 PHP 处理器使用
include()
发送视图文件。被包括的视图访问调用代码的范围变量,所以您创建一个名为
$model
的变量,其中包括被提供的模型对象,所有视图都可以被找到(即使它为空)。
ControllerForward
类用于控制器处理某些操作,但是然后希望进一步控制另一个
ActionDispatcher
控制器。它的
respond()
方法使用新的控制器键码回调到
ActionDispatcher
之中,为那个控制器再次处理整个过程(但并不重新读取属性文件)。
由于所有的控制器都必须提供一个
execute()
方法,并且使注入属性变得容易,所以它们将继承
AbstractController
类。
AbstractController
类:
abstract class AbstractController extends Injectable {
public abstract function execute();
}
使用 index.php 处理请求分派
使用 index.php 处理请求分派
ActionDispatcher
已经准备好处理请求了,现在我们需要调用它。创建一个最小的 index.php 扮演分派系统入口点的角色。
上面的类分别 存放在 lib/ ,属性文件存放在 app/
<?php ini_set('include_path', '.;.\client;.\lib;.\app'); require_once('ActionDispatcher.php'); require_once('Properties.php'); $dispatcher = new ActionDispatcher (new Properties (array('conf\app.properties', 'conf\db.properties'))); $dispatcher->dispatchKey($_REQUEST['controller']); ?>
ini_set
调用设置包括路径(请注意在 Windows 中包括路径使用 ‘;’ 而在 Linux 系统中应当使用 ‘:’),从而其他的 PHP 文件就不再需要知道它们在哪里相互请求。然后,脚本创建一个
Properties
对象,传递所有在应用程序中使用过的属性文件路径,并且据此创建一个
ActionDispatcher
对象。然后,它调用
ActionDispatcher
的
dispatchKey()
方法,传递一个
controller
请求变量,其中包含要创建和调用的控制器键码。index.php 扮演了一个类似于 Java Web 应用程序中 web.xml 的角色,将所有控制传递到一个可配置的请求分派器上;在 Java 这一边是 Spring 的
DispatcherServlet
,而在 PHP 中则是
ActionDispatcher
。
创建一个从 Apache 到 Application Server 的
反转代理
(怎么创建反转代理)
为了使这两个服务器的使用同外部世界隔离开来,请修改 httpd.conf 文件,并且使用一个 .htaccess 文件来代理从 Apache 到 WebSphere Application Server 的您已经在 Java 中执行的那些请求。首先,在 httpd.conf (/usr/local/php/etc/httpd.conf)中,做出两处改变:
- 都是非注释的(也就是说,它们都不以 # 号开头)。
在 httpd.conf 中授权 mod_rewrite 和 mod_proxyLoadModule rewrite_module modules/mod_rewrite.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so
2.httpd.conf 中的任何位置添加
代理
<Directory "C:/Program Files/Zend/Apache2/htdocs/fb_stock_demo"> Options FollowSymLinks AllowOverride All </Directory> <Proxy *> Order deny,allow Allow from all </Proxy> ProxyPass /fb_stock_demo/java http://localhost:9083/facebook-stock-demo/action ProxyPassReverse /fb_stock_demo/java http://localhost:9083/facebook-stock-demo/action
使您能够通过
AllowOverride All
命令使用 .htaccess 对其进行控制,并且使用 mod_proxy 创建一个反转的代理,从而任何以 /fb_stock_demo/java 开头的请求都将被重定向到运行在 WebSphere Application Server 的 Spring
DispatcherServlet
之中(映射到 web.xml 中的 行为 URI)。为了实现这些改变,请重新启动 Apache。具体方法是:请打开命令行提示窗口,进入 Apache2 的 bin 目录(C:\Program Files\Zend\Apache2\bin),并且输入命令
httpd.exe -k stop
和
httpd.exe -k start
。不要使用重启命令;在 Windows 中如果您不能关闭 Apache 的话,它就将静静地死去。如果 Zend Core 将其作为一个 Windows 服务器来安装的话,那么您也许必须通过 Windows 服务管理器来把它停止下来,从而使其能够从命令提示行中被启动和停止,或者通过任务管理器将其终止。
使用 .htaccess 分派到 PHP 或者 Java 的请求
现在,您已经拥有了向 Java 应用程序路由请求的中心机制,您需要一种方法将特定的请求映射到正确的 PHP 或者 Java 控制器上。当 Apache 重启时,在 /var/www/html/htdocs/fb_stock_demo 中创建一个 .htaccess 文件,如下:。
RewriteEngine on RewriteCond %{QUERY_STRING} (.*) RewriteRule ^php/(.*) index.php?controller=$1&%1 [last] RewriteRule ^phpDbTest$ php/dbTest [next] RewriteRule ^stockList$ java/stockList [next]
第一航开始 URL 重写。
RewriteCond
/
RewriteRule
对为调用 index.php 创建了一个协议;fb_stock_demo 目录下的所有以 php/ 开头的请求都被发送到 index.php 中,将 php/ 后面的所有字符设置为一个
controller
请求变量,并且追加其后的任何其他的请求。举例来说,
http://localhost/fb_stock_demo/php/myController?var=3
将重写进
http://localhost/fb_stock_demo/index.php?controller=myController&var=3
之中。此后,您可以创建特定的
RewriteRules
将特定的请求发送到运行在 Zend Core 或者 IBM WebSphere 上面的特定控制器中。第二个
RewriteRule
发送 “phpDbTest” URL (比如
http://localhost/fb_stock_demo/phpDbTest
) 到
http://localhost/fb_stock_demo/php/phpDbTest
,并且再次根据第一条规则被重写进
http://localhost/fb_stock_demo/index.php?controller=dbTest
之中。第三个
RewriteRule
重写 “stockList” URI (比如
http://localhost/fb_stock_demo/stockList
) 到
http://localhost/fb_stock_demo/java/stockList
,在 httpd.conf 中的反转代理定义重写进
http://localhost:9083/facebook-stock-demo/action/stockList
,Spring 分配器发送到
StockListController
。请参见
参考资料
一节中的 mod_rewrite 文档以获得关于这一有用的 Apache 模块的更多详细信息。为了测试您在 .htaccess 中所创建的反转代理和 PHP/Java 分派菜单,请在您的浏览器中输入 http://localhost/fb_stock_demo/stockList,并且您将再次看到一组股票作为 XML 从 Java 控制器中被交付过来。现在,您拥有了一个从外部世界(也就是 Facebook)调用应用程序的完整机制,并且解除外部世界和您所使用的两种服务器技术之间的耦合性。
图 描绘了一个请求从用户的浏览器到一个 PHP 或者 Java 控制器的分派序列。