Chapter 15. Integrating with other web frameworks

15.1. Introduction

Spring can be easily integrated into any Java-based web framework. All you need to do is to declare the ContextLoaderListener in your web.xml and use a contextConfigLocation <context-param> to set which context files to load.

The <context-param>:

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

The <listener>:

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

NOTE: Listeners were added to the Servlet API in version 2.3. If you have a Servlet 2.2 container, you can use the ContextLoaderServlet to achieve this same functionality.

If you don't specify the contextConfigLocation context parameter, the ContextLoaderListener will look for a /WEB-INF/applicationContext.xml file to load. Once the context files are loaded, Spring creates a WebApplicationContext object based on the bean definitions and puts it into the ServletContext.

All Java web frameworks are built on top of the Servlet API, so you can use the following code to get the ApplicationContext that Spring created.

WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

The WebApplicationContextUtils class is for convenience, so you don't have to remember the name of the ServletContext attribute. Its getWebApplicationContext() method will return null if an object doesn't exist under the WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE key. Rather than risk getting NullPointerExceptions in your application, it's better to use the getRequiredWebApplicationContext() method. This method throws an Exception when the ApplicationContext is missing.

Once you have a reference to the WebApplicationContext, you can retrieve beans by their name or type. Most developers retrieve beans by name, then cast them to one of their implemented interfaces.

Fortunately, most of the frameworks in this section have simpler ways of looking up beans. Not only do they make it easy to get beans from the BeanFactory, but they also allow you to use dependency injection on their controllers. Each framework section has more detail on its specific integration strategies.

15.2. JavaServer Faces

JavaServer Faces (JSF) is a component-based, event-driven web framework. According to Sun Microsystem's JSF Overview, JSF technology includes:

  • A set of APIs for representing UI components and managing their state, handling events and input validation, defining page navigation, and supporting internationalization and accessibility.

  • A JavaServer Pages (JSP) custom tag library for expressing a JavaServer Faces interface within a JSP page.

15.2.1. DelegatingVariableResolver

The easiest way to integrate your Spring middle-tier with your JSF web layer is to use the DelegatingVariableResolver class. To configure this variable resolver in your application, you'll need to edit your faces-context.xml. After the opening <faces-config> element, add an <application> element and a <variable-resolver> element within it. The value of the variable resolver should reference Spring's DelegatingVariableResolver:

<faces-config>
  <application>
	  <variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
	  <locale-config>
	    <default-locale>en</default-locale>
	    <supported-locale>en</supported-locale>
	    <supported-locale>es</supported-locale>
	  </locale-config>
	  <message-bundle>messages</message-bundle>
	</application>

By specifying Spring's variable resolver, you can configure Spring beans as managed properties of your managed beans. The DelegatingVariableResolver will first delegate value lookups to the default resolver of the underlying JSF implementation, and then to Spring's root WebApplicationContext. This allows you to easily inject dependencies into your JSF-managed beans.

Managed beans are defined in your faces-config.xml file. Below is an example where #{userManager} is a bean that's retrieved from Spring's BeanFactory.

<managed-bean>
  <managed-bean-name>userList</managed-bean-name>
	<managed-bean-class>com.whatever.jsf.UserList</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
    <property-name>userManager</property-name>
    <value>#{userManager}</value>
  </managed-property>
</managed-bean>

The DelegatingVariableResolver is the recommended strategy for integrating JSF and Spring. If you're looking for more robust integration features, you might take a look at the JSF-Spring project.

15.2.2. FacesContextUtils

A custom VariableResolver works well when mapping your properties to beans in faces-config.xml, but at times you may need to grab a bean explicitly. The FacesContextUtils class makes this easy. It's similar to WebApplicationContextUtils, except that it takes a FacesContext parameter rather than a ServletContext parameter.

ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());

15.3. Struts

Struts is the de facto web framework for Java applications, mainly because it was one of the first to be released (June 2001). Invented by Craig McClanahan, Struts is an open source project hosted by the Apache Software Foundation. At the time, it greatly simplified the JSP/Servlet programming paradigm and won over many developers who were using proprietary frameworks. It simplified the programming model, it was open source, and it had a large community, which allowed the project to grow and become popular among Java web developers.

