http://www.developer.com/

Back to article

Comparing the Struts 1 and Struts 2 Web Application Frameworks


November 8, 2007

Because the Struts web application framework was initially released at the beginning of this decade, developers have utilized it to construct many thousands of applications. Struts was, and still is, the most popular framework for building Java-based web applications. Though not without its flaws, Struts simplifies the construction of robust and maintainable web-based software. Today, there are probably twice as many applications using Struts than there are those using all other competing web frameworks combined. It is a tribute to Struts that so many succeeding frameworks, such as JSF (JavaServer Faces), borrowed many of their concepts directly from Struts. Struts is far from perfect, however, and in recent years developers have increasingly begun to turn to alternative solutions. To keep pace with developer demand, the Struts team released a major update to the framework; it's generally known as 'Struts 2'. The purpose of this article is to provide Struts 1 developers an overview of how Struts 2 works by looking at code. You will look at a sample application twice, once as a Struts 1 application, and then a second time as a Struts 2 application. You won't be able to cover everything, but it should be enough to get you started with Struts 2.

Background

Struts was first released in 2001. Its primary contributor was Craig McClanahan. Prior to Struts, web applications were often a jumbled mix of Java scattered throughout JSP(JavaServer Pages) scriptlets, Java Servlets, and various Java classes. Applications produced in this haphazard manner were typically hard to test and even harder to maintain. Struts promoted an MVC (Model-View-Controller) architecture. With MVC, developers were able to produce more modular code that was flexible enough to allow an application to grow. The MVC style of development has stood the test of time and has proven itself to be a successful approach for constructing web applications. In the last several years, competing frameworks have emerged to address some of Struts' inadequacies. The Struts community began work on a next-generation Struts to address some of these problems. Another framework, called WebWork, had in the meantime gained some notoriety as a viable successor to Struts. Finally, the Struts team and WebWork decided to join forces; this union led to the emergence of Struts 2. Struts 2, in many respects, represents a significant improvement over Struts 1.

Struts 1 Walkthrough

As previously mentioned, this article will attempt to provide a comparison of Struts 1 and Struts 2 by comparing the code used to develop the same application with each framework. Some cursory knowledge of Struts 1 is assumed. The sample application you will create is called 'defect-tracker'. It allows users to record application defects and developers to record resolutions to those defects. The application is trivial with the most basic requirements, but it should provide sufficient opportunity to cover basic features common to most all web applications.

To begin a Struts 1 application, the first file you need is the web.xml configuration file. The web.xml file must be modified for Struts 1 so application URI requests can be routed to a Struts controller:

<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</param-value>
   </init-param>
   <load-on-startup>2</load-on-startup>
</servlet>


<!--Map Struts-related request to action-->
<servlet-mapping>
   <servlet-name>action</servlet-name>
   <url-pattern>*.do</url-pattern>
</servlet-mapping>

URI requests matching the pattern '*.do' are received by the Struts ActionServlet. ActionServlet finds the Struts Action mapped to this request as specified in the struts-config.xml configuration file. The ActionServlet and Action objects comprise the 'controller' portion in the MVC architecture of a Struts application. To transport data between Struts Actions and the 'view', typically implemented as JSPs, Struts uses ActionForm objects. ActionForm objects represent the 'model'. When a user submits a form, the ActionForm, along with its data, is passed to the Action's execute method. The Action might validate the data, and then invoke any necessary business logic, which, ideally, will be encapsulated in another layer that is completely unaware of Struts. Once business logic executes, the Action transfers control to a view specified in one or more ActionForward objects, which are also configured in struts-config.xml. ActionForward entries represent the possible outcomes of an Action. They can be defined for each Action or globally to apply to all Actions. A glimpse of the struts-config file is provided below. The defect-tracker application has two sets of Actions and ActionForms. One set concerns the listing of existing defects. The second set addresses the manipulation of defect data, typically referred to as 'CRUD' ('Create-Update-Delete').

struts-config.xml

<global-forwards>
   <forward name="list" path="/list.do"/>
</global-forwards>

<form-beans>
   <form-bean name="listForm" type="web.DefectsListForm" />
   <form-bean name="actionForm" type="web.DefectsActionForm" />
</form-beans>

