Wednesday, September 23, 2009

Struts 2 in review

Much of what I learned of Struts 2 is based on the book Struts 2 in Action.  The book was well written, well organized, and pretty easy to read over the weekend despite being just shy of 400 pages.  I have to say I'm pretty impressed with the Manning books.  I have one other Manning book that I'm reading, Spring in Action, for my analysis of Spring MVC and it's also of very high quality.

Anyway, after reading the book, I experimented with building a few apps where I could demo the framework's features.  As we will see, some of them make customization easier, some of them help separate model and view concerns, and yet others left me scratching my head why they chose to go that direction.





The flow of a Struts 2 request has not changed much since Struts Classic (aka Struts 1) but several new architectural components have been added to the mix and the ActionForm has been removed.  When a request is processed by the Struts 2 controller, it first reads your configuration and attempts to associate it with an Action.  But rather than invoking the Action right away, it processes the request through something called an Interceptor stack.

Interceptors

The Interceptor stack is the Struts team's response to complaints about Struts Classic's rigid request processing nature.  Previously, developers had very little control over how Struts processed a request.  Interceptors, on the other hand, give developers control over the behavior of the controller, allowing you to augment Struts' current Interceptor stack with business activities that cut across many actions (for example, authentication).  Interceptors are configured in the struts.xml and are arranged in a sequence representing a call stack.  During request processing, the first Interceptor in the sequence is called which performs some pre-processing then calls the next interceptor in the stack.  The last Interceptor in the sequence invokes the Action.  On the return trip back up the call stack (once the Action has returned) the Interceptors get an opportunity to perform some post-processing.  Interceptors also may interrupt processing through the stack and return early, not invoking the Action at all but redirecting the user to a different page.  This would happen for example if validation or authentication failed.

Admittedly, I did have some issues defining my own custom Interceptor properly.  Declaring the Interceptor and adding it to the stack feels a little clumsy, and it took me a few iterations of fumbling with the config before I got it working.

Configuration

This brings me to my next topic... configuration.  The configuration file in Struts 2 is organized similarly to Struts 1, but now your Actions are organized into packages.  A package is merely a namespace for your Actions.  Common Interceptors and Results can be defined within the namespace and applied to all Actions within the namespace.  Here is what a package configuration looks like (using the Interceptors I configured in the last section).
    <package name="secure" namespace="/secure" extends="struts-default">
        <interceptors>
            <interceptor name="authentication" class="mike.AuthenticationInterceptor" />
           
>
                <interceptor-ref name="authentication" />
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="secureStack" />

       
<global-results>
           
<result name="login" type="redirect">/login.jspx</result>
        </global-results>

       
<action name="info" class="mike.InfoAction">
            <result name="success">info.jspx</result>
        </action>
        <action name="logout" class="mike.LogoutAction">
            <result name="success" type="redirect">/login.jspx</result>
        </action>
    </package>
It is also now possible to use annotations to mark up all this information in your Actions directly, rather than put the information into a centralized configuration file.  However, I don't see very much of a benefit as it's now just dispersed configuration.  The only thing available in the way of convention is mapping action paths to action classes.  Maybe others might find this useful, but personally I just do not see a major benefit in using the annotations here.

Actions

As I mentioned earlier, Struts 2 has removed the ActionForm.  The ActionForm was generally considered an unnecessary inconvenience as developers were required to create separate beans to represent page components, but those beans were forever tied to the Struts framework by extension.  The execute() method of the Action was given the ActionForm and it was up to the developer to cast it to the correct type.  Furthermore, validation logic was wired into the ActionForm not the Action, which made integration with backend services (typically Spring-injected) more difficult.  But ultimately the ActionForm often lent itself to anti-patterns when not designed on a per-page basis, resulting in large and unwieldy ActionForms permanently stored in session scope.

In Struts 2, request parameters are instead saved directly to properties of the Action or to model objects accessible from the Action.  (Technically, this is happening against the Value Stack, but we'll get to that later...)  If you still prefer to model your pages with separate backing beans (and there may be many reasons why you'd want to) then you are welcome to do so but Struts no longer enforces it.  Validation methods are also tied to the Action now, making it easier to access Spring-injected services.  Actions, in fact, don't even need to implement the Action interface, but there are some reasons why you'd want to anyway.  (convention over configuration being one such reason)

A very simple Action looks as follows:

public class LogoutAction extends ActionSupport
    implements SessionAware
{
    private Map session;

    public void setSession(Map session)
    {
        this.session = session;
    }

    public String execute()
    {
        session.put("user", null);
        return Results.LOGIN.getValue();
    }
}