To integrate your Struts application with Spring, you have two options:

  • Configure Spring to manage your Actions as beans, using the ContextLoaderPlugin, and set their dependencies in a Spring context file.

  • Subclass Spring's ActionSupport classes and grab your Spring-managed beans explicitly using a getWebApplicationContext() method.

15.3.1. ContextLoaderPlugin

The ContextLoaderPlugin is a Struts 1.1+ plug-in that loads a Spring context file for the Struts ActionServlet. This context refers to the root WebApplicationContext (loaded by the ContextLoaderListener) as its parent. The default name of the context file is the name of the mapped servlet, plus -servlet.xml. If ActionServlet is defined in web.xml as <servlet-name>action</servlet-name>, the default is /WEB-INF/action-servlet.xml.

To configure this plug-in, add the following XML to the plug-ins section near the bottom of your struts-config.xml file:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/>

The location of the context configuration files can be customized using the "contextConfigLocation" property.

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
  <set-property property="contextConfigLocation"
      value="/WEB-INF/action-servlet.xml.xml,/WEB-INF/applicationContext.xml"/>
</plug-in>

It is possible to use this plugin to load all your context files, which can be useful when using testing tools like StrutsTestCase. StrutsTestCase's MockStrutsTestCase won't initialize Listeners on startup so putting all your context files in the plugin is a workaround. A bug has been filed for this issue.

After configuring this plug-in in struts-config.xml, you can configure your Action to be managed by Spring. Spring 1.1.3 provides two ways to do this:

  • Override Struts' default RequestProcessor with Spring's DelegatingRequestProcessor.

  • Use the DelegatingActionProxy class in the type attribute of your <action-mapping>.

Both of these methods allow you to manage your Actions and their dependencies in the action-context.xml file. The bridge between the Action in struts-config.xml and action-servlet.xml is built with the action-mapping's "path" and the bean's "name". If you have the following in your struts-config.xml file:

<action path="/users" .../>

You must define that Action's bean with the "/users" name in action-servlet.xml:

<bean name="/users" .../>

15.3.1.1. DelegatingRequestProcessor

To configure the DelegatingRequestProcessor in your struts-config.xml file, override the "processorClass" property in the <controller> element. These lines follow the <action-mapping> element.

<controller>
  <set-property property="processorClass"
      value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>

After adding this setting, your Action will automatically be looked up in Spring's context file, no matter what the type. In fact, you don't even need to specify a type. Both of the following snippets will work:

<action path="/user" type="com.whatever.struts.UserAction"/>		
	<action path="/user"/>

If you're using Struts' modules feature, your bean names must contain the module prefix. For example, an action defined as <action path="/user"/> with module prefix "admin" requires a bean name with <bean name="/admin/user"/>.

NOTE: If you're using Tiles in your Struts application, you must configure your <controller> with the DelegatingTilesRequestProcessor.

15.3.1.2. DelegatingActionProxy

If you have a custom RequestProcessor and can't use the DelegatingTilesRequestProcessor, you can use the DelegatingActionProxy as the type in your action-mapping.

<action path="/user" type="org.springframework.web.struts.DelegatingActionProxy"
    name="userForm" scope="request" validate="false" parameter="method">
  <forward name="list" path="/userList.jsp"/>
  <forward name="edit" path="/userForm.jsp"/>
</action>

The bean definition in action-servlet.xml remains the same, whether you use a custom RequestProcessor or the DelegatingActionProxy.

If you define your Action in a context file, the full feature set of Spring's bean container will be available for it: dependency injection as well as the option to instantiate a new Action instance for each request. To activate the latter, add singleton="false" to your Action's bean definition.

<bean name="/user" singleton="false" autowire="byName"
    class="org.example.web.UserAction"/>

15.3.2. ActionSupport Classes

As previously mentioned, you can retrieve the WebApplicationContext from the ServletContext using the WebApplicationContextUtils class. An easier way is to extend Spring's Action classes for Struts. For example, instead of subclassing Struts' Action class, you can subclass Spring's ActionSupport class.

The ActionSupport class provides additional convenience methods, like getWebApplicationContext(). Below is an example of how you might use this in an Action:

