|
With version 1.3 (now in SVN), Struts developers have completely rebuilt the RequestProcessor using the Chain-of-Command/Chain-of-Responsibility (CoR) pattern implemented in Apache Commons-Chain. This allows us to create cleaner, more efficient code. How? Developers now have much more flexibility in specifying what happens when an action URL is called (such as /do/testAct or testAct.do). You can download an entire sample application that uses Struts 1.3 dev and CoR on JRE 1.5, Tomcat 5.5.7 on Eclipse 3.1M4 (all included in download) at http://sourceforge.net/projects/infonoia/ So what's new and better in Struts 1.3? Previously, you were limited by having to specify a single action class with the "type" attribute. <action path="/testAct" type="com.mD.test.TestAct" name="testBean" scope="session" validate="false"> <forward name="Success" path="/WEB-INF/modules/test/inf/test.jsp"/> </action> In addition, that class had to extend from org.apache.struts.action.Action: With Struts 1.3, you can optionally include an entire *command chain* by specifying the "command" and "catalog" attributes. <action path="/testAct" command="testCmd" catalog="testCat" name="testBean" scope="session" validate="false"> <forward name="Success" path="/WEB-INF/modules/test/inf/test.jsp"/> </action> The classes (now there can be more than one!) to be executed in the testCmd chain are specified in a chain-config.xml, similar to struts-config.xml <catalog name="testCat">
<chain name="testCmd"> <command name="testCmd1" className="com.mD.test.NewCmd"/> <command name="testCmd2" className="com.mD.test.New2Cmd"/> </chain>
</catalog> Now the command class can be any arbitrary class, as long as it *implements* the Command interface (no more need to extend Action!). The command interface is very simple, has one method only public boolean execute(Context context) throws Exception; The simplest implementation of NewCmd would be: public class NewCmd implements Command{ public static Log log = LogFactory.getLog(NewCmd.class); public boolean execute(Context context) throws Exception{ log.debug("testCmd1 executed"); //do something return false; //return true if you want the chain to stop (unlikely in the Struts chain) } } The implementation of the Context interface can be any implementation of Map, with .put("key1","value1") and .get("key1") methods. It allows you to transport the state of your environment through the chain. By default, the new Struts RequestProcessor ("ComposableRequestProcessor") will instantiate a class named ServletWebContext that implements ActionContext that will have been prepopulated with request parameters, bean in scope etc. Now you can start mixing and matching command classes, and they will be executed in the order specified in the chain-config.xml. Let's say you want to log certain user actions, just add a command to the chain. public class SaveCmd implements Command{ public boolean execute(Context context) throws Exception{ MyBean b = (MyBean) context.getForm(); //or MyDao dao = b.getDao(); //depending on your bean implementation dao.save(); context.put("doLog", "true"); //optionally, just to show use of context return false; } } public class LogCmd implements Command{ public boolean execute(Context context) throws Exception{ if (context.get("doLog") != null) //optionally, typically we know that we want to log ;//code to log to DB here return false; } } You can also add entire subchains to a chain, as in <chain name="testCmd"> <command name="testCmd1" className="com.mD.test.NewCmd"/> <chain name="subCmd"/> </chain>
Using the same approach, Struts developers have rewritten the Struts request processor and broken the entire request processing *chain* into individual commands. If you want to tweak that chain you can replace it with your own, with an init-parameter named chainConfig of your action servlet in web.xml: <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/config/struts-config.xml</param-value> </init-param> <init-param> <param-name>chainConfig</param-name> <param-value>/WEB-INF/config/chain-config.xml, /WEB-INF/modules/test/inf/chainTest.xml</param-value> </init-param> ...
This is also where you register the chains you created yourself. We are fans of grouping *related* actions in one action class, one thing that the DispatchAction (org.apache.struts.action.DispatchAction) allows. For example, we consider all CRUD actions on a bean as related actions. So what we did is create a BaseCmd class that works similiarly to DispatchAction; it dispatches from execute(Context context) to onSaveExec(Context context), onDeleteExec(Context context), onInsertExec(Context context) etc. The appropriate method is called with /do/contentAct?Dispatch=save, /do/contentAt?Dispatch=delete&ID=5 etc. That way we have handler methods that come very similar to swing event handlers. Those methods would typically return the name of an action forward. We wanted to add some convenience methods to the struts-generated ActionContext, like getIDParameter(), so our handler command to display a single record could be written as follows: public String onDisplayExec(ActContext context) throws Exception{ MyBean b = (MyBean) context.getBean(); MyDao dao = b.getDao(); dao.query(context.getIDParameter()); //short for context.getRequest().getParameter("ID"); return "Success"; } and have it called (e.g. from a DisplayTag list) with /do/contentAct?Dispatch=Display&ID=5. As usual in Struts, the view JSP will pull the populated data from the bean in scope. If you have common interfaces on your beans and DAOs, you can even put that method in BaseCmd and reuse it for *anything* that needs to be queried by ID. To be able to add convenience methods to context, we created our own class "ActContext" that extends ServletWebContext. To have Struts create our ActContext, we created our own ActRequestProcessor that extends ComposableRequestProcessor, and plugged it in in struts-config.xml with: <controller processorClass="com.mD.base.ActRequestProcessor" /> You can download the sample application that uses Struts 1.3 dev on JRE1.5, Tomcat5.5.7 on Eclipse 3.1M4 (all included in download) at http://sourceforge.net/projects/infonoia/ The application also uses Tiles, JSTL, StrutsMenu etc. etc. Enjoy, Wolfgang Gehner www.infonoia.com The author of this article is co-author of the book "Struts Best Practices" published in German (2004) and French (Feb 2005). Further reading: http://jakarta.apache.org/commons/chain/ http://www.infonoia.com/en/publications.jsp
|