開始使用Commons Chain (第二部分) 開始使用Commons Chain (第一部分)就像我們?cè)诘谝徊糠种杏懻摰哪菢樱珻ommons Chain提供了一個(gè)基于Java的框架和API來描述順序的處理過程?,F(xiàn)在這個(gè)在Javarta Commons項(xiàng)目下開發(fā)的框架正在最新的Struts發(fā)布版(在這指的是1.3版本)中接受考驗(yàn)。在這一部分,我將具體描述Struts如何使用Chain簡(jiǎn)化HTTP請(qǐng)求處理。 Commons Chain允許你定義多組順序的命令。每一組命令(也就是鏈)負(fù)責(zé)實(shí)現(xiàn)某種處理。鏈中每一個(gè)命令都是一個(gè)實(shí)現(xiàn)了Command接口的Java類。這個(gè)接口只包含了一個(gè)execute方法,方法有一個(gè)類型是Context的參數(shù)。execute方法返回true來聲明請(qǐng)求的處理已完成,這時(shí)鏈的執(zhí)行就會(huì)結(jié)束。如果返回false,處理將交給下個(gè)命令繼續(xù)。 你可以使用Commons Chain的API或者XML文件定義鏈。使用XML文件可以提供最大的靈活性,因?yàn)檫@樣做你可以在部署期修改鏈的定義。下面是我在第一篇文章中使用的鏈定義文件:
<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替換了原來傳統(tǒng)的在RequestProcessor類中執(zhí)行的HTTP請(qǐng)求處理。Struts的ActionServlet通過struts-config.xml決定使用哪個(gè)RequestProcessor。如果沒有顯式的指出,ActionServlet將使用org.apache.struts.action.RequestProcessor。他首先得到一個(gè)RequestProcessor的實(shí)例,調(diào)用實(shí)例的init方法,然后執(zhí)行實(shí)例的process方法。
下面是RequestProcessor的init方法:
public void init(ActionServlet servlet, ModuleConfig moduleConfig) throws ServletException {
synchronized (actions) { actions.clear(); } this.servlet = servlet; this.moduleConfig = moduleConfig; }
沒有什么不可思議的地方——這個(gè)方法只是簡(jiǎn)單的清除了Action實(shí)例緩存,設(shè)置了幾個(gè)實(shí)例變量。RequestProcessor的核心在它的process方法。這個(gè)方法里定義了處理請(qǐng)求和響應(yīng)的順序算法。
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);
}
這個(gè)處理就是為Commons Chain定制的。(一些Struts的擁護(hù)者同時(shí)也是Chain的擁護(hù)者決不是巧合。)這個(gè)處理由一串順序的步驟組成,這些步驟是一些名為processFoo的方法。它們的輸入主要由request和response對(duì)象組成。其中一些方法會(huì)返回Struts的對(duì)象,如ActionMapping和ActionForm。如果這些對(duì)象為null就返回false,表示處理無法繼續(xù);另一些方法直接返回true或false,false用于表示請(qǐng)求已被處理而且無法繼續(xù)執(zhí)行。
Struts 1.3提供了一個(gè)新的請(qǐng)求處理類(ComposableRequestProcessor)來使用Commons Chain,這個(gè)類繼承RequestProcessor,覆蓋了init和process方法。ComposableRequestProcessor的init方法從鏈編目中載入請(qǐng)求處理鏈。默認(rèn)情況下,這個(gè)編目名是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 + "‘"); } }
為了運(yùn)行鏈(也就是運(yùn)行命令)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(); }
處理請(qǐng)求的步驟在一個(gè)名為chain-config.xml的XML文件中定義,這個(gè)文件里定義了一組需要順序執(zhí)行的命令。(在這processMultipart方法是個(gè)例外。這個(gè)在ComposableRequestProcessor里實(shí)現(xiàn)的方法包裝了內(nèi)容類型是multipart/form-data的請(qǐng)求)。為了了解它是如何工作的,我們來仔細(xì)察看一下chain-config.xml的內(nèi)容。首先,Struts使用了子鏈,Commons Chain中通過使用LookupCommand將整個(gè)鏈看成是一個(gè)命令。Struts使用了元素define使基于LookupCommand的子鏈定義更便捷。
<define name= "lookup" className="org.apache.commons.chain.generic.LookupCommand"/> ComposableRequestProcessor執(zhí)行了名為servlet-standard的鏈。這個(gè)鏈包含了3個(gè)命令:
servlet-exception 處理異常的Chain過濾器。過濾器是一種實(shí)現(xiàn)了postprocess方法的特殊命令。postprocess會(huì)在鏈中命令執(zhí)行后調(diào)用(實(shí)際上是在那些在過濾器聲明之后的命令執(zhí)行后調(diào)用)。 process-action 處理請(qǐng)求并執(zhí)行恰當(dāng)?shù)腶ction的主要流程。 process-view 處理到視圖的轉(zhuǎn)向(例如JSP頁(yè)面)。
<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鏈定義了處理請(qǐng)求和調(diào)用你自己的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被實(shí)現(xiàn)了Command接口類重寫了實(shí)現(xiàn)。在Struts里,每一個(gè)Command的實(shí)現(xiàn)都繼承了一個(gè)抽象的基類。這個(gè)基類實(shí)現(xiàn)了Command的execute方法。這個(gè)execute方法調(diào)用具體的子類方法執(zhí)行實(shí)際的操作。 我們來考察如何在HTTP請(qǐng)求中存取locale。首先,下面是原始的RequestProcessor中processLocale的實(shí)現(xiàn):
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的實(shí)現(xiàn)使用了兩個(gè)類:AbstractSelectLocale和SelectLocale。抽象類AbstractSelectLocale實(shí)現(xiàn)了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并實(shí)現(xiàn)了抽象方法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);
}
抽象類和它的實(shí)現(xiàn)類都充分使用了接收到的context對(duì)象。在Commons Chain里,Context對(duì)象作為鏈中的命令的共享存儲(chǔ)空間。與RequestProcessor直接操作HTTP請(qǐng)求和響應(yīng)不同的是,命令需要做一些額外的工作來操作他們。上面的抽象命令將context向下轉(zhuǎn)型成ActionContext類型。ActionContext可以顯式地操作Struts相關(guān)屬性,例如消息資源(message resource)、正在執(zhí)行的Action以及請(qǐng)求(request)和會(huì)話(session)的資源和服務(wù)。但是,ActionContext并不依賴Servlet API。命令的具體實(shí)現(xiàn)類進(jìn)一步將context向下轉(zhuǎn)型到ServletActionContext類型。ServletActionContext實(shí)現(xiàn)了ActionContext接口,并且包裝了Commons Chain的ServletWebContext。ServletWebContext包含了一些servlet對(duì)象,例如HttpServletRequest、HttpServletResponse和ServletContext。
下面的類圖展示了Struts是如何使用Chain提供的類和接口的:
 圖1.