public class UserAction extends DispatchActionSupport {

    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("entering 'delete' method...");
        }

        WebApplicationContext ctx = getWebApplicationContext();
        UserManager mgr = (UserManager) ctx.getBean("userManager");

        // talk to manager for business logic

        return mapping.findForward("success");
    }
}

Spring includes subclasses for all of the standard Struts Actions - the Spring versions merely have Support appended to the name:

The recommended strategy is to use the approach that best suits your project. Subclassing makes your code more readable, and you know exactly how your dependencies are resolved. However, using the ContextLoaderPlugin allow you to easily add new dependencies in your context XML file. Either way, Spring provides some nice options for integrating the two frameworks.

15.4. Tapestry

Tapestry is a powerful, component-oriented web application framework from Apache's Jakarta project (http://jakarta.apache.org/tapestry). While Spring has its own powerful web ui layer, there are a number of unique advantages to building a J2EE application using a combination of Tapestry for the web ui, and the Spring container for the lower layers. This document attempts to detail a few best practices for combining these two frameworks. It is expected that you are relatively familiar with both Tapestry and Spring Framework basics, so they will not be explained here. General introductory documentation for both Tapestry and Spring Framework are available on their respective web sites.

15.4.1. Architecture

A typical layered J2EE application built with Tapestry and Spring will consist of a top UI layer built with Tapestry, and a number of lower layers, hosted out of one or more Spring Application Contexts.

  • User Interface Layer:

    - concerned with the user interface

    - contains some application logic

    - provided by Tapestry

    - aside from providing UI via Tapestry, code in this layer does its work via objects which implement interfaces from the Service Layer. The actual objects which implement these service layer interfaces are obtained from a Spring Application Context.

  • Service Layer:

    - application specific 'service' code

    - works with domain objects, and uses the Mapper API to get those domain objects into and out of some sort of repository (database)

    - hosted in one or more Spring contexts

    - code in this layer manipulates objects in the domain model, in an application specific fashion. It does its work via other code in this layer, and via the Mapper API. An object in this layer is given the specific mapper implementations it needs to work with, via the Spring context.

    - since code in this layer is hosted in the Spring context, it may be transactionally wrapped by the Spring context, as opposed to managing its own transactions

  • Domain Model:

    - domain specific object hierarchy, which deals with data and logic specific to this domain

    - although the domain object hierarchy is built with the idea that it is persisted somehow and makes some general concessions to this (for example, bidirectional relationships), it generally has no knowledge of other layers. As such, it may be tested in isolation, and used with different mapping implementations for production vs. testing.

    - these objects may be standalone, or used in conjunction with a Spring application context to take advantage of some of the benefits of the context, e.g., isolation, inversion of control, different strategy implementations, etc.

  • Data Source Layer:

    - Mapper API (also called Data Access Objects): an API used to persist the domain model to a repository of some sort (generally a DB, but could be the filesystem, memory, etc.)

    - Mapper API implementations: one or more specific implementations of the Mapper API, for example, a Hibernate-specific mapper, a JDO-specific mapper, JDBC-specific mapper, or a memory mapper.

    - mapper implementations live in one or more Spring Application Contexts. A service layer object is given the mapper objects it needs to work with via the context.

  • Database, filesystem, or other repositories:

    - objects in the domain model are stored into one or more repositories via one or more mapper implementations

    - a repository may be very simple (e.g. filesystem), or may have its own representation of the data from the domain model (i.e. a schema in a db). It does not know about other layers howerver.

15.4.2. Implementation

The only real question (which needs to be addressed by this document), is how Tapestry pages get access to service implementations, which are simply beans defined in an instance of the Spring Application Context.

15.4.2.1. Sample application context

Assume we have the following simple Application Context definition, in xml form:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
        "http://www.springframework.org/dtd/spring-beans.dtd">
 
