Recipe10.6.Integrating Spring with Struts


Recipe 10.6. Integrating Spring with Struts

Problem

You've heard about the Spring framework and want to use it with your Struts application.

Solution

There is no better way of learning than by doing. This recipe shows you how to apply Spring to the struts-example web application.

Discussion

There are many ways to use Spring. The Solution shown here uses Spring for two main purposes:

  • To configure portions of the model

  • To inject model dependencies into Struts Actions

    Spring is an active project. You may find that APIs, filenames, and XML schemas have changed since this book was published; however, the basic process should be about the same.


Here's what you need to do:

  1. Download the Spring framework from http://www.springframework.org. At the time of this writing, Version 1.1 had just been released.

  2. Download the Struts 1.2.4 source distribution.

  3. Copy the Struts libraries, struts-example classes, struts-example web resources, and struts-example configuration files into a web application directory structure.

  4. Copy the spring.jar file from the downloaded Spring framework into the web-application's WEB-INF/lib folder.

Now that you've got these mundane preliminaries out of the way, you can get to the gist of Spring. The first thing you are going to do is change the MemoryDatabase used by the struts-example to be managed and loaded by Spring instead of by a Struts plug-in.

In the struts-example, the MemoryDatabasePlugIn loads and opens the MemoryUserDatabase. To use Spring instead of this plug-in, you need to make a minor change to the MemoryUserDatabase source. Change the open( ) method to read the input file from the classpath instead of the filesystem. This makes the solution easier to deploy. Here's the revised open() method with the two modified lines shown in bold.

