Issue #20 June 2006

Developing web apps: Spring is here


-->

Many Java Web applications use EJBs (Enterprise Java Beans) to encapsulate business logic and manage persistence, and use Struts to facilitate the creation of the frontend of the application. However, recently a number of lightweight frameworks have been developed to simplify the creation of enterprise applications. The Spring Framework is the current front-runner.

This two-part article explores how you can greatly improve an application and simplify its maintenance. Part one describes integrating Spring gradually into an existing EJB/Struts web application with the goal of eventually taking out EJBs completely; part two discusses removing EJBs entirely in favor of using Hibernate for persistence.

The Spring framework

The Spring framework was created to simplify the development of JEE (formerly J2EE) web applications. Spring relies upon the ideas of IoC (Inversion of Control)[1] and AOP (Aspect Oriented Programming)[2] to simplify the development process. Spring also allows for integration with a variety of technologies—including those that it is capable of replacing, such as Struts.

Spring relies on a flavor of inversion of control known as dependency injection. Application objects configured as Spring beans do not need to retrieve their dependencies; instead, Spring constructs these dependencies and injects them at runtime. This removes the dependence on container APIs and glue code from the objects. (For instance, instead of looking up a datasource using JNDI inside an application server, the datasource object is injected into an object that depends on it using a JavaBean setter method.)

Once dependencies within an application are externalized in a Spring configuration file, it becomes easier to reconfigure parts of the application by simply injecting a different Spring bean as a dependency. An application configured using dependency injection becomes simpler to test as mock objects can be injected as dependencies during testing in place of real objects. The business objects will not depend on container APIs that would not be available during testing, and in most cases have no dependencies on Spring's APIs.

Spring is modular and does not require the application developer to use it for all parts of a program. It is easy to gradually introduce Spring into an existing project, as we will show in the rest of this article.

Spring also allows developers to choose from among a number of options for various parts of their applications. When it comes to the web tier, Spring can integrate with web MVC[3] frameworks such as Struts (as will be shown), WebWork or Tapestry, or it can rely completely on its own MVC framework, SpringMVC. The same principle applies to persistence; developers can choose a framework that best suits their needs. JDBC, Hibernate (used in part two of this article), and JDO are all available as data-access technologies and are tightly integrated into Spring.

Introduction to the examples

The sections that follow walk you through some of the steps we took to convert an existing web application built upon EJBs and Struts to a variety of different versions of the same application, all while maintaining the same look and feel of the initial application. The example used is called Olstore, which is an online store demonstration application used as an example program by the Red Hat Application Server. First we integrate Spring into Olstore using a minimalistic approach: Spring is simply a communication layer between the EJBs and Struts. In the second example we remove Struts. In the third example we remove EJBs to produce a "Spring and Hibernate" application. In the end, we have four versions of the application. While all appear almost identical to the user, their internals are (in some cases) dramatically different.

All four versions are available via the JOnAS CVS repository.

Please note that the steps described are simply how we integrated various parts of Spring into the application, and do not illustrate all of the options that Spring provides.

Spring integration, step 1 - EJBs, Struts, and Spring

Bringing Spring into our Olstore application begins by adding a listener to the web.xml configuration file:

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

This allows the Spring framework to load when the application is deployed and ensures that all the Spring beans and their dependencies are created.

Next, in order to bring Spring into our EJB and Struts application, we need to use Spring's support for Struts. This leaves all of the existing EJBs and most of the Struts code unchanged. In order to enable the Struts actions in the current application to access Spring-managed beans, the classes that extended org.apache.struts.action.Action were modified to extend org.springframework.web.struts.ActionSupport.

ActionSupport provides a number of helpful methods, a key one being getWebApplicationContext(). From the WebApplicationContext object we can retrieve any bean configured in Spring using the getBean() method.

Spring uses the applicationContext.xml configuration file to configure the Spring beans; you place the file inside the web application's WEB-INF directory so that Spring can find it. We required two types of configurations of Spring beans to ensure that the functionality of the application remained the same. The first was used to connect to stateless[4] beans via a proxy, and the second was used to connect to a stateful[5] bean.

Configuration 1: Connecting to stateless beans via a proxy

<bean id="itemHelper"class=
   "org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean"
   lazy-init="true">
   <property name="jndiName">
        <value>ejb/ItemHelper</value>
   </property>
   <property name="resourceRef">
        <value>true</value>
   </property>
   <property name="businessInterface">
        <value>olstore.session.helper.ItemHelper</value>
   </property>