<beans>
 
    <!-- ========================= GENERAL DEFINITIONS ========================= -->
 
    <!-- ========================= PERSISTENCE DEFINITIONS ========================= -->
 
    <!-- the DataSource -->
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName"><value>java:DefaultDS</value></property>
        <property name="resourceRef"><value>false</value></property>
    </bean>
 
    <!-- define a Hibernate Session factory via a Spring LocalSessionFactoryBean -->
    <bean id="hibSessionFactory" 
        class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="dataSource"><ref bean="dataSource"/></property>
    </bean>
 
    <!--
     - Defines a transaction manager for usage in business or data access objects.
     - No special treatment by the context, just a bean instance available as reference
     - for business objects that want to handle transactions, e.g. via TransactionTemplate.
     -->
    <bean id="transactionManager" 
        class="org.springframework.transaction.jta.JtaTransactionManager">
    </bean>
 
    <bean id="mapper" 
        class="com.whatever.dataaccess.mapper.hibernate.MapperImpl">
        <property name="sessionFactory"><ref bean="hibSessionFactory"/></property>
    </bean>
   
    <!-- ========================= BUSINESS DEFINITIONS ========================= -->
 
    <!-- AuthenticationService, including tx interceptor -->
    <bean id="authenticationServiceTarget"
        class="com.whatever.services.service.user.AuthenticationServiceImpl">
        <property name="mapper"><ref bean="mapper"/></property>
    </bean>
    <bean id="authenticationService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref bean="transactionManager"/></property>
        <property name="target"><ref bean="authenticationServiceTarget"/></property>
        <property name="proxyInterfacesOnly"><value>true</value></property>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>  
 
    <!-- UserService, including tx interceptor -->
    <bean id="userServiceTarget"
        class="com.whatever.services.service.user.UserServiceImpl">
        <property name="mapper"><ref bean="mapper"/></property>
    </bean>
    <bean id="userService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref bean="transactionManager"/></property>
        <property name="target"><ref bean="userServiceTarget"/></property>
        <property name="proxyInterfacesOnly"><value>true</value></property>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>  
 
 </beans>

Inside the Tapestry application, we need to load this application context, and allow Tapestry pages to get the authenticationService and userService beans, which implement the AuthenticationService and UserService interfaces, respectively.

15.4.2.2. Obtaining beans in Tapestry pages

At this point, the application context is available to a web application by calling Spring's static utility function WebApplicationContextUtils.getApplicationContext(servletContext), where servletContext is the standard ServletContext from the J2EE Servlet specification. As such, one simple mechanism for a page to get an instance of the UserService, for example, would be with code such as:

    WebApplicationContext appContext = WebApplicationContextUtils.getApplicationContext(
        getRequestCycle().getRequestContext().getServlet().getServletContext());
    UserService userService = (UserService) appContext.getBean("userService");
    ... some code which uses UserService

This mechanism does work. It can be made a lot less verbose by encapsulating most of the functionality in a method in the base class for the page or component. However, in some respects it goes against the Inversion of Control approach which Spring encourages, which is being used in other layers of this app, in that ideally you would like the page to not have to ask the context for a specific bean by name, and in fact, the page would ideally not know about the context at all.

Luckily, there is a mechanism to allow this. We rely upon the fact that Tapestry already has a mechanism to declaratively add properties to a page, and it is in fact the preferred approach to manage all properties on a page in this declarative fashion, so that Tapestry can properly manage their lifecycle as part of the page and component lifecycle.

15.4.2.3. Exposing the application context to Tapestry

First we need to make the ApplicationContext available to the Tapestry page or Component without having to have the ServletContext; this is because at the stage in the page's/component's lifecycle when we need to access the ApplicationContext, the ServletContext won't be easily available to the page, so we can't use WebApplicationContextUtils.getApplicationContext(servletContext) directly. One way is by defining a custom version of the Tapestry IEngine which exposes this for us:

package com.whatever.web.xportal;
...
import ...
...
public class MyEngine extends org.apache.tapestry.engine.BaseEngine {
 
    public static final String APPLICATION_CONTEXT_KEY = "appContext";
 
    /**
     * @see org.apache.tapestry.engine.AbstractEngine#setupForRequest(org.apache.tapestry.request.RequestContext)
     */
    protected void setupForRequest(RequestContext context) {
        super.setupForRequest(context);
     
        // insert ApplicationContext in global, if not there
        Map global = (Map) getGlobal();
        ApplicationContext ac = (ApplicationContext) global.get(APPLICATION_CONTEXT_KEY);
        if (ac == null) {
            ac = WebApplicationContextUtils.getWebApplicationContext(
                context.getServlet().getServletContext()
            );
            global.put(APPLICATION_CONTEXT_KEY, ac);
        }
    }
}