<action-mappings>
   <action
      path="/list"
      name="listForm"
      scope="request"
      type="web.DefectsList"
      validate="false">
      <forward name="list" path="/pages/defects.jsp" />
   </action>
   <action
      path="/action"
      name="actionForm"
      scope="request"
      type="web.DefectsAction"
      parameter="method"
      validate="false">
      <forward name="edit" path="/pages/editDefect.jsp" />
      <forward name="add" path="/pages/addDefect.jsp" />
      <forward name="list" path="/list.do" redirect="true" />
   </action>
</action-mappings>

The last two entries in struts-config declare a properties file for displaying application messages and labels, and a separate configuration file to be used to add validation rules for the ActionForm objects in your application.

The defect-tracker application has a single domain object, Defect, that represents a system defect. Upon startup, the user is redirected to 'list.do', which maps to the DefectsList Action object's execute method that will retrieve a list of defects.

DefectsList execute method

public ActionForward execute(
      ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response) {
   DefectsListForm defectsListForm = (DefectsListForm) form;

   //Get data from business layer and assign it to the form
   //object...
   //...

   return mapping.findForward("list");
}

The execute method obtains the application data and assigns it to a Collection of Defect instances using the setDefects() method on ActionForm DefectsListingActionForm class.

DefectsListForm's Defects property

   private List defects;

   public List getDefects() {
      return defects;
   }

   public void setDefects(List defects) {
      this.defects = defects;
   }

The defects.jsp file utilizes Struts 1 tag libraries to display these defects and to display labels and messages from a .properties file that you declared in struts-config.xml.

defects.jsp displays all defects


Click here for a larger image.

The user can choose to add, edit, or delete a defect. When the user wants to edit a record, for example, the handler first obtains the ID from the HTTPServletRequest, which is passed as a parameter to the Action's execute method. The execute method then will invoke a method in the business layer to obtain the defect, and then it transfers to an editable form which will be populated with data. When adding a new defect, a blank form appears. Because it does not make sense to allow a user to resolve a defect they have just entered, the add form provides only a subset of fields.

addDefect.jsp to enter information about new defects


Click here for a larger image.

editDefect.jsp for updating defects


Click here for a larger image.

Struts 1 does not always handle the conversion of input strings very well so sometimes the developer needs to convert form data before passing it to a domain object. The DateConverter class accomplishes this for date fields. After a successful add, edit, or delete, the user is returned to the list again. The first Action we saw, DefectsList, had its 'execute' method automatically invoked. Sometimes an execute method is all you need, however, in order to help developers consolidate related actions in a single Action object, Struts 1 offers the 'DispatchAction' class. DispatchAction uses the 'method' request parameter to tell the Struts Action which method to invoke. So,instead of 'execute', we can define methods like 'add', 'edit', and 'delete' to handle each type of request. In order to use DispatchAction, your Action object needs to extend DispatchAction (not Action) and define a form parameter that identifies the method to invoke. The method for deleting a defect from DefectsAction is shown here:

DefectsAction's delete method

public ActionForward delete(
      ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response) {

   long id = Long.parseLong(request.getParameter("id"));

   //Invoke business layer to delete the object using the id...

   return mapping.findForward("list");
}

Struts 1 developers use the Apache Commons Validator framework to validate data. To enable validation, you must register the Validator plug-in in the struts-config.xml file, and then make sure your own ActionForms extend ValidatorForm instead of ActionForm. You can declare validation rules in a validation.xml file. For example, the validation.xml file you use states that a defect's description is required. The 'validate' method is invoked by the Action before saving a record. When the user does not provide a description, the Struts ActionErrors collection is populated and its contents displayed on the view page with the help of an <html:errors/> tag.

<form name="actionForm">
   <field property="defect.description" depends="required">
      <arg0 key="defects.description"/>
   </field>
</form>