</bean>

Configuration 2: Connecting to a stateful bean

<bean id="shoppingCart" class=
   "org.springframework.jndi.JndiObjectFactoryBean"
   lazy-init="true">
   <property name="jndiName">
        <value>ejb/ShoppingCart</value>
   </property>
   <property name="resourceRef">
        <value>true</value>
   </property>
</bean>

The LocalStatelessSessionProxyFactoryBean type used in the class field enables the configuration of a Spring bean proxy for an existing stateless EJB in the application. All that is required is to provide the business interface (a POJO[6] interface class) and the JNDI name to use when looking up the EJB. This proxy enables the EJB to be treated as a POJO, thus freeing you from dealing with EJB-specific details in your code. As the Spring configuration file becomes more complex throughout the conversion process the benefits of dependency injection will become more apparent. A detailed example of one such situation will be presented in part two of the article.

The JndiObjectFactoryBean type follows the same basic principles, although a business interface is not required because the JndiObjectFactoryBean is not creating a proxy to an existing EJB, but rather is simplifying how to access the EJB. JndiObjectFactoryBean simplifies your code by doing the JNDI lookup and returning the home interface for you.

The original Struts action required the following EJB specific code to access the EJBs:

EJBHomeFactoryfactory = EJBHomeFactory.getInstance();
ItemHelperLocalHome itemHelperHome =
    (ItemHelperLocalHome) factory.getLocalHome
    (EJBHomeFactory.ITEM_HELPER);
ItemHelperLocal itemHelper = itemHelperHome.create();

Here, EJBHomeFactory is a helper class that uses a JNDI name to return an EJB LocalHome object, which in turn is used to create an instance of the EJB. This binds the non-EJB code tightly to the EJBs themselves. In contrast, our new Spring-enabled actions access the bean through a simple line of code:

ItemHelper itemHelper =
(ItemHelper)getWebApplicationContext().getBean("itemHelper");

The parameter passed to getBean() is the ID of the bean configured above. Also note that the return value is the POJO business interface, which enables our action code to be EJB-independent. The Spring approach accesses the exact same bean as the original method, yet requires less code and has a more defined separation between the front end and back end of the application.

The Spring framework is divided up among a variety of JAR files (an all-in-one JAR is also available), each handling a different aspect of Spring's functionality. This enables you to package only what is needed in your application. In the case of this first integration of Spring, the required JARs are:

  • spring-beans.jar
  • spring-context.jar
  • spring-core.jar
  • spring-remoting.jar
  • spring-web.jar

Packaging more JARs with your application, or even the all-in-one spring.jar, will not affect the performance of the application, merely the size.

Spring integration, step 2 - SpringMVC replaces Struts

Next, we move on to replacing the view layer of the web application. SpringMVC provides support for many view technologies such as JSP, FreeMarker, Velocity, Tiles, iText, and POI. The example used in this article was originally written using JSPs, so we continued to use JSPs throughout the integration process. This required modifications to the JSPs to access the Spring content (more on this later).

The controllers

Similar to the first integration of Spring, the EJBs are left in place, and Struts is replaced with SpringMVC. The first steps in the migration from Struts to SpringMVC are to remove the Struts Action classes and to create new Spring Controller classes. This is accomplished by extending the provided Spring org.springframework.web.servlet.mvc.Controller class (or any of the other provided controller classes) and overriding the handleRequest() method. The handleRequest() method returns a ModelAndView object, which helps illustrate a key advantage of SpringMVC over Struts by accessing data from the Action/Controller in the view. The ModelAndView object that we returned takes two parameters, a String that represents the name of the view (resolved to a file name by a org.springframework.web.servlet.ViewResolver bean specified in the olstore-servlet.xml configuration file), and a java.util.Map object that represents the model.

Example:

Map model = new HashMap();
model.put("type", type);
model.put("productList", productList);
new ModelAndView("listItems", model);

Note that in the example, the type object is a java.lang.String, and the productList is a java.util.List object. As the model is a java.util.Map, you can pass in any object you want and have that object (and fields) available in the view, accessing them by using the key value given when adding objects to the java.util.Map. Conversely, in Struts a new Form class was needed for most Actions. Accessing information while using Struts also required creating a field for each object in the Form and adding set and get methods. This makes the SpringMVC alternative a much more attractive solution.