This engine class places the Spring Application Context as an attribute called "appContext" in this Tapestry app's 'Global' object. Make sure to register the fact that this special IEngine instance should be used for this Tapestry application, with an entry in the Tapestry application definition file. For example:

file: xportal.application:
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC 
    "-//Apache Software Foundation//Tapestry Specification 3.0//EN" 
    "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application
    name="Whatever xPortal"
    engine-class="com.whatever.web.xportal.MyEngine">
</application>

15.4.2.4. Component definition files

Now in our page or component definition file (*.page or *.jwc), we simply add property-specification elements to grab the beans we need out of the ApplicationContext, and create page or component properties for them. For example:

    <property-specification name="userService"
                            type="com.whatever.services.service.user.UserService">
        global.appContext.getBean("userService")
    </property-specification>
    <property-specification name="authenticationService"
                            type="com.whatever.services.service.user.AuthenticationService">
        global.appContext.getBean("authenticationService")
    </property-specification>

The OGNL expression inside the property-specification specifies the initial value for the property, as a bean obtained from the context. The entire page definition might look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC 
    "-//Apache Software Foundation//Tapestry Specification 3.0//EN" 
    "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
     
<page-specification class="com.whatever.web.xportal.pages.Login">
 
    <property-specification name="username" type="java.lang.String"/>
    <property-specification name="password" type="java.lang.String"/>
    <property-specification name="error" type="java.lang.String"/>
    <property-specification name="callback" type="org.apache.tapestry.callback.ICallback" persistent="yes"/>
    <property-specification name="userService"
                            type="com.whatever.services.service.user.UserService">
        global.appContext.getBean("userService")
    </property-specification>
    <property-specification name="authenticationService"
                            type="com.whatever.services.service.user.AuthenticationService">
        global.appContext.getBean("authenticationService")
    </property-specification>
   
    <bean name="delegate" class="com.whatever.web.xportal.PortalValidationDelegate"/>
 
    <bean name="validator" class="org.apache.tapestry.valid.StringValidator" lifecycle="page">
        <set-property name="required" expression="true"/>
        <set-property name="clientScriptingEnabled" expression="true"/>
    </bean>
 
    <component id="inputUsername" type="ValidField">
        <static-binding name="displayName" value="Username"/>
        <binding name="value" expression="username"/>
        <binding name="validator" expression="beans.validator"/>
    </component>
   
    <component id="inputPassword" type="ValidField">
        <binding name="value" expression="password"/>
       <binding name="validator" expression="beans.validator"/>
       <static-binding name="displayName" value="Password"/>
       <binding name="hidden" expression="true"/>
    </component>
 
</page-specification>

15.4.2.5. Adding abstract accessors

Now in the Java class definition for the page or component itself, all we need to do is add an abstract getter method for the properties we have defined, to access them. When the page or component is actually loaded by Tapestry, it performs runtime code instrumentation on the classfile to add the properties which have been defined, and hook up the abstract getter methods to the newly created fields. For example:

    // our UserService implementation; will come from page definition
    public abstract UserService getUserService();
    // our AuthenticationService implementation; will come from page definition
    public abstract AuthenticationService getAuthenticationService();

For completeness, the entire Java class, for a login page in this example, might look like this:

package com.whatever.web.xportal.pages;
 
/**
 *  Allows the user to login, by providing username and password.
 *  After successfully logging in, a cookie is placed on the client browser
 *  that provides the default username for future logins (the cookie
 *  persists for a week).
 */
