Struts is undoubtedly the most successful Java web development framework. It is the first widely adopted Model View Controller (MVC) framework, and has proven itself in thousands of projects. Struts was ground-breaking when it was introduced, but the web development landscape has changed a lot in the last few years. It is also fair to say that Struts is not universally popular, and has in many ways been overtaken by other frameworks.
Developers not satisfied with Struts have tended to embrace other frameworks, or even create their own. This is even the case within the Struts community. The two major initiatives - the integration with WebWork and the JSF-based Shale - are producing frameworks quite different from the Struts to which most users are accustomed.
A less well known fact is that the increasing adoption of Java 5 has brought in a third way forward for Struts. In this article I introduce Strecks, a set of Java 5 based extensions which offer a greatly streamlined programming model for Struts applications, without introducing any new compatibility issues. If you‘re confused about the name, think of it as a shortened, misspelled version of "Struts Extensions for Java 5".
Strecks is built on the existing Struts 1.2 code base, adding a range of productivity enhancing features, including:
These are discussed in more detail in the rest of the article.
Strecks aims to simplify, not replace, the existing Struts programming model. Familiar artifacts such as Actions
and ActionForms
are still present, in an enhanced form. Strecks actions and forms can be used seamlessly within existing applications. The user interface or view layer of the framework has not been altered, and neither has configuration; there are no new custom tag libraries to speak of, with only a couple of useful tags added.
Strecks enhancements chiefly affect Struts‘s controller layer, which consists of ActionForms
, Actions
and a controlling RequestProcessor. Let‘s look at form-handling enhancements first, starting with form validation.
A requirement for virtually any application which accepts user input is form validation. Few would argue that programmatic validation code is tedious to write. An example of Struts 1.2 programatic validation, taken from our example application, is shown below:
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){ActionErrors errors = new ActionErrors();if (days == null){ActionMessage error = new ActionMessage("holidaybookingform.days.null");errors.add("days", error);hasError = true;}else{if (!GenericValidator.isInt(days)){ActionMessage error = new ActionMessage("holidaybookingform.days.number");errors.add("days", error);hasError = true;}}//validation for other fields omittedif (!hasError)return null;return errors;}
All of the above code is required simply to validate a single field, that is, to determine that the number of days supplied in our holiday booking entry form has been supplied in the correct Integer format.
Struts offers an alternative XML-based validation mechanism in the form of Jakarta Commons Validator. However, the format is regarded by many as verbose, and there has been a shift in recent years away from the use of XML in this way.
Strecks offers a concise annotation-based alternative, which we see in action below:
private String days;@ValidateRequired(order = 3, key = "holidaybookingform.days.null")@ValidateInteger(key = "holidaybookingform.days.number")public void setDays(String days){this.days = days;}
Your form class is still a subclass of ActionForm
; it is not exposed to the framework‘s mechanism for implementing the validation. This all takes place behind the scenes. Also, you can also implement programmatic validation logic in the form‘s validate()
method.
One problem when working with domain models in Struts applications is the need to write data binding and type conversion code - the framework provides very limited support for this. A practical limitation with Struts 1.2 is that action forms generally need to use String properties. Consider a form field in which the user is expected to enter a number. If a user enters "one", conversion will fail. The problem is when the form is redisplayed, the value 0 will be displayed, and not "one", an unacceptable usability scenario. The workaround is to use String-based form properties, at the price of having to do type conversion and data binding programmatically.
An example of a type conversion code and form property to domain model in Struts 1.2 is shown below, for just the Date field in the example application‘s form:
public void readFrom(HolidayBooking booking){if (booking != null){if (booking.getStartDate() != null)this.startDate = new java.sql.Date(booking.getStartDate().getTime()).toString();}}public void writeTo(HolidayBooking booking){if (this.startDate != null && this.startDate.trim().length() > 0)booking.setStartDate(java.sql.Date.valueOf(startDate));}
Frameworks with declarative data binding and type conversion generally implement these features in the view to controller boundary. For example, JSF data conversions are defined in the view, with the target being managed bean properties. Strecks, by contrast, uses the ActionForm
itself for type conversion and data binding. This has two benefits. Firstly, it allows the interface between the view and the form bean to remain very simple, no different from existing Struts applications. Also, it also allows type conversion and data binding behaviour to be unit tested very easily.
The Strecks type conversion and data binding features allow the previous example to be reduced as shown below:
private String startDate;private HolidayBooking booking;@BindSimple(expression = "booking.startDate")@ConvertDate(pattern = "yyyy-MM-dd")public String getStartDate(){return startDate;}//getters and setters omitted
The value of the form property is converted using the Converter
identified using the @ConvertDate
annotation, then bound the the startDate property in the domain object HolidayBooking
. No extra code is needed. Specifying a converter explicitly is not necessary for most basic types, such as Integer
, Long
and Boolean
. Also, the converter class can be set explicitly as a @BindSimple
attribute, in which case the separate @ConvertDate
would not be necessary.
Both converter annotations and binding mechanisms can be added without additional configuration by using the Annotation Factory pattern. The table below describes the additional artifacts required for each:
Binding Mechanism | Converter | |
---|---|---|
Implementation | Implementation of BindHandler | Implementation of Converter |
Annotation | Annotation annotated with @BindFactoryClass | Annotation annotated with @ConverterFactoryClass |
Factory | Implementation of BindHandlerFactory | Implementation of ConverterFactory |
In the web tier of a J2EE application, dependencies take many forms, ranging from application-scoped business tier services to request-scoped parameters and attributes. Effective dependency management is about getting to just the data and services your application needs, in a simple and robust manner. The recognition of the value of Dependency Injection in achieving this is among the most important developments in Java programming practice in the last few years.
Dependency management in a typical Struts 1.2 application is somewhat out of step with these developments, for a number of reasons:
ActionSupport
class, which is used to simplify access to a Spring ApplicationContext
). Actions
are closely bound to the Servlet API, which makes unit testing harder than for actions with no framework or Servlet API dependencies. Consider the example below, which uses a Spring bean to retrieve HolidayBooking
data for a given ID.
public class RetrieveBookingAction extends ActionSupport{public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,HttpServletResponse response) throws Exception{HolidayBookingService holidayBookingService = (HolidayBookingService) getWebApplicationContext().getBean("holidayBookingService");long id = Long.parseLong(request.getParameter("holidayBookingId"));HolidayBooking holidayBookings = holidayBookingService.getHolidayBooking(id);request.setAttribute("holidayBooking", holidayBookings);return mapping.findForward("success");}}
Notice how the service bean is obtained using a programmatic hook. The request parameter ID is converted programmatically using Long.parseLong()
.
Strecks has a simple mechanism for dependency injection, also based on Java 5 annotations. Action beans (the replacement for Actions which discussed in more detail later) are instantiated on a per-request basis, allowing dependencies to be injected via setter method annotations.
The Strecks equivalent to our previous example is shown below. Notice how the execute()
method no longer has multiple method arguments, and is much shorter than the Struts example.
@Controller(name = BasicController.class)public class RetrieveBookingAction implements BasicAction{private HolidayBookingService holidayBookingService;private WebHelper webHelper;private long holidayBookingId;public String execute(){HolidayBooking holidayBookings = holidayBookingService.getHolidayBooking(holidayBookingId);webHelper.setRequestAttribute("holidayBooking", holidayBookings);return "success";}@InjectSpringBean(name = "holidayBookingService")public void setService(HolidayBookingService service){this.holidayBookingService = service;}@InjectWebHelperpublic void setWebHelper(WebHelper webHelper){this.webHelper = webHelper;}@InjectRequestParameter(required = true)public void setHolidayBookingId(long holidayBookingId){this.holidayBookingId = holidayBookingId;}}
Dependency Injection in Strecks is supported through the InjectionHandler
interface, which has the following form:
public interface InjectionHandler{public Object getValue(ActionContext actionContext);}
ActionContext
is simply an interface which simply wraps the HttpServletRequest
, HttpServletResponse
, ServletContext
, ActionForm
and ActionMapping
. In other words, InjectionHandler
supports retrieving data or services accessible via any one of these objects. In addition to the injectors shown in the example above, injectors are available for request, session and application context attributes, the Locale
, MessageResources
, the ActionForm
, the ActionMapping
, as well as other objects.
Adding your own injection handlers is simple, again using the Annotation Factory pattern:
InjectionHandler
implementation which returns the object you wish to inject. InjectionHandler
. InjectionHandlerFactory
which can be used to read the annotation and create an InjectionHandler
instance. Actions are the key artifacts in a Struts application because they are the primary home for service tier invocations as well as the application‘s presentation logic. In the previous section, we discussed briefly the need for thread-safety in actions. This, together with the presence of a fairly predefined inheritance hierarchy, makes reuse of request processing logic quite difficult.
Strecks offers a simple solution to these problems. The responsibility of a traditional Struts action is divided between two objects, an action controller and an action bean.
The idea behind the action controller is that common request processing logic can be abstracted and reused across many actions. For example, simple CRUD web applications typically use three types of request handling logic:
With request processing logic abstracted into a controller, all that remains is to implement the domain-specific aspects of each operation. This is the job of the action bean.
In Strecks, the action bean is registered in struts-config.xml exactly as if it were a regular Action
. When used for the first time, an annotation on the action bean‘s class definition is used to identify the controller. An instance of the controller is created. The controller, which extends Action
, is held in the action cache for subsequent requests. Each time a request is received, the controller creates a new instance of the action bean. The action controller itself holds no state, and is shared among multiple requests.
Lets take a look at an action bean implementation. SubmitEditBookingAction
, shown below, is invoked when submitting an update from the "Edit Holiday Booking" form.
@Controller(name = BasicSubmitController.class)public class SubmitEditBookingAction implements BasicSubmitAction{private HolidayBookingForm form;private HolidayBookingService holidayBookingService;private WebHelper webHelper;public void preBind(){}public String cancel(){webHelper.setRequestAttribute("displayMessage", "Cancelled operation");webHelper.removeSessionAttribute("holidayBookingForm");return "success";}public String execute(){HolidayBooking holidayBooking = form.getBooking();holidayBookingService.updateHolidayBooking(holidayBooking);webHelper.setRequestAttribute("displayMessage", "Successfully updated entry: " + holidayBooking.getTitle());webHelper.removeSessionAttribute("holidayBookingForm");return "success";}//dependency injection setters omitted}
The action bean uses BasicSubmitController
, which itself mandates that its action beans implement the BasicSubmitAction
interface. SubmitEditBookingAction
does so by implementing the preBind()
, cancel()
and execute()
methods. Lets consider cancel()
, which is invoked when a user clicks on a button rendered using the <html:cancel/>
; tag.
The key point here is that the logic for determining whether a form has been cancelled is implemented in the controller, not the action bean. We see how this happens by looking at the controller implementation below:
@ActionInterface(name = BasicSubmitAction.class)public class BasicSubmitController extends BaseBasicController{@Overrideprotected ViewAdapter executeAction(Object actionBean, ActionContext context){BasicSubmitAction action = (BasicSubmitAction) actionBean;ActionForm form = context.getForm();HttpServletRequest request = context.getRequest();boolean cancelled = false;if (request.getAttribute(Globals.CANCEL_KEY) != null){cancelled = true;}if (form instanceof BindingForm && !cancelled){action.preBind();BindingForm bindingForm = (BindingForm) form;bindingForm.bindInwards();}String result = cancelled ? action.cancel() : action.execute();return getActionForward(context, result);}}
In a traditional Struts application, code for identifying a cancel event would need to be present in the Action
class.
Use of an action controller in this way simplifies development of actions and allows for effective reuse of request processing logic. A number of action controllers are available out of the box, including form handling controllers as well as dispatch controllers which mimic the behaviour of DispatchAction
, MappingDispatchAction
and LookupDispatchAction
. Creating controllers customised for AJAX operations, for example, would not be difficult.
One of the most powerful Strecks features is the ability to add custom annotations to action beans. This feature allows the contract between the action bean and the controller action to be extended without changing the interface that the action bean must implement.
The mechanisms behind custom action bean annotations are already used by the framework for a number of purposes:
LookupDispatchAction
equivalent The same mechanism could be used to features currently not present, such as action bean-specific interceptors and action bean validate()
methods.
Lets consider action bean source configuration. Using a single annotation class level annotation, it is possible to identify an action bean as "Spring-managed", as shown in the example below:
@Controller(name = BasicController.class)@SpringBean(name = "springActionBean")public class SpringControlledAction implements BasicAction{private String message;private SpringService springService;public String execute(){int count = springService.getCount();message = "Executed Spring controlled action, count result obtained: " + count;return "success";}public String getMessage(){return message;}//Don‘t need dependency injection annotation, becausepublic void setSpringService(SpringService service){this.springService = service;}}
Its hard to imagine how providing this level of integration with Spring could be more simple. In the example, the action bean is instantiated per request by obtaining the bean named springActionBean
from the Spring application context. Note the absense of the @InjectSpringBean
annotation - the service dependency injection is handled within the Spring startup process.
A second example, shown below, involves an action bean handling submission of a form with multiple submit buttons. It in turn uses BasicLookupDispatchController
, which supports the @DispatchMethod
annotation for determining which method to execute.
@Controller(name = BasicLookupDispatchController.class)public class ExampleBasicLookupSubmitAction implements BasicSubmitAction{private String message;private MessageResources resources;public void preBind(){}public String execute(){message = "Ran execute() method in " + ExampleBasicLookupSubmitAction.class;return "success";}@DispatchMethod(key = "button.add")public String insert(){message = "Ran insert() method linked to key ‘" + resources.getMessage("button.add") + "‘ from "+ ExampleBasicLookupSubmitAction.class;return "success";}public String cancel(){message = "Ran cancel() method";return "success";}@InjectMessageResourcespublic void setResources(MessageResources resources){this.resources = resources;}}
The plain Struts equivalent would require implementation of a method returning a java.util.Map
containing the message key to method name mappings.
Custom action bean annotations are supported through the use of the Annotation Decorator pattern, which works in a similar way to the Annotation Factory pattern.
Specifically, the controller must include a class level annotation which can be used to identify an ActionBeanAnnotationReader
implementation. For example, BasicLookupDispatchController
has the class definition which includes the @ReadDispatchLookups
.
@ActionInterface(name = BasicSubmitAction.class)@ReadDispatchLookupspublic class BasicLookupDispatchControllerextends BasicDispatchControllerimplements LookupDispatchActionController{// rest of class definition}
The @ReadDispatchLookups
identifies the DispatchMethodLookupReader
as the annotation reader, which in turn simply looks for @DispatchMethod
annotations in order to populate the message key to method name map. The implementation of ActionBeanAnnotationReader
for handling pluggable navigation is more complex but uses the same basic mechanism.
As with validators, converters and dependency injectors, no XML is required to add custom annotations. All that is required are controller annotations to identify the ActionBeanAnnotationReaders
, and the necessary annotations in the action bean implementation itself.
Most web applications require operations which are common to many actions, such logging, authentication and customized state management. In Struts 1.2 it is relatively difficult to accomodate these requirements in an elegant way. A Struts developer typically has two choices:
RequestProcessor
subclass Action
superclass which all of the application‘s actions should extend While allowing duplication to be factored out, both solutions interfere with an inheritance hierarchy. Adding behaviour typically requires changing existing classes rather than simply adding a new class and appropriate configuration entry.
The solution, evident in frameworks such as WebWork and Spring MVC and also Strecks, is interceptors. Strecks defines two interceptor interfaces, BeforeInterceptor
, and AfterInterceptor
. A class which provides a simple implementation of both is shown below:
public class ActionLoggingInterceptor implements BeforeInterceptor, AfterInterceptor{private static Log log = LogFactory.getLog(ActionLoggingInterceptor.class);public void beforeExecute(Object actionBean, ActionContext context){HttpServletRequest request = context.getRequest();log.info("Starting process action perform " + request.getRequestURI());log.info("Using " + context.getMapping().getType());}public void afterExecute(Object actionBean, ActionContext context, Exception e){HttpServletRequest request = context.getRequest();log.info("Ended action perform of " + request.getRequestURI() + StringUtils.LINE_SEPARATOR);}}
Interceptors have access to the ActionContext
and the same injected state as the action bean itself. BeforeInterceptors
are run before any of the action bean methods execute but after action bean dependency injection has occurred. Neither BeforeInterceptors
nor AfterInterceptor
can affect navigation - this is the responsibility of the action bean and controller. BeforeInterceptors
can interrupt action execution by throwing an exception. In this case, the neither the action bean methods nor the beforeExecute()
method of remaining BeforeInterceptors
will be executed.
By contrast, the afterExecute()
of each AfterInterceptor
is always executed. If an exception has been thrown during execution, this will be passed in to the afterExecute()
method.
Each interceptor operates across all actions in a given Struts module. Action-specific interceptors are not present, although adding them is planned.
Navigation and view rendering is another area in which Strecks provides subtle but powerful improvements over traditional Struts capabilities.
In Struts 1.2, the mechanism for navigation involves the action returning an ActionForward
instance from the Action
‘s execute()
method. This works fine for simple JSP-based views.
Rendering using alternative view technologies (e.g. outputting of binary data, use of Velocity, etc.) is typically done in one of two ways in a Struts 1.2 application:
ServletOutputStream
or PrintWriter
. In this case the action returns null
to indicate that the response is complete. In the former case, the mechanism is arguably rather crude, while the latter requires extra configuration, since the solution is not "built-in".
Instead of returning ActionForward
, Strecks controller actions return a ViewAdapter
instance. This is the starting point for more flexible navigation, because it allows for alternative view rendering implementations. For ActionForward
-based navigation, the controller simply returns an ActionForwardViewAdapter
, which wraps an ActionForward
instance.
Strecks makes it particularly easy to handle what Craig McLanahan terms "outcome-based navigation", which is implemented in Struts using navigation mapping entries in struts-config.xml, retrieved using ActionMapping.findForward(result)
. In the action bean example below, ActionMapping.findForward()
will automatically be called with the success
passed in as the argument.
@Controller(name = BasicController.class)public class ExampleOutcomeAction implements BasicAction{public String execute(){//add processing herereturn "success";}}
The same result can also be achieved with the @NavigateForward
annotation:
@Controller(name = NavigableController.class)public class ExampleOutcomeAction implements NavigableAction{public void execute(){//add processing here}@NavigateForwardpublic String getResult(){return "success";}}
So why there are two navigation mechanisms available for the same end result? The answer is the first gives you convenience if you want to use outcome-based navigation and are happy for this to be built into the action bean‘s interface. The second gives you flexibility. The second example could be changed to handle navigation by returning an ActionForward
from the action bean itself:
@Controller(name = NavigableController.class)public class ExampleOutcomeAction implements NavigableAction{public void execute(){//add processing here}@NavigateForwardpublic ActionForward getResult(){return new ActionForward("/result.jsp");}}
In other words, the navigation mechanism is independent of the NavigableAction
interface which the controller uses to talk to the action bean. A different navigation reader and annotation could easily be used, again with the help of the Annotation Factory pattern, to plug in a completely different view rendering mechanism.
This brings us back to why the controller returns ViewAdapter
, and not ActionForward
. Instead of returning an ActionForwardViewAdapter
, the controller could return a RenderingViewAdapter
instance. RenderingViewAdapter
extends ViewAdapter
and defines the method:
public void render(ActionContext actionContext);
Of course, render()
could be implemented in lots of different ways. One rather tempting option (not implemented yet) is to use this interface as an adapter for the Spring MVC View
and ViewResolver
interfaces. In a simple stroke, we could tap in the full functionality of Spring MVC‘s support for alternative view technologies, probably the most powerful feature of this framework.
Strecks also provides additional navigation handling utilities in two areas:
The article has hopefully shown you that Strecks is a powerful framework which can transform the way you write Struts applications. It has potential to be an excellent solution for organisations which have:
Strecks‘s aim has been to:
Strecks has been open sourced under the Apache License, version 2. API changes between now and the first 1.0 release are not expected to be significant. We encourage you to download the Strecks and try it out today. Feedback would be welcome.
Phil Zoio is an independent Java and J2EE developer and consultant based in Suffolk in the UK. His interests include agile development techniques, Java open source frameworks and persistence. He can be contacted at philzoio@realsolve.co.uk.