Validating the form upon save

   ActionMessages errors = form.validate(mapping, request);
   if (!errors.isEmpty()) {
      saveErrors(request, errors);

Validation error message is displayed on addDefect.jsp


Click here for a larger image.

That just about wraps up your look at the Struts 1 version of defect-tracker. Hopefully, it will have been mostly a review for you. However, before you move on, it is obvious that you have not really produced a working application. Struts provides the web framework, but what about the portion of the application where you implement your business logic and save data to the database? Struts cannot help with this piece. Business logic is best kept in a separate business layer implemented with EJBs (Enterprise JavaBeans) or plain Java classes that have no concept of Struts. One popular framework for orchestrating the business layer logic is the Spring framework. Spring, for those unfamiliar with the framework, is essentially a dependency injection framework that declares Java classes and their dependencies in a configuration file. At runtime, instead of having to obtain dependent objects manually, they are injected for you. Beyond this, Spring offers numerous other useful features and integration points for many other Java frameworks and toolsets. For defect-tracker, I have chosen the Spring framework primarily for two reasons. First, Spring is very popular and integrates well with Struts. A great many Struts 1 applications already use Spring. Secondly, Struts 2, as you will see, is even more closely tied to Spring, making an even stronger argument for its use with Struts.

Because so many Spring developers use Struts, Spring offers supporting classes for working with Struts 1. You have two options: Use Spring's ContextLoaderPlugin to manage injections or use Spring's ActionSupport classes to obtain Spring's application context. Both options are fairly easy to implement. This article is about Struts, so I will not dive into the details of the business and data tiers of the application, but I have implemented these two layers using Spring, with the help of the Hibernate framework for persistence. Here, briefly, are the steps I have used to wire Spring to the defect-tracker application.

First, the Spring configuration file needs to be mentioned in the web.xml file:

<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/application-context.xml</param-value>
</context-param>

<listener>
   <listener-class>org.springframework.web.context.
      ContextLoaderListener</listener-class>
</listener>

Using Spring's ActionSupport classes is the simplest approach and the one used here. You obtain a Spring-managed bean in your Action method and use it to invoke business logic. For example, in the save method on the DefectsAction class, you obtain Spring's context, and invoke the methods provided in an implementation of the DefectManager business interface:

...

WebApplicationContext ctx = getWebApplicationContext();
DefectsManager mgr = (DefectsManager) ctx.getBean("defectsManager");
mgr.saveDefect(defect);
...

DefectManager is where any business rules should be coded. To illustrate this approach, when the user enters a resolution to a defect, the DefectManager will automatically audit that user (obtaining an actual name in a real application of course), and set the Defect's isResolved flag to 'true'. Then, Spring calls upon Hibernate to handle the necessary database interaction. Click here to view the complete 'Spring-enabled' Action classes: DefectsList and DefectsAction.

On to Struts 2...

Now that you've seen Struts 1, it's time to have a second go around at defect-tracker, this time using Struts 2. My goal is to not deviate too much from the original design to to keep comparisons valid. To implement defect-tracker as a Struts 2 application, the first thing you will need to do is modify the web.xml file. Struts 1 used a Java Servlet called ActionServlet to intercept requests matching a specified URI pattern ('*.do' by default). Struts 2 opts to use a Servlet Filter instead. The default extension for Struts 2 is not "*.do" but "*.action". This extension does not need to be specified in the Filter mapping, but will be assumed. It can be overridden, as many of Struts default properties can by editing a 'struts.properties' file and placing it in the classpath. Due to the different ways that each version of Struts maps requests, developers should have no problem using both Struts 1 and Struts 2 together in the same application if they choose.

Filter mappings in Struts 2 web.xml

<filter>
   <filter-name>struts2</filter-name>
   <filter-class>org.apache.struts2.dispatcher.
                 FilterDispatcher</filter-class>
</filter>

<filter-mapping>
   <filter-name>struts2</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

Struts 1 utilized the 'struts-config.xml' file for Struts configuration. For example, the struts-config is where a developer maps path names to Actions, defines ActionForms that go along with those Actions, and specifies ActionForwards for different types of Action outcomes. Well, a lot has changed in this area in Struts 2. For starters, Struts 2 replaces struts-config with a file called 'struts.xml'. The 'struts.xml' file can be placed anywhere in the classpath. Peering into struts.xml, you will notice some big changes. First and foremost, ActionForms are gone. Struts 2 places no restrictions on where a developer stores a page's data. For example, you can pull the form data directly from the Action class (Actions have stuck around for Struts 2). Struts 2 replaces ActionForward objects with Result objects. Similar to Struts 1, an Action returns a string result that is matched against an entry in struts.xml. Below is how your two Actions in defect-tracker are configured in Struts 2. Note the various changes in element names too. For the most part, these new element names are fairly intuitive of what they represent.

<struts>
<include file="struts-default.xml"/>

<constant name="struts.custom.i18n.resources"
          value="MessageResources" />

<package name="default" extends="struts-default">

   <action name="list" class="web.DefectsList">
      <result>/pages/defects.jsp</result>
   </action>

   <action name="action_*" method="{1}"
           class="web.DefectsAction">
      <result name="input">/pages/editDefect.jsp</result>
      <result type="redirect">list.action</result>
   </action>

</package>

</struts>

In struts.xml, developers group related-actions into a 'package'. You are simply using the default package here. However, if you decided to create a packaged called 'crud' with a similarly titled 'namespace' attribute, URI requests for Actions in that package would take the form of '/crud/*.action'. Developers can define constants here to override some default settings in the Struts 2 framework. Looking at the Action declarations, you will notice that a single result can be specified without a name. Struts 2 assumes this default entry to be the 'success' result and there is no need to specify it. If you will recall, you used a Struts 1 DispatchAction to handle the multiple CRUD events for a defect. There is no need to extend DispatchAction (in fact, DispatchAction does not exist in Struts 2). Rather, a wildcard/placeholding mechanism can be used instead to transfer control to a specific Action method. For example, in the Struts 2 DeffectsAction, a URI of 'action_go' will result in the invocation of a 'go' method on the relevant Action class.

The 'list.action' URI request maps to your Struts 2 version of the DefectsList Action class. Here is the code:

package web;

import business.DefectsManager;
import com.opensymphony.xwork2.ActionSupport;
import java.util.List;

public class DefectsList extends ActionSupport  {

   private List defects;
   private DefectsManager defectsManager;

   public List getDefects() {
      return defects;
   }
   public void setDefects(List defects) {
      this.defects = defects;
   }

   public void setDefectsManager(DefectsManager defectsManager) {
      this.defectsManager = defectsManager;
   }

   public String execute() {
      this.defects = this.defectsManager.getDefects();

      return SUCCESS;
   }

}

Compared to the original DefectsList Action class, I think you will agree the code here is much cleaner. Extending the Struts 2 ActionSupport class is not a requirement but it does provide access to many useful methods and constants, such as the SUCCESS constant you are using as a result. Contributing to the leanness of this code is the fact tha the execute method, which used to require four parameters, now has zero parameters. One of these parameters was for the ActionForm and ActionForms that are no more. But besides that, the remaining parameters are missing because Struts 2 uses the Spring framework internally to inject necessary dependencies like the HTTPServletRequest object at runtime, when it is needed. Along with dependency injection, an important concept in Struts 2 is its use of Interceptors. Essentially, Interceptors are Java classes provided by the Struts 2 framework that allow you to perform logic before and after Action executions. Several Struts 2 Interceptors are provided out of the box, but you can also create your own. Typically, to use an Interceptor, an Action class will implement the appropriate interface; then, this will alert the Interceptor associated with that interface that it has a job to do.

Because Spring is built into Struts 2, injecting Spring beans is as easy as creating a setter method for the Spring-managed bean. Struts 2 will inject this dependency automatically at runtime. Another option available is to declare Struts Action classes as Spring beans in Spring's configuration file. Read the Struts 2 documentation for more information on this.

Here is the result of your Struts 2 'list' action, defects.jsp. The Struts 2 tag library is easier to use and much more powerful than those provided in Struts 1. One noteworthy feature of the tag library is its use of themes. Depending on which theme you set, the tags will generate a good deal of stylized markup for you, further reducing the amount of page clutter. You can set the theme globally and can override it for individual tags, as I have done here to keep the generated output similar to the Struts 1 version of defect-tracker. Here are a few examples of Struts 2 tags found in the list page:

<!--passes id as parameter to the input method on our Action-->
<s:url id="editLink" action="action!input">
   <s:param name="id" value="%{id}" />
</s:url>

<!--Displays a formatted date as a label-->
<s:date name="submittedOn" format="yyyy-MM-dd hh:mm" />

DefectsAction in Struts 2 looks even cleaner than DefectsList given the number of methods it contains. There are no ActionForms to contend with, no lengthy method APIs, and you can access your Spring business layer with ease, just as you did in DefectsList. Here is the code in its entirety:

package web;

import business.DefectsManager;
import business.data.Defect;
import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut;

public class DefectsAction extends ActionSupport
                           implements ServletRequestAware {

   private Defect defect;
   private DefectsManager defectsManager;
   private HttpServletRequest request;

   public Defect getDefect() {
      return defect;
   }

   public void setDefect(Defect defect) {
      this.defect = defect;
   }

   public void setDefectsManager(DefectsManager defectsManager) {
      this.defectsManager = defectsManager;
   }

   public void setServletRequest(HttpServletRequest
                                 httpServletRequest) {
      this.request = httpServletRequest;
   }

    public String input() {
      if (request.getParameter("id") != null) {
         long id = Long.parseLong(request.getParameter("id"));
         this.defect = this.defectsManager.getDefect(id);
      }
      return INPUT;
   }

   public String delete(){
      long id = Long.parseLong(request.getParameter("id"));
      this.defectsManager.removeDefect(id);

      return SUCCESS;
   }


   public String save() {
      this.defectsManager.saveDefect(this.defect);

      return SUCCESS;
   }

}

As with Struts 1, you need to access the HTTPServletRequest to obtain the defect's IF. To obtain this ID, DefectsAction utilizes what is probably the most common Struts 2 Interceptor you will use. You simply implement the ServletRequestAware interface and add a setter method for the request and the framework will make sure you have it when you need it. The 'input' method in this class replaces the add and edit methods in the previous version of the application. If an ID is present, you query for the defect; otherwise, you simply continue on to the result. This leads you to editDefect.jsp. In Struts 2, if the page is passed a reference to a null object, it will automatically create a new one. This is exactly what you want when creating a new Defect object.

Data form for creating and modifying defects

<s:form action="action!save" method="post"  >
   <s:hidden name="defect.id" value="%{defect.id}" />
   <s:textfield label="%{getText('defect.description')}"
      name="defect.description" value="%{defect.description}"
      size="100" maxlength="200" required="true" />
   <s:select label="%{getText('defect.priority')}"
      name="defect.priority" value="%{defect.priority}"
      list="#{'1':'1','2':'2','3':'3','4':'4'}" />
   <br/>
   <s:if test="defect.id > 0">
      <s:textfield label="%{getText('defect.resolution')}"
         name="defect.resolution" value="%{defect.resolution}"
         size="100" maxlength="200" />
   </s:if>
   <br/>
   <s:submit value="%{getText('defect.editrecord')} " />
   <s:submit value="%{getText('defect.cancel')}" action="list" />
</s:form>

Struts 2 utilizes the OGNL library to provide support for expressions. This allows you to efficiently display labels and model data together. The Struts 2 <if> tag can be used to toggle the display of the resolution field, depending on whether an existing id can be found. The form's action attribute specifies 'action!save', meaning the 'save' method will be invoked upon submit.

Finally, I will quickly discuss validation in Struts 2. Struts 2 uses the validation framework provided by WebWork. It too offers a similar declarative method for validation, but it is a more fine-grained approach. You have two choices, validate on a per-model basis or per-action. To validate at the Action level, you would simply create a file that takes the name {your action}-validation.xml and place it in the same package as the Action class. To validate at the model level, you would create a similar file that takes the name of the model object. A common approach is to validate at the model level, as you have done in the file Defect-validation.xml, then direct your Action validation file to validate per the rules in the model's validation file:

Defect validation

<validators>
   <field name="defect.description">
      <field-validator type="requiredstring">
         <message key="errors.required"/>
      </field-validator>
   </field>
</validators>

Action validator, delegates validation to the model with the 'type="visitor" ' attribute

<validators>
   <field name="defect">
      <field-validator type="visitor">
         <param name="appendPrefix">false</param>
         <message/>
      </field-validator>
   </field>
</validators>

In Struts 2, validation is enabled by default. There is no 'validate' method to invoke. Struts 2 also avoids firing validation on Actions that return 'input' so you can avoid writing code to suppress validation when it is not desired. There is a similar set of tags in Struts 2 for displaying validation error messages.

Wrap-Up

There is no denying that there is a lot to learn in Struts 2. Changes have been numerous, but the influence of its predecessor is still evident throughout the framework. Most of the new features and capabilities provided by Struts 2, I think you will agree, make it a worthy successor. This article was intended to provide a brief comparison between Struts 1 and Struts 2. The source files from this article can be downloaded here.There is a good deal of information that this article did not cover and I strongly encourage you to visit the Struts 2 web site and peruse the tutorials and documentation available there to make the most out of the framework.

About the Author

Michael Klaene is a Principal Consultant with Sogeti USA. He has over 10 years of experience in IT, and specializes in J2EE, .NET, and Oracle design and development.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date