摘要:
Commons Chain提供了一个基于Java的框架和API来描述顺序的处理过程。现在这个在Javarta Commons项目下开发的框架正在最新的Struts发布版(在这指的是1.3版本)中接受考验。在这一部分,我将具体描述Struts如何使用Chain简化HTTP请求处理。
版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Bill Siggelkow; niuij(作者的Blog: http://blog.matrix.org.cn/page/niuij)
原文: http://www.onjava.com/pub/a/onjava/2005/03/02/commonchains2.html
中文: http://www.matrix.org.cn/resource/article/44/44088_Commons+Chain.html
关键字:Commons Chain
开始使用Commons Chain (第一部分)
就像我们在第一部分中讨论的那样,Commons Chain提供了一个基于Java的框架和API来描述顺序的处理过程。现在这个在Javarta Commons项目下开发的框架正在最新的Struts发布版(在这指的是1.3版本)中接受考验。在这一部分,我将具体描述Struts如何使用Chain简化HTTP请求处理。
Commons Chain允许你定义多组顺序的命令。每一组命令(也就是链)负责实现某种处理。链中每一个命令都是一个实现了Command接口的Java类。这个接口只包含了一个execute方法,方法有一个类型是Context的参数。execute方法返回true来声明请求的处理已完成,这时链的执行就会结束。如果返回false,处理将交给下个命令继续。
你可以使用Commons Chain的API或者XML文件定义链。使用XML文件可以提供最大的灵活性,因为这样做你可以在部署期修改链的定义。下面是我在第一篇文章中使用的链定义文件:
Struts使用Chain替换了原来传统的在RequestProcessor类中执行的HTTP请求处理。Struts的ActionServlet通过struts-config.xml决定使用哪个RequestProcessor。如果没有显式的指出,ActionServlet将使用org.apache.struts.action.RequestProcessor。他首先得到一个RequestProcessor的实例,调用实例的init方法,然后执行实例的process方法。
下面是RequestProcessor的init方法:
没有什么不可思议的地方——这个方法只是简单的清除了Action实例缓存,设置了几个实例变量。RequestProcessor的核心在它的process方法。这个方法里定义了处理请求和响应的顺序算法。
这个处理就是为Commons Chain定制的。(一些Struts的拥护者同时也是Chain的拥护者决不是巧合。)这个处理由一串顺序的步骤组成,这些步骤是一些名为processFoo的方法。它们的输入主要由request和response对象组成。其中一些方法会返回Struts的对象,如ActionMapping和ActionForm。如果这些对象为null就返回false,表示处理无法继续;另一些方法直接返回true或false,false用于表示请求已被处理而且无法继续执行。
Struts 1.3提供了一个新的请求处理类(ComposableRequestProcessor)来使用Commons Chain,这个类继承RequestProcessor,覆盖了init和process方法。ComposableRequestProcessor的init方法从链编目中载入请求处理链。默认情况下,这个编目名是struts,命令名是servlet-standard。
为了运行链(也就是运行命令)ComposableRequestProcessor覆盖了process方法:
处理请求的步骤在一个名为chain-config.xml的XML文件中定义,这个文件里定义了一组需要顺序执行的命令。(在这processMultipart方法是个例外。这个在ComposableRequestProcessor里实现的方法包装了内容类型是multipart/form-data的请求)。为了了解它是如何工作的,我们来仔细察看一下chain-config.xml的内容。首先,Struts使用了子链,Commons Chain中通过使用LookupCommand将整个链看成是一个命令。Struts使用了元素define使基于LookupCommand的子链定义更便捷。
<define name= "lookup" className="org.apache.commons.chain.generic.LookupCommand"/>
ComposableRequestProcessor执行了名为servlet-standard的链。这个链包含了3个命令:
servlet-exception 处理异常的Chain过滤器。过滤器是一种实现了postprocess方法的特殊命令。postprocess会在链中命令执行后调用(实际上是在那些在过滤器声明之后的命令执行后调用)。
process-action 处理请求并执行恰当的action的主要流程。
process-view 处理到视图的转向(例如JSP页面)。
process-action链定义了处理请求和调用你自己的action的命令组:
原始的RequestProcessor中的方法processFoo被实现了Command接口类重写了实现。在Struts里,每一个Command的实现都继承了一个抽象的基类。这个基类实现了Command的execute方法。这个execute方法调用具体的子类方法执行实际的操作。
我们来考察如何在HTTP请求中存取locale。首先,下面是原始的RequestProcessor中processLocale的实现:
新的用链处理locale的实现使用了两个类:AbstractSelectLocale和SelectLocale。抽象类AbstractSelectLocale实现了execute方法:
SelectLocale继承了AbstractSelectLocale并实现了抽象方法getLocale:
抽象类和它的实现类都充分使用了接收到的context对象。在Commons Chain里,Context对象作为链中的命令的共享存储空间。与RequestProcessor直接操作HTTP请求和响应不同的是,命令需要做一些额外的工作来操作他们。上面的抽象命令将context向下转型成ActionContext类型。ActionContext可以显式地操作Struts相关属性,例如消息资源(message resource)、正在执行的Action以及请求(request)和会话(session)的资源和服务。但是,ActionContext并不依赖Servlet API。命令的具体实现类进一步将context向下转型到ServletActionContext类型。ServletActionContext实现了ActionContext接口,并且包装了Commons Chain的ServletWebContext。ServletWebContext包含了一些servlet对象,例如HttpServletRequest、HttpServletResponse和ServletContext。
下面的类图展示了Struts是如何使用Chain提供的类和接口的:
图1.
Struts 1.3的开发人员尽力降低了和Servlet API的耦合。通过构建上面显示的使用Chain Context的结构,Struts将对Servlet API的依赖隔离到了最底层:具体的命令实现类。事实上,你会发现Struts的命令遵循了一个及其一致的模式:
1. 一个抽象命令实现了Command的execute方法,它只对ActionContext做处理。
2. 在execute方法中,这个抽象的基类调用一个自定义的抽象方法完成特定的servlet工作。
3. 具体的子类实现了自定义的抽象方法,将ActionContext向下转型到ServletActionContext,然后使用HttpServletRequest和HttpSession等执行特殊的工作。
4. 根据抽象方法返回的结果,抽象命令会返回false(链继续执行)或true(链停止执行)。
如果你想自定义ComposableRequestProcessor的行为就需要了解ComposableRequestProcessor和链配置。Commons Chain为开发人员提供了多种方法自定义Struts的请求处理。例如,假设你想自定义locale的处理,你可以使用下面任意一种技术:
• 你自己继承AbstractSelectLocale并实现getLocale方法。将chain-config.xml中的className改成你自己写的类名。
• 自己实现Command接口,然后替换原来的类。将chain-config.xml中的className改成你自己写的类名。
• 最后一种:使用LookupCommand,你可以将处理locale的单一命令替换成一个子链。
如果你曾经自定义过Struts的RequestProcessor,你可能覆盖过processPreprocess方法来执行自定义的请求处理。Struts 1.3通过Chain来提供类似的处理方法。process-action链中第一个命令的定义如下:
这个元素声明了一个子链作为process-action链的第一个命令。这的optional=true确保子链未定义时父链仍能继续执行。在Jakarta Struts Cookbook中,我展示了如何通过覆盖RequestProcessor的processPreprocess方法来检查用户是否登录。作为例子,我们来看在Struts Mail Reader示例应用中如何加入这个行为。下面这个命令检查了User对象是否已绑定到会话(session)上。参数checkUser用于指明是否要执行检查。
现在我们需要声明一个包含这个命令的链。但是我们把这个XML加在哪?有两种方法可以解决这个问题:第一种方法,你可以将这个链加入Struts提供的chain-config.xml文件中。这是最直接的办法,但是当你更新Struts的时候,你需要保证在新的配置文件中加入这些修改。另外一个更好的方法是为你的子链单独创建一个配置文件,然后告诉Struts将你的链定义文件和Struts自带的一起使用。要使用这个方法,首先你要在你应用的WEB-INF文件夹下面建立一个名为costom-chain-config.xml的文件,添加下面的链声明:
然后你需要修改struts-config.xml文件,让ActionServlet在载入Struts自己的链配置时载入你的链配置文件
当你访问Mail Reader的欢迎页面时,会显示图2所示的页面:
图2.
但是当你加上checkUser参数再次访问时,CheckUser命令终止了链的执行并返回它产生的响应,如图3所示:
图3.
如果比起创建一个链你更愿意替换命令,那你只需要简单地修改chain-config.xml文件。
在我为Struts和Chain加上包装前,让我们看一下Struts是如何使用Chain过滤器处理异常的。Struts定义了一个捕捉异常的命令作为servlet-standart链的第一个元素:
我们已经讨论过命令元素是如何使用属性的;但在这catalogName和exceptionCommand是两个新的属性。实际上它们没有什么不可思议的地方。它们在Chain中没有任何内在的意义。当Commons Chain解析链配置时(使用Commons Digester),任何在Chain中没有显式处理的属性都使用JavaBean的setter方法处理。换句话说,当Chain解析上面的命令时,它会获取一个ExceptionCatcher对象,然后象下面的代码那样设置JavaBean的属性:
类ExceptionCatcher实现了Filter。实现Filter的类必须实现下面两个方法:
类ExceptionCatcher的execute方法只是简单的重置了命令,将ActionContext中的当前异常设置成null,然后返回false(告诉链继续执行)。所有有趣的东西都在postprocess方法里。如果这个方法接收到的异常不是null,它会通过catalogName和exceptionCommand找到对应的命令(也可以是一个链)并执行。
servlet-exception链中配置了Struts的异常处理流程:
这个链中使用的类完成了和Struts 1.2的错误处理类同样的功能。ExceptionHandler是AbstractExceptionHandler的子类,这里的第一个命令会定位已经声明的Struts异常处理程序(如果存在的话)并执行它的execute方法。返回的ActionForward被存储在ActionContext中由通用的PerformForward命令处理。
我希望我已将Struts如何使用链解释清楚了。现在Commons Chain和Struts 1.3仍在不断变化中,所以上面说的可能会有变化。你最好下载最新的Struts的源码,仔细看看它是如何使用Chain的。我想你会发现这样实现是经过深思熟虑的;现在为Struts的请求处理加入自定义的行为要比以前更容易,更不受束缚。
资源
· Matrix-Java开发者社区: http://www.matrix.org.cn
·onjava.com: onjava.com
作者介绍:
Bill Siggelkow,自由顾问,擅长软件设计、软件开发、技术培训
作者:Bill Siggelkow; niuij(作者的Blog: http://blog.matrix.org.cn/page/niuij)
原文: http://www.onjava.com/pub/a/onjava/2005/03/02/commonchains2.html
中文: http://www.matrix.org.cn/resource/article/44/44088_Commons+Chain.html
关键字:Commons Chain
开始使用Commons Chain (第一部分)
就像我们在第一部分中讨论的那样,Commons Chain提供了一个基于Java的框架和API来描述顺序的处理过程。现在这个在Javarta Commons项目下开发的框架正在最新的Struts发布版(在这指的是1.3版本)中接受考验。在这一部分,我将具体描述Struts如何使用Chain简化HTTP请求处理。
Commons Chain允许你定义多组顺序的命令。每一组命令(也就是链)负责实现某种处理。链中每一个命令都是一个实现了Command接口的Java类。这个接口只包含了一个execute方法,方法有一个类型是Context的参数。execute方法返回true来声明请求的处理已完成,这时链的执行就会结束。如果返回false,处理将交给下个命令继续。
你可以使用Commons Chain的API或者XML文件定义链。使用XML文件可以提供最大的灵活性,因为这样做你可以在部署期修改链的定义。下面是我在第一篇文章中使用的链定义文件:
<catalog>
<chain name="sell-vehicle">
<command id="GetCustomerInfo"
className="com.jadecove.chain.sample.GetCustomerInfo"/>
<command id="TestDriveVehicle"
className="com.jadecove.chain.sample.TestDriveVehicle"/>
<command id="NegotiateSale"
className="com.jadecove.chain.sample.NegotiateSale"/>
<command id="ArrangeFinancing"
className="com.jadecove.chain.sample.ArrangeFinancing"/>
<command id="CloseSale"
className="com.jadecove.chain.sample.CloseSale"/>
</chain>
</catalog>
Struts使用Chain替换了原来传统的在RequestProcessor类中执行的HTTP请求处理。Struts的ActionServlet通过struts-config.xml决定使用哪个RequestProcessor。如果没有显式的指出,ActionServlet将使用org.apache.struts.action.RequestProcessor。他首先得到一个RequestProcessor的实例,调用实例的init方法,然后执行实例的process方法。
下面是RequestProcessor的init方法:
public void init(ActionServlet servlet,
ModuleConfig moduleConfig)
throws ServletException {
synchronized (actions) {
actions.clear();
}
this.servlet = servlet;
this.moduleConfig = moduleConfig;
}
没有什么不可思议的地方——这个方法只是简单的清除了Action实例缓存,设置了几个实例变量。RequestProcessor的核心在它的process方法。这个方法里定义了处理请求和响应的顺序算法。
public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Wrap multipart requests with a special wrapper
request = processMultipart(request);
// Identify the path component we will use to select a mapping
String path = processPath(request, response);
if (path == null) {
return;
}
// Select a Locale for the current user if requested
processLocale(request, response);
// Set the content type and no-caching headers if requested
processContent(request, response);
processNoCache(request, response);
// General purpose preprocessing hook
if (!processPreprocess(request, response)) {
return;
}
this.processCachedMessages(request, response);
// Identify the mapping for this request
ActionMapping mapping = processMapping(request, response, path);
if (mapping == null) {
return;
}
// Check for any role required to perform this action
if (!processRoles(request, response, mapping)) {
return;
}
// Process any ActionForm bean related to this request
ActionForm form = processActionForm(request, response, mapping);
processPopulate(request, response, form, mapping);
if (!processValidate(request, response, form, mapping)) {
return;
}
// Process a forward or include specified by this mapping
if (!processForward(request, response, mapping)) {
return;
}
if (!processInclude(request, response, mapping)) {
return;
}
// Create or acquire the Action instance to process this request
Action action = processActionCreate(request, response, mapping);
if (action == null) {
return;
}
// Call the Action instance itself
ActionForward forward =
processActionPerform(request, response,
action, form, mapping);
// Process the returned ActionForward instance
processForwardConfig(request, response, forward);
}
这个处理就是为Commons Chain定制的。(一些Struts的拥护者同时也是Chain的拥护者决不是巧合。)这个处理由一串顺序的步骤组成,这些步骤是一些名为processFoo的方法。它们的输入主要由request和response对象组成。其中一些方法会返回Struts的对象,如ActionMapping和ActionForm。如果这些对象为null就返回false,表示处理无法继续;另一些方法直接返回true或false,false用于表示请求已被处理而且无法继续执行。
Struts 1.3提供了一个新的请求处理类(ComposableRequestProcessor)来使用Commons Chain,这个类继承RequestProcessor,覆盖了init和process方法。ComposableRequestProcessor的init方法从链编目中载入请求处理链。默认情况下,这个编目名是struts,命令名是servlet-standard。
public void init(ActionServlet servlet,
ModuleConfig moduleConfig)
throws ServletException {
super.init(servlet, moduleConfig);
ControllerConfig controllerConfig =
moduleConfig.getControllerConfig();
String catalogName = controllerConfig.getCatalog();
catalog = CatalogFactory.getInstance().getCatalog(catalogName);
if (catalog == null) {
throw new ServletException("Cannot find catalog '" +
catalogName + "'");
}
String commandName = controllerConfig.getCommand();
command = catalog.getCommand(commandName);
if (command == null) {
throw new ServletException("Cannot find command '" +
commandName + "'");
}
}
为了运行链(也就是运行命令)ComposableRequestProcessor覆盖了process方法:
public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Wrap the request in the case of a multipart request
request = processMultipart(request);
// Create and populate a Context for this request
ActionContext context = contextInstance(request, response);
// Create and execute the command.
try {
command.execute(context);
} catch (Exception e) {
// Execute the exception processing chain??
throw new ServletException(e);
}
// Release the context.
context.release();
}
处理请求的步骤在一个名为chain-config.xml的XML文件中定义,这个文件里定义了一组需要顺序执行的命令。(在这processMultipart方法是个例外。这个在ComposableRequestProcessor里实现的方法包装了内容类型是multipart/form-data的请求)。为了了解它是如何工作的,我们来仔细察看一下chain-config.xml的内容。首先,Struts使用了子链,Commons Chain中通过使用LookupCommand将整个链看成是一个命令。Struts使用了元素define使基于LookupCommand的子链定义更便捷。
<define name= "lookup" className="org.apache.commons.chain.generic.LookupCommand"/>
ComposableRequestProcessor执行了名为servlet-standard的链。这个链包含了3个命令:
servlet-exception 处理异常的Chain过滤器。过滤器是一种实现了postprocess方法的特殊命令。postprocess会在链中命令执行后调用(实际上是在那些在过滤器声明之后的命令执行后调用)。
process-action 处理请求并执行恰当的action的主要流程。
process-view 处理到视图的转向(例如JSP页面)。
<chain name="servlet-standard">
<!-- Establish exception handling filter -->
<command
className="org.apache.struts.chain.commands.ExceptionCatcher"
catalogName="struts"
exceptionCommand="servlet-exception"/>
<lookup
catalogName="struts"
name="process-action"
optional="false"/>
<lookup
catalogName="struts"
name="process-view"
optional="false"/>
</chain>
process-action链定义了处理请求和调用你自己的action的命令组:
<chain name="process-action">
<lookup catalogName="struts" name="servlet-standard-preprocess"
optional="true"/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectLocale"
/>
<command className=
"org.apache.struts.chain.commands.servlet.RequestNoCache"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SetContentType"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.AuthorizeAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.CreateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.PopulateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.ValidateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectInput"
/>
<command className=
"org.apache.struts.chain.commands.ExecuteCommand"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectForward"
/>
<command className=
"org.apache.struts.chain.commands.SelectInclude"
/>
<command className=
"org.apache.struts.chain.commands.servlet.PerformInclude"
/>
<command className=
"org.apache.struts.chain.commands.servlet.CreateAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.ExecuteAction"
/>
</chain>
原始的RequestProcessor中的方法processFoo被实现了Command接口类重写了实现。在Struts里,每一个Command的实现都继承了一个抽象的基类。这个基类实现了Command的execute方法。这个execute方法调用具体的子类方法执行实际的操作。
我们来考察如何在HTTP请求中存取locale。首先,下面是原始的RequestProcessor中processLocale的实现:
protected void processLocale(HttpServletRequest request,
HttpServletResponse response) {
// Are we configured to select the Locale automatically?
if (!moduleConfig.getControllerConfig().getLocale()) {
return;
}
// Has a Locale already been selected?
HttpSession session = request.getSession();
if (session.getAttribute(Globals.LOCALE_KEY) != null) {
return;
}
// Use the Locale returned by the servlet container (if any)
Locale locale = request.getLocale();
if (locale != null) {
session.setAttribute(Globals.LOCALE_KEY, locale);
}
}
新的用链处理locale的实现使用了两个类:AbstractSelectLocale和SelectLocale。抽象类AbstractSelectLocale实现了execute方法:
public boolean execute(Context context) throws Exception {
ActionContext actionCtx = (ActionContext) context;
// Are we configured to select Locale automatically?
ModuleConfig moduleConfig = actionCtx.getModuleConfig();
if (!moduleConfig.getControllerConfig().getLocale()) {
return (false);
}
// Retrieve and cache appropriate Locale for this request
Locale locale = getLocale(actionCtx);
actionCtx.setLocale(locale);
return (false);
}
SelectLocale继承了AbstractSelectLocale并实现了抽象方法getLocale:
protected Locale getLocale(ActionContext context) {
ServletActionContext saContext = (ServletActionContext) context;
// Has a Locale already been selected?
HttpSession session = saContext.getRequest().getSession();
Locale locale = (Locale) session.getAttribute(Globals.LOCALE_KEY);
if (locale != null) {
return (locale);
}
// Select and cache the Locale to be used
locale = saContext.getRequest().getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
session.setAttribute(Globals.LOCALE_KEY, locale);
return (locale);
}
抽象类和它的实现类都充分使用了接收到的context对象。在Commons Chain里,Context对象作为链中的命令的共享存储空间。与RequestProcessor直接操作HTTP请求和响应不同的是,命令需要做一些额外的工作来操作他们。上面的抽象命令将context向下转型成ActionContext类型。ActionContext可以显式地操作Struts相关属性,例如消息资源(message resource)、正在执行的Action以及请求(request)和会话(session)的资源和服务。但是,ActionContext并不依赖Servlet API。命令的具体实现类进一步将context向下转型到ServletActionContext类型。ServletActionContext实现了ActionContext接口,并且包装了Commons Chain的ServletWebContext。ServletWebContext包含了一些servlet对象,例如HttpServletRequest、HttpServletResponse和ServletContext。
下面的类图展示了Struts是如何使用Chain提供的类和接口的:
图1.
Struts 1.3的开发人员尽力降低了和Servlet API的耦合。通过构建上面显示的使用Chain Context的结构,Struts将对Servlet API的依赖隔离到了最底层:具体的命令实现类。事实上,你会发现Struts的命令遵循了一个及其一致的模式:
1. 一个抽象命令实现了Command的execute方法,它只对ActionContext做处理。
2. 在execute方法中,这个抽象的基类调用一个自定义的抽象方法完成特定的servlet工作。
3. 具体的子类实现了自定义的抽象方法,将ActionContext向下转型到ServletActionContext,然后使用HttpServletRequest和HttpSession等执行特殊的工作。
4. 根据抽象方法返回的结果,抽象命令会返回false(链继续执行)或true(链停止执行)。
如果你想自定义ComposableRequestProcessor的行为就需要了解ComposableRequestProcessor和链配置。Commons Chain为开发人员提供了多种方法自定义Struts的请求处理。例如,假设你想自定义locale的处理,你可以使用下面任意一种技术:
• 你自己继承AbstractSelectLocale并实现getLocale方法。将chain-config.xml中的className改成你自己写的类名。
• 自己实现Command接口,然后替换原来的类。将chain-config.xml中的className改成你自己写的类名。
• 最后一种:使用LookupCommand,你可以将处理locale的单一命令替换成一个子链。
如果你曾经自定义过Struts的RequestProcessor,你可能覆盖过processPreprocess方法来执行自定义的请求处理。Struts 1.3通过Chain来提供类似的处理方法。process-action链中第一个命令的定义如下:
<!-- Look up optional preprocess command -->
<lookup catalogName="struts"
name="servlet-standard-preprocess"
optional="true"/>
这个元素声明了一个子链作为process-action链的第一个命令。这的optional=true确保子链未定义时父链仍能继续执行。在Jakarta Struts Cookbook中,我展示了如何通过覆盖RequestProcessor的processPreprocess方法来检查用户是否登录。作为例子,我们来看在Struts Mail Reader示例应用中如何加入这个行为。下面这个命令检查了User对象是否已绑定到会话(session)上。参数checkUser用于指明是否要执行检查。
package com.jadecove.chain.commands;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.struts.apps.mailreader.Constants;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
public class CheckUser implements Command {
public boolean execute(Context ctx) throws Exception {
ActionContext context = (ActionContext) ctx;
Object user = context.getSessionScope().get(Constants.USER_KEY);
if (user == null &&
context.getParameterMap().containsKey("checkUser")) {
HttpServletResponse response =
((ServletActionContext) ctx).getResponse();
response.sendError(403, "User not logged in.");
return true;
}
return false;
}
}
现在我们需要声明一个包含这个命令的链。但是我们把这个XML加在哪?有两种方法可以解决这个问题:第一种方法,你可以将这个链加入Struts提供的chain-config.xml文件中。这是最直接的办法,但是当你更新Struts的时候,你需要保证在新的配置文件中加入这些修改。另外一个更好的方法是为你的子链单独创建一个配置文件,然后告诉Struts将你的链定义文件和Struts自带的一起使用。要使用这个方法,首先你要在你应用的WEB-INF文件夹下面建立一个名为costom-chain-config.xml的文件,添加下面的链声明:
<?xml version="1.0" ?>
<catalog name="struts">
<chain name="servlet-standard-preprocess">
<command className="com.jadecove.chain.commands.CheckUser"/>
</chain>
</catalog>
然后你需要修改struts-config.xml文件,让ActionServlet在载入Struts自己的链配置时载入你的链配置文件
<!-- Action Servlet Configuration -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/struts-config.xml,
/WEB-INF/struts-config-registration.xml
</param-value>
</init-param>
<init-param>
<param-name>chainConfig</param-name>
<param-value>
/WEB-INF/chain-config.xml,
/WEB-INF/custom-chain-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
当你访问Mail Reader的欢迎页面时,会显示图2所示的页面:
图2.
但是当你加上checkUser参数再次访问时,CheckUser命令终止了链的执行并返回它产生的响应,如图3所示:
图3.
如果比起创建一个链你更愿意替换命令,那你只需要简单地修改chain-config.xml文件。
在我为Struts和Chain加上包装前,让我们看一下Struts是如何使用Chain过滤器处理异常的。Struts定义了一个捕捉异常的命令作为servlet-standart链的第一个元素:
<!-- Establish exception handling filter -->
<command className="org.apache.struts.chain.commands.ExceptionCatcher"
catalogName="struts"
exceptionCommand="servlet-exception"/>
我们已经讨论过命令元素是如何使用属性的;但在这catalogName和exceptionCommand是两个新的属性。实际上它们没有什么不可思议的地方。它们在Chain中没有任何内在的意义。当Commons Chain解析链配置时(使用Commons Digester),任何在Chain中没有显式处理的属性都使用JavaBean的setter方法处理。换句话说,当Chain解析上面的命令时,它会获取一个ExceptionCatcher对象,然后象下面的代码那样设置JavaBean的属性:
exceptionCatcher.setCatalogName("struts");
exceptionCatcher.setExceptionCommand("servlet-exception");
类ExceptionCatcher实现了Filter。实现Filter的类必须实现下面两个方法:
// (From the Command interface) Called when the Filter is first
// processed in sequence
public boolean execute(Context context);
// Called after a chain command throws an exception
// or the chain reaches its end
public boolean postprocess(Context context, Exception exception);
类ExceptionCatcher的execute方法只是简单的重置了命令,将ActionContext中的当前异常设置成null,然后返回false(告诉链继续执行)。所有有趣的东西都在postprocess方法里。如果这个方法接收到的异常不是null,它会通过catalogName和exceptionCommand找到对应的命令(也可以是一个链)并执行。
public boolean postprocess(Context context, Exception exception) {
// Do nothing if there was no exception thrown
if (exception == null) {
return (false);
}
// Store the exception in the context
ActionContext actionCtx = (ActionContext) context;
actionCtx.setException(exception);
// Execute the specified command
try {
Command command = lookupExceptionCommand();
if (command == null) {
throw new IllegalStateException
("Cannot find exceptionCommand '" +
exceptionCommand + "'");
}
command.execute(context);
} catch (Exception e) {
throw new IllegalStateException
("Exception chain threw exception");
}
return (true);
}
servlet-exception链中配置了Struts的异常处理流程:
<!-- ========== Servlet Exception Handler Chain ============= -->
<chain name="servlet-exception">
<!-- Execute the configured exception handler (if any) -->
<command className=
"org.apache.struts.chain.commands.servlet.ExceptionHandler"
/>
<!-- Follow the returned ForwardConfig (if any) -->
<command className=
"org.apache.struts.chain.commands.servlet.PerformForward"
/>
</chain>
这个链中使用的类完成了和Struts 1.2的错误处理类同样的功能。ExceptionHandler是AbstractExceptionHandler的子类,这里的第一个命令会定位已经声明的Struts异常处理程序(如果存在的话)并执行它的execute方法。返回的ActionForward被存储在ActionContext中由通用的PerformForward命令处理。
我希望我已将Struts如何使用链解释清楚了。现在Commons Chain和Struts 1.3仍在不断变化中,所以上面说的可能会有变化。你最好下载最新的Struts的源码,仔细看看它是如何使用Chain的。我想你会发现这样实现是经过深思熟虑的;现在为Struts的请求处理加入自定义的行为要比以前更容易,更不受束缚。
资源
· Matrix-Java开发者社区: http://www.matrix.org.cn
·onjava.com: onjava.com
作者介绍:
Bill Siggelkow,自由顾问,擅长软件设计、软件开发、技术培训
本页面地址:
→投票评分
<script src="/j/cookie.js" type="text/javascript"></script> <script src="/j/vote.js" type="text/javascript"></script>→用户评论列表
看起来有些webwork的感觉了,难怪他们要合并了.
#12522 评论作者: Marcolm 发表时间:2006-04-30 10:52
好,谢谢niuji大哥的译文。问题:chain的config可以按照module来划分吗?就是不同的module用不同的chainConfig?