For example, if we wanted to print the value of the type String in our JSP, we would simply enter the following line:

<c:out value="${type}"/>

The c:out tag shown is available via the JSTL[7] library, which we used extensively for displaying content, looping through lists, and handling conditionals. The original JSPs needed to be converted to use the JSTL tags instead of the ones provided by Struts, and information received in the views had to be changed. The overall result of the JSP changes made them easier to read and completely decoupled from the EJBs.

The configuration

The configuration of the web-specific beans is placed in a separate file. This file must be located in the WEB-INF directory of the application and must be named servlet_name-servlet.xml, where servlet_name is the name used in the web.xml configuration of the DispatcherServlet class. In this example the servlet_name is Olstore:

<servlet>
        <servlet-name>olstore</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

The configuration file is olstore-servlet.xml.

Configuration is another area where the SpringMVC approach is clearly superior. The following is the configuration required for a Struts action and its corresponding form-bean and forward:

<form-bean name="CreateItemForm" type="olstore.form.CreateItemForm" />
<forward name="createItem" path="/admin/createItem.jsp" redirect="true" />
<action path="/admin/createItem" name="CreateItemForm"
    input="/admin/createItem.jsp" scope="request"
    type="olstore.action.ItemCreateAction"
    validate="false" />

By comparison, for the same task the SpringMVC configuration is as follows:

<bean name="/admin/createItem.do" class="olstore.controller.ItemController">
    <property name="olstore" ref="olstore"/>
</bean>

The configuration of the bean above relies on the BeanNameUrlHandlerMapping class, one of the two HandlerMapping implementations provided by Spring. The DispatcherServlet looks for the HandlerMapping bean that is defined in the servlet_name-servlet.xml configuration file. In this case, BeanNameUrlHandlerMapping bean is defined and therefore a request for /admin/createItem.do (after the servlet mapping part of the URL) will direct the request to the bean with the name defined above. The class that will end up processing the request is ItemController.

Interceptor

Another convenient feature used was Spring's ability to introduce an interceptor into the request processing chain. An interceptor's property can be added to the BeanNameUrlHandlerMapping bean to specify which interceptors are to be invoked. Here is a snippet of the configuration that accomplishes this:

<property name="interceptors">
    <list>
        <ref bean="shoppingCartInterceptor"/>
    </list>
</property>

Here, shoppingCartInterceptor is a Spring bean defined in the same configuration file. The class that implements it, ShoppingCartInterceptor, extends HandlerInterceptorAdapter. Three methods can be overridden to introduce additional logic, preHandle(), postHandle() and afterCompletion(). In this case, the preHandle() method is overridden in the ShoppingCartInterceptor class to make sure that a ShoppingCart object is always present in the HttpSession before a request reaches a controller.

No login required. Want to see your comments in print? Send a letter to the editor.

The reduction in configuration complexity is important both for ease of development and for tracking down potential configuration bugs.

Stay tuned for next month's issue of Red Hat Magazine where we will detail our experience replacing EJBs with Hibernate and how Spring's built-in support for Hibernate simplifies this task. As more of the application is converted to use Spring components the benefits of the framework will become more apparent.

Footnotes

[1] Inversion of Control creates the objects for the developer, so they do not have to be created manually. IoC allows you to define properties of an object in an XML and inject properties into objects either through a constructor argument or through setter methods. This allows properties to be modified via an XML file rather then making changes throughout various classes.

[2] Aspect Oriented Programming is the encapsulation of cross-cutting concerns (those that end up scattered all over the program). This keeps essential aspects of a program (such as logging or security) separate from other parts of the program.

[3] Model-View-Controller (MVC) is an architecture for developing Web Applications that separates an applications data (Model), user interface (View), and controlling logic (Controller) into three components.

[4] Stateless Beans are shared objects that do not have an associated state; this allows concurrent access to the beans.

[5] Stateful Beans are shared object that have an associated state. Access to such beans is limited to one client.

[6] POJO: Plain Old Java Class.

[7] JavaServer Pages Standard Tag Library (JSTL) extends the JavaServer Page (JSP) specification by adding various tags for performing common functions.

More information

About the authors

Greg Lapouchnian (glapouch@redhat.com) and Patrick Smith (pasmith@redhat.com) are both engineering interns in the Toronto office and students at the University of Toronto. They have spent the majority of their internship working on web applications.