Struts 1.3的開發(fā)人員盡力降低了和Servlet API的耦合。通過構(gòu)建上面顯示的使用Chain Context的結(jié)構(gòu),Struts將對(duì)Servlet API的依賴隔離到了最底層:具體的命令實(shí)現(xiàn)類。事實(shí)上,你會(huì)發(fā)現(xiàn)Struts的命令遵循了一個(gè)及其一致的模式: 1. 一個(gè)抽象命令實(shí)現(xiàn)了Command的execute方法,它只對(duì)ActionContext做處理。 2. 在execute方法中,這個(gè)抽象的基類調(diào)用一個(gè)自定義的抽象方法完成特定的servlet工作。 3. 具體的子類實(shí)現(xiàn)了自定義的抽象方法,將ActionContext向下轉(zhuǎn)型到ServletActionContext,然后使用HttpServletRequest和HttpSession等執(zhí)行特殊的工作。 4. 根據(jù)抽象方法返回的結(jié)果,抽象命令會(huì)返回false(鏈繼續(xù)執(zhí)行)或true(鏈停止執(zhí)行)。
如果你想自定義ComposableRequestProcessor的行為就需要了解ComposableRequestProcessor和鏈配置。Commons Chain為開發(fā)人員提供了多種方法自定義Struts的請(qǐng)求處理。例如,假設(shè)你想自定義locale的處理,你可以使用下面任意一種技術(shù): • 你自己繼承AbstractSelectLocale并實(shí)現(xiàn)getLocale方法。將chain-config.xml中的className改成你自己寫的類名。 • 自己實(shí)現(xiàn)Command接口,然后替換原來的類。將chain-config.xml中的className改成你自己寫的類名。 • 最后一種:使用LookupCommand,你可以將處理locale的單一命令替換成一個(gè)子鏈。
如果你曾經(jīng)自定義過Struts的RequestProcessor,你可能覆蓋過processPreprocess方法來執(zhí)行自定義的請(qǐng)求處理。Struts 1.3通過Chain來提供類似的處理方法。process-action鏈中第一個(gè)命令的定義如下:
<!-- Look up optional preprocess command --> <lookup catalogName="struts" name="servlet-standard-preprocess" optional="true"/>
這個(gè)元素聲明了一個(gè)子鏈作為process-action鏈的第一個(gè)命令。這的optional=true確保子鏈未定義時(shí)父鏈仍能繼續(xù)執(zhí)行。在Jakarta Struts Cookbook中,我展示了如何通過覆蓋RequestProcessor的processPreprocess方法來檢查用戶是否登錄。作為例子,我們來看在Struts Mail Reader示例應(yīng)用中如何加入這個(gè)行為。下面這個(gè)命令檢查了User對(duì)象是否已綁定到會(huì)話(session)上。參數(shù)checkUser用于指明是否要執(zhí)行檢查。
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; }
}
現(xiàn)在我們需要聲明一個(gè)包含這個(gè)命令的鏈。但是我們把這個(gè)XML加在哪?有兩種方法可以解決這個(gè)問題:第一種方法,你可以將這個(gè)鏈加入Struts提供的chain-config.xml文件中。這是最直接的辦法,但是當(dāng)你更新Struts的時(shí)候,你需要保證在新的配置文件中加入這些修改。另外一個(gè)更好的方法是為你的子鏈單獨(dú)創(chuàng)建一個(gè)配置文件,然后告訴Struts將你的鏈定義文件和Struts自帶的一起使用。要使用這個(gè)方法,首先你要在你應(yīng)用的WEB-INF文件夾下面建立一個(gè)名為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自己的鏈配置時(shí)載入你的鏈配置文件
<!-- 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>
當(dāng)你訪問Mail Reader的歡迎頁(yè)面時(shí),會(huì)顯示圖2所示的頁(yè)面:
 圖2.