public void open(  ) throws Exception {     InputStream fis = null;     BufferedInputStream bis = null;     try {         // Acquire an input stream to our database file         if (log.isDebugEnabled( )) {             log.debug("Loading database from '" + pathname + "'");         }         fis = this.getClass( ).getResourceAsStream(pathname);         bis = new BufferedInputStream(fis);         // Construct a digester to use for parsing         Digester digester = new Digester( );         digester.push(this);         digester.setValidating(false);         digester.addFactoryCreate             ("database/user",              new MemoryUserCreationFactory(this));         digester.addFactoryCreate             ("database/user/subscription",              new MemorySubscriptionCreationFactory( ));         // Parse the input stream to initialize our database         digester.parse(bis);         bis.close( );         bis = null;         fis = null;     } catch (Exception e) {         log.error("Loading database from '" + pathname + "':", e);         throw e;     } finally {         if (bis != null) {             try {                 bis.close( );             } catch (Throwable t) {                 ;             }             bis = null;             fis = null;         }     } }

This change enables the MemoryUserDatabase to require the MemoryDatabasePlugin no longer. With this revised version of the MemoryUserDatabase, all you have to do is relocate the database.xml file from the WEB-INF directory to the web application's classes folder. Move database.xml to the subdirectory of WEB-INF/classes that contains the MemoryUserDatabase.class file (WEB-INF/classes/org/apache/struts/webapp/example/memory).

Change the MemoryUserDatabase to be managed by Spring instead of the plug-in. Remove the MemoryDatabasePlugIn configuration from the struts-config.xml; it's no longer needed. Create the applicationContext-struts.xml file in the WEB-INF folder as shown in Example 10-18.

Example 10-18. Spring configuration for the MemoryUserDatabase
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean                            init-method="open"            destroy-method="close">         <property name="pathname">             <value>database.xml</value>         </property>     </bean> </beans>

This spring-beans configuration file is the heart of Spring. The first bean element, shown in Example 10-18, configures the MemoryUserDatabase. The nested property element specifies a dependency that will be injected after the MemoryUserDatabase class is constructed. In this case, the pathname property is set to the path of the XML file containing the list of users for the struts-example application. After Spring instantiates the class (using its default constructor), property values are set based on the nested property elements. The init-method and destroy-method attributes allow you to control the lifecycle of the class. The init-method attribute specifies the name of a method that will be called once property values have been set. The method, named by the destroy-method attribute, will be called when the object is evicted from Spring's application context.

For Spring to work, it has to load the XML files that it will use to create the managed bean objects. In addition, each created object must be stored in Spring's application context. For Struts applications, Spring comes with a Struts plug-in that performs these tasks. Here's the plug-in element used for this Solution:

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

Like other plug-ins, the ContextLoaderPlugin can be passed through a comma-separated list of configuration files. For the Solution, only a single configuration file is used, the XML file shown in Example 10-18.

Recall that the MemoryDatabasePlugin instantiated and opened the MemoryUserDatabase, storing the created instance in the servlet context under a well-known key value. Since you are no longer using the plug-in, you can change how the Actions acquire a reference to the MemoryUserDatabase.

The "Spring-way" of doing this is to let Spring control the creation of the Actions. In a non-Spring Struts application, Actions are created by the RequestProcessor. When the RequestProcessor receives a request for a given path, it determines your corresponding action configuration, instantiates the specified Action class, and calls Action.execute( ). So, how do you let Spring control the creation of the Actions instead of the RequestProcessor?

The answer is the DelegatingActionProxy. This class, originally created as part of the original Struts Spring Plug-in effort led by Don Brown, is a general-purpose Action that proxies requests to custom Actions. The proxy action determines your custom Action associated with a request by matching the action path with a bean name in Spring's configuration file. At runtime, Spring instantiates your custom Action and injects any required dependencies.

The Actions provided in the struts-example extend from a BaseAction that provides a helper method for acquiring the UserDatabase:

protected UserDatabase getUserDatabase(HttpServletRequest request) {     return (UserDatabase) servlet.getServletContext(  ).getAttribute(             Constants.DATABASE_KEY); }

This method retrieves the UserDatabase from the servlet context, placed there by the MemoryDatabasePlugin. This is the kind of dependency that Spring was made to handle. Since the UserDatabase is a Spring-managed object, you can replace this method, which requires the servlet request, with a common property getter method. The getter method returns the UserDatabase from an instance variable of the the BaseAction. You will create a setter method that sets the value of that instance variable.

Hold the phone! Did I say create an instance variable in a Struts Action? Doesn't this violate thread safety (see Recipe 6.4)? If you use the DelegatingActionProxy shown here, you don't need to worry about thread safety for the Actions. Spring will create a new Action instance for every request; alleviating thread-safety concerns.


Spring calls the setter method when an Action instance is created. Subclasses of the BaseAction call the getter method to retrieve the UserDatabase. Each Action that needs the UserDatabase is configured with this dependency via a Spring configuration file. The complete applicationContext-struts.xml file with the new entries for each Action is shown in Example 10-19.

Example 10-19. Spring configuration including Actions
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"   "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean                            init-method="open"            destroy-method="close">         <property name="pathname">             <value>database.xml</value>         </property>     </bean>     <bean name="/Welcome"           >         <property name="userDatabase">             <ref bean="userDatabase"/>         </property>     </bean>     <bean name="/SaveSubscription"           >         <property name="userDatabase">             <ref bean="userDatabase"/>         </property>     </bean>     <bean name="/SaveRegistration"           >         <property name="userDatabase">             <ref bean="userDatabase"/>         </property>     </bean>          <bean name="/SubmitLogon"           >         <property name="userDatabase">             <ref bean="userDatabase"/>         </property>     </bean> </beans>

The last step is to change the action elements in the struts-config.xml file to use the DelegatingActionProxy instead of the Actions directly. For example, here's the new action mapping for the /SubmitLogon path. The only thing that changed was the type attribute, which refers to the Spring-provided DelegatingActionProxy:

<action  path="/SubmitLogon"          type="org.springframework.web.struts.DelegatingActionProxy"          name="LogonForm"         scope="request"         input="logon">     <exception key="expired.password"               type="org.apache.struts.webapp.example.ExpiredPasswordException"               path="/ExpiredPassword.do"/> </action>

Once you understand how Spring works, it makes programming easier. In fact, it's probably harder to convert existing code to use Spring than writing new code. When you write new Actions and business services, you won't need to worry about breaking out dependencies like you had to do with the UserDatabase in this recipe. Most of your objects will be plain-old-Java-objects (POJOs) that won't have any hardcoded dependencies on other objects. You wire your objects together in your spring-beans.xml file, and you're ready.

See Also

The original web site for the Struts Spring plug-in can be found at http://struts.sourceforge.net/struts-spring/. For the latest on the Spring framework, including its built-in support for Struts, go to http://www.springframework.org.



    Jakarta Struts Cookbook
    Jakarta Struts Cookbook
    ISBN: 059600771X
    EAN: 2147483647
    Year: 2005
    Pages: 200

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net