Results

When the Action finishes executing, it returns a string representing the next view component to load.  In Struts Classic these were called ActionForwards.  In Struts 2 they are called Results, but they also represent something more than just the location of the next page.  Results are full-fledged components in the Struts 2 architecture.  The default result type works with JSP output, but the framework provides other results types to handle non-JSP content, for example Freemarker, Velocity, and redirects.  You are also free to write your own Result to handle output such as JSON output, XML, or XSLT.  This makes Results one of the most important components in my view as it ensures view-specific logic stays out of the Action.

Thus far, I have no major grievances with the Struts 2 framework.  I think Interceptors and Results were a clean design and they help separate the Action from cross-cutting concerns and view-specific logic.  They do add another layer of complexity to the architecture, but the default configuration makes enough sense so that if you don't need to do anything fancy with them then they won't get in your way.  But where I have some major differences with Struts 2 is with the view layer and the Value Stack.

The Tags

Struts 2 has consolidated a lot of the tags from Struts Classic.  While there were previously four tag libraries with tons of tags, there is now one tag library with a much shorter list.  The advantage here is that there is less to learn.  The taglib includes general purpose tags (e.g. retrieving properties, manipulating the value stack, conditional and iterative logic) as well as UI component tags.

The UI component tags are "themed", requiring you to put a s:head tag in the page header to set up styles for the rest of the page.  This of course means that the tags do more than just output the component, they attempt to provide formatting around the component as well.  A bold effort on the part of Struts 2, but there are several problems here.  First of all, the default theme outputs tables around your input elements and outputs the tags in XHTML, both of which I have problems with.  Now, unless you've lived in a hole the past five years or so, tables should not be used for input forms in modern web sites.  This is how it was done in the 90's and the first half of this decade.  Nowadays, tables should only be used for tabular data (and even then this is debatable).  If you're not using divs and styles then you have a bit of catching up to do.  Second, XHTML has not become the "next version" of HTML we all though it would be 10 years ago.  IE support for it is still broken, and XHTML renderers are often unforgiving about syntax mistakes.

Having said all that, Struts does offer other themes which will add div's around the tags for you.  Even still, I'm a little bit hesitant to use this over the "basic" theme (which supplies no formatting).  The problem I have here is that I believe that it's not the responsibility of the framework to format your HTML.  There are many evolutions taking place in this space as well, including HTML5.  And although it will take some time to see how these new technologies flush themselves out, my concern is that the speed of these developments will outpace the framework.  To Struts' credit, it allows you to create your own themes if you're not content with any of the built-in ones.  But why require that at all?  If you want something to output HTML for you, why not let the tools that are good at building your HTML already do that work for you?

Another issue I have with the tags is Struts 2's deviance from JSTL tags.  For example, there are s:if, s:iterate, and  s:property.  JSTL has tags that do the same thing: c:if and c:choose perform conditional logic, c:foreach iterates over collections, and c:out (or even an EL expression without tag) will output a property value.  Standard conformance is a good thing, especially when it's widely in use.  Much of the reason for the duplication I believe is because of two other technologies employed by the framework: OGNL and the Value Stack.

OGNL

In order to resolve properties, Struts employs a language called OGNL - Object Graph Navigation Language.  You can pronounce it "oganal" for short.  OGNL expressions evaluate relative to the Value Stack and operate only within the context of a Struts tag.  So if you want to access property myProp on your Action, you would use <s:property value="%{myProp}" /> or in most cases you can just specify the property name without the braces, as in <s:property value="myProp" />.  OGNL can do some pretty neat things.  For example, you can specify a list inline as you might want to do for select boxes or check boxes, though in doing so you have to be careful not to inject business logic into your view.

OGNL does seem like a useful and expressive language, but again, as with JSTL, there is a considerable deviation from the standard here.  JSP developers will already be familiar with EL, and will take some time getting use to the %{} syntax instead of the familiar ${}.  Granted the expressions within the braces are not too different from EL, but there are some differences.  Furthermore, the expressions can only be used within Struts tags, and not all attributes accept OGNL by default.  In order words, although you can shorten your OGNL expressions in most cases to avoid the use of curly braces it's not immediately clear, without referencing the documentation, if an attribute is allowed to be written as simply myProp or must be written with the curly braces as in %{myProp}.  Overall, the benefit you get from the selection of OGNL seems very minimal over conformance to a common standard.  If anything the Value Stack could be exposed as a page variable and incorporated into the Struts component tags with EL expressions.