但是當(dāng)你加上checkUser參數(shù)再次訪問時(shí),CheckUser命令終止了鏈的執(zhí)行并返回它產(chǎn)生的響應(yīng),如圖3所示:
 圖3.
如果比起創(chuàng)建一個(gè)鏈你更愿意替換命令,那你只需要簡(jiǎn)單地修改chain-config.xml文件。 在我為Struts和Chain加上包裝前,讓我們看一下Struts是如何使用Chain過濾器處理異常的。Struts定義了一個(gè)捕捉異常的命令作為servlet-standart鏈的第一個(gè)元素:
<!-- Establish exception handling filter --> <command className="org.apache.struts.chain.commands.ExceptionCatcher" catalogName="struts" exceptionCommand="servlet-exception"/>
我們已經(jīng)討論過命令元素是如何使用屬性的;但在這catalogName和exceptionCommand是兩個(gè)新的屬性。實(shí)際上它們沒有什么不可思議的地方。它們?cè)贑hain中沒有任何內(nèi)在的意義。當(dāng)Commons Chain解析鏈配置時(shí)(使用Commons Digester),任何在Chain中沒有顯式處理的屬性都使用JavaBean的setter方法處理。換句話說,當(dāng)Chain解析上面的命令時(shí),它會(huì)獲取一個(gè)ExceptionCatcher對(duì)象,然后象下面的代碼那樣設(shè)置JavaBean的屬性:
exceptionCatcher.setCatalogName("struts"); exceptionCatcher.setExceptionCommand("servlet-exception");
類ExceptionCatcher實(shí)現(xiàn)了Filter。實(shí)現(xiàn)Filter的類必須實(shí)現(xiàn)下面兩個(gè)方法:
// (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方法只是簡(jiǎn)單的重置了命令,將ActionContext中的當(dāng)前異常設(shè)置成null,然后返回false(告訴鏈繼續(xù)執(zhí)行)。所有有趣的東西都在postprocess方法里。如果這個(gè)方法接收到的異常不是null,它會(huì)通過catalogName和exceptionCommand找到對(duì)應(yīng)的命令(也可以是一個(gè)鏈)并執(zhí)行。
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>
這個(gè)鏈中使用的類完成了和Struts 1.2的錯(cuò)誤處理類同樣的功能。ExceptionHandler是AbstractExceptionHandler的子類,這里的第一個(gè)命令會(huì)定位已經(jīng)聲明的Struts異常處理程序(如果存在的話)并執(zhí)行它的execute方法。返回的ActionForward被存儲(chǔ)在ActionContext中由通用的PerformForward命令處理。
我希望我已將Struts如何使用鏈解釋清楚了?,F(xiàn)在Commons Chain和Struts 1.3仍在不斷變化中,所以上面說的可能會(huì)有變化。你最好下載最新的Struts的源碼,仔細(xì)看看它是如何使用Chain的。我想你會(huì)發(fā)現(xiàn)這樣實(shí)現(xiàn)是經(jīng)過深思熟慮的;現(xiàn)在為Struts的請(qǐng)求處理加入自定義的行為要比以前更容易,更不受束縛。
|