public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
 
    /** the key under which the authenticated user object is stored in the visit as */
    public static final String USER_KEY = "user";
   
    /**
     * The name of a cookie to store on the user's machine that will identify
     * them next time they log in.
     **/
    private static final String COOKIE_NAME = Login.class.getName() + ".username";  
    private final static int ONE_WEEK = 7 * 24 * 60 * 60;
 
    // --- attributes
 
    public abstract String getUsername();
    public abstract void setUsername(String username);
 
    public abstract String getPassword();
    public abstract void setPassword(String password);
 
    public abstract ICallback getCallback();
    public abstract void setCallback(ICallback value);
    
    public abstract UserService getUserService();
 
    public abstract AuthenticationService getAuthenticationService();
 
    // --- methods
 
    protected IValidationDelegate getValidationDelegate() {
        return (IValidationDelegate) getBeans().getBean("delegate");
    }
 
    protected void setErrorField(String componentId, String message) {
        IFormComponent field = (IFormComponent) getComponent(componentId);
        IValidationDelegate delegate = getValidationDelegate();
        delegate.setFormComponent(field);
        delegate.record(new ValidatorException(message));
    }
 
    /**
     *  Attempts to login. 
     *
     *  <p>If the user name is not known, or the password is invalid, then an error
     *  message is displayed.
     *
     **/
    public void attemptLogin(IRequestCycle cycle) {
     
        String password = getPassword();
 
        // Do a little extra work to clear out the password.
 
        setPassword(null);
        IValidationDelegate delegate = getValidationDelegate();
 
        delegate.setFormComponent((IFormComponent) getComponent("inputPassword"));
        delegate.recordFieldInputValue(null);
 
        // An error, from a validation field, may already have occurred.
 
        if (delegate.getHasErrors())
            return;
 
        try {
            User user = getAuthenticationService().login(getUsername(), getPassword());
           loginUser(user, cycle);
        }
        catch (FailedLoginException ex) {
            this.setError("Login failed: " + ex.getMessage());
            return;
        }
    }
 
    /**
     *  Sets up the [email protected] User} as the logged in user, creates
     *  a cookie for their username (for subsequent logins),
     *  and redirects to the appropriate page, or
     *  a specified page).
     *
     **/
    public void loginUser(User user, IRequestCycle cycle) {
     
        String username = user.getUsername();
 
        // Get the visit object; this will likely force the
        // creation of the visit object and an HttpSession.
 
        Map visit = (Map) getVisit();
        visit.put(USER_KEY, user);
 
        // After logging in, go to the MyLibrary page, unless otherwise
        // specified.
 
        ICallback callback = getCallback();
 
        if (callback == null)
            cycle.activate("Home");
        else
            callback.performCallback(cycle);
 
        // I've found that failing to set a maximum age and a path means that
        // the browser (IE 5.0 anyway) quietly drops the cookie.
 
        IEngine engine = getEngine();
        Cookie cookie = new Cookie(COOKIE_NAME, username);
        cookie.setPath(engine.getServletPath());
        cookie.setMaxAge(ONE_WEEK);
 
        // Record the user's username in a cookie
 
        cycle.getRequestContext().addCookie(cookie);
 
        engine.forgetPage(getPageName());
    }
   
    public void pageBeginRender(PageEvent event) {
        if (getUsername() == null)
            setUsername(getRequestCycle().getRequestContext().getCookieValue(COOKIE_NAME));
    }
}

15.4.3. Summary

In this example, we've managed to allow service beans defined in the Spring ApplicationContext to be provided to the page in a declarative fashion. The page class does not know where the service implementations are coming from, and in fact it is easy to slip in another implementation, for example, during testing. This inversion of control is one of the prime goals and benefits of the Spring Framework, and we have managed to extend it all the way up the J2EE stack in this Tapestry application.

15.5. WebWork

WebWork is a web framework designed with simplicity in mind. It's built on top of XWork, which is a generic command framework. XWork also has an IoC container, but it isn't as full-featured as Spring and won't be covered in this section. WebWork controllers are called Actions, mainly because they must implement the Action interface. The ActionSupport class implements this interface, and it is most common parent class for WebWork actions.

WebWork maintains its own Spring integration project, located on java.net in the xwork-optional project. Currently, three options are available for integrating WebWork with Spring:

  • SpringObjectFactory: override XWork's default ObjectFactory so XWork will look for Spring beans in the root WebApplicationContext.

  • ActionAutowiringInterceptor: use an interceptor to automatically wire an Action's dependencies as they're created.

  • SpringExternalReferenceResolver: look up Spring beans based on the name defined in an <external-ref> element of an <action> element.

All of these strategies are explained in further detail in WebWork's Documentation.