Now to Struts' credit the view layer was not designed with only JSP in mind.  There are other view technologies that Struts considered including Freemarker and Velocity.  OGNL therefore helps satisfy their requirement that Struts remains view agnostic.  But it was done so with the risk of making JSP developers unhappy and ultimately abandoning the framework.

The Value Stack

I had mentioned the Value Stack a few times but did not say much about it.  The Value Stack is essentially a "virtual" bean containing request parameters, the Action, and optionally any model objects you're working with.  When you use OGNL to ask the ValueStack to get a property, it attempts to retrieve its value from the top-most object on the stack.  If the it has no accessor for that property then the ValueStack will check the next object in the stack, and so on.   What's important here is that it's happening per property.  So, to demonstrate how this works, let's consider the following bit of code: 
<s:property name="outerProp" />
<s:push value="innerObject">
    <s:property name="outerProp" />
    <s:property name="innerProp" />
</s:push>

Suppose the top object of the Value Stack is the Action.  The first tag would essentially execute the method MyAction.getOuterProp(), which we assume exists for this example.  The next tag invokes MyAction.getInnerObject() and pushes the value on the Value Stack.  The inner object will stay on the value stack within the s:push element.  The property innerProp exists on the inner object and is therefore retrieved from it.  But outerProp does not exist on this object, so the Value Stack falls back to the previous object in the stack, the Action.

Note that s:push behaves much like the old nested tags from Struts Classic.  In fact, I can't seem to find any other reason for the existence of the Value Stack.  Nested tags were kind of cool when they first appeared as it helped simplify Struts' expressions and therefore reduced code.  But I found in practice they were actually somewhat of a pain to maintain.  The problem is that your nested context keeps changing throughout the code, making any property expression ambiguous in and of itself.  Whenever I would come across such an expression, I found myself traipsing through the code to find the nested context, which was often nested within several other nested contexts.  Once JSTL and EL appeared on the scene I found I had hardly any need or desire to use the nested tags anymore, except in cases where JSTL could not do the job.  Not only was EL more terse, but your expressions had to be prefixed with the object they were being retrieved from.  If you wanted to avoid long chains of properties everywhere, you'd assign the value to a page variable using c:set.  This makes the view logic inherently clearer since there's no scoping going on "implicitly" and therefore behaving more like a regular scripting language.

To further confound things, the Value Stack makes things even more confusing when it cannot find a property value on the currently scoped object.  It will attempt to retrieve that value from the previous object in the stack.  So while looking only at the view code it may seem like the innerObject object has two properties retrieved from it, in reality it only has one.  If you need to debug this code for some reason you'll have to trace the expression.  Typically, to trace the resolution of an EL expression, you need to start at the parent object and make a trip down property operators to find the class actually being returned.  But with the Value Stack, you not only need to make the trip down, but you may need to turn around and go back if the property isn't actually there.  Really, the added benefit of saving the developer a few seconds during the initial coding does not seem to outweigh the maintenance overhead.

AJAX

The book tends to hype up Struts 2's support for AJAX, indicating that there are new tag libraries being built to handle AJAX actions.  It does not go into details, saying that the tags are currently under construction.  Some web research, however, shows otherwise.  Struts 2.1 has now deprecated support for the DOJO-based AJAX tags, allegedly because DOJO changes too quickly.  This should tell you to take hype with a grain of salt.  While this would seem to be an unfortunate turn of events, I did manage to find that there is an excellent jQuery plugin which appears to address many AJAX needs.  The showcase in particular looks very promising, showing tag usage examples next to the demonstrations.  While I did not get the opportunity to experiment with the tags, I may do so soon as they look pretty exciting.

Last Word

Interestingly missing from Struts 2 is any state management in the architecture.  In Struts Classic, you were permitted to store your forms in session scope through a setting in the config file.  In Struts 2, there is no session management whatsoever.  It's entirely up to you to manage your sessions.  This is good in that it solves the problem of having bloated ActionForms in session scope, but it leaves me feeling that some major aspect is missing.


In conclusion, Struts 2 gets points for the addition of Interceptors and Results to the architecture.  It also simplifies the Action considerably, consolidating ActionForm behavior into it.  But the selection of OGNL instead of EL was an unfortunate deviance from standards.  Furthermore, the Value Stack implementation feels unnecessarily complex.  I would also have taken away points for the loss of AJAX components, but the jQuery plugin looks like a very promising replacement.

No comments:

Post a Comment