http://www.infonoia.com/en/content.jsp?d=inf.02.03

Better Code With Struts 1.3#

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 contextthrows 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 contextthrows Exception{
        MyBean b = (MyBeancontext.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 contextthrows 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 contextthrows Exception{
    MyBean b = (MyBeancontext.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" /> 

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor