Like Struts, Spring can also function as an MVC implementation. Both frameworks have their merits and drawbacks, although most would agree that Struts is still king when it comes to MVC. Many development teams have learned to rely on Struts as the foundation for building quality software under strict deadlines. With so much momentum behind Struts, even development teams that would like to integrate features of the Spring framework don't want to switch to Spring MVC. The good news is that you don't have to. The Spring architecture allows you to connect Struts as your Web framework to Spring-based business and persistence layers. The end result is that you can have your cake and eat it too!
In the recipes that follow, you'll learn three ways to integrate Struts MVC into the Spring framework. I'll expose the cons of each recipe as well as its comparative advantages. Once you've seen all three in action, I'll show you an exciting application of the approach I like best.
Three little recipes
Each of the following integration techniques (or recipes) has its merits, as well as its own particular quirks. I'm partial to only one of them, but knowing them all will deepen your understanding of both Struts and Spring. It will also provide you with a broad range of options for dealing with various scenarios. The recipes are as follows:
1. Use Spring's ActionSupport class to integrate Struts
2. Override the Struts RequestProcessor with Spring's DelegatingRequestProcessor
3. Delegate Struts Action management to the Spring framework
Loading the application context:
No matter which technique you use, you will need to use the Spring ContextLoaderPlugin to load the Spring application context for the Struts ActionServlet. Simply add the plug-in to your struts-config.xml file as you would any other plug-in, as shown here:
As previously mentioned, you'll find the complete source for the three fully functional example applications in the Download section. Each example presents a different approach to combining Struts and Spring for a book-search application. You can follow the basics of the examples here, but download the applications to see all the nitty-gritty details!
Recipe 1. Use Spring's ActionSupport:
Creating a Spring context manually is the most intuitive way to integrate Struts with Spring. To make it even easier, Spring offers a little help. The org.springframework.web.struts.ActionSupport class provides a getWebApplicationContext() method to easily obtain a Spring context. All you need to do is extend your action from Spring's ActionSupport instead of the Struts Action class, as shown in Listing 1:Listing 1. Using ActionSupport to integrate Struts
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import org.springframework.context.ApplicationContext;
import org.springframework.web.struts.ActionSupport;
import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;
public class SearchSubmit extends ActionSupport { (1)
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form;
String isbn = (String) searchForm.get("isbn");
//the old fashion way
//BookService bookService = new BookServiceImpl();
ApplicationContext ctx =
getWebApplicationContext(); (2)
BookService bookService =
(BookService) ctx.getBean("bookService"); (3)
Book book = bookService.read(isbn.trim());
if (null == book) {
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,new ActionError
("message.notfound"));
saveErrors(request, errors);
return mapping.findForward("failure") ;
}
request.setAttribute("book", book);
return mapping.findForward("success");
}
}
Let's quickly consider what's happening here. At (1), I create an Action by extending from the Spring ActionSupport class rather than the Struts Action class. At (2), I use the getWebApplicationContext() method to obtain an ApplicationContext. To obtain the business service, I use the context obtained at (2) to look up a Spring bean at (3).
This technique is simple and easy to understand. Unfortunately, it couples the Struts action to the Spring framework. If you ever decide to replace Spring, you would have to rewrite the code. Moreover, because the Struts action isn't under Spring's control, it can't reap the benefits of Spring AOP. This technique may be useful when using multiple independent Spring contexts, but for the most part it's not as desirable a solution as the other two choices.
Recipe 2. Override the RequestProcessor:
Decoupling Spring from the Struts action is a much smarter design choice. One way to do this is to override the Struts RequestProcessor processor with the org.springframework.web.struts.DelegatingRequestProcessor class, as shown in Listing 2:Listing 2. Integration via Spring's DelegatingRequestProcessor
Here, I've used the
Note that at (1), I've registered a bean using the name attribute to match the struts-config action mapping name. The SearchSubmit action exposes a JavaBean property, allowing Spring to populate the property at run time, as shown in Listing 4:Listing 4. A Struts action with a JavaBean property
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;
public class SearchSubmit extends Action {
private BookService bookService;
public BookService getBookService() {
return bookService;
}
public void setBookService(BookService bookService) { (1)
this.bookService = bookService;
}
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form;
String isbn = (String) searchForm.get("isbn");
Book book = getBookService().read(isbn.trim()); (2)
if (null == book) {
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,new ActionError("message.notfound"));
saveErrors(request, errors);
return mapping.findForward("failure") ;
}
request.setAttribute("book", book);
return mapping.findForward("success");
}
}
In Listing 4, you can see how to build the Struts action. At (1), I create a JavaBean property. This property is automatically populated by the DelegatingRequestProcessor. This design protects the Struts action from knowing it's being managed by Spring while giving you all the benefits of Spring's action management framework. Because your Struts actions are oblivious to the existence of Spring, you can swap out Spring for some other inversion of control container without refactoring your Struts code.
While the DelegatingRequestProcessor approach is definitely better than the first one, it does have some problems. If you were using a different RequestProcessor, then you would need to integrate the Spring DelegatingRequestProcessor manually. The added code would become a maintenance hassle and would also reduce your application's flexibility going forward. Moreover, there has been some talk of replacing the Struts RequestProcessor with a chain of command. Such a change would negatively impact the longevity of this solution.
Recipe 3. Delegate action management to Spring:
A much better solution is to delegate Struts action management to the Spring framework. You can do this by registering a proxy in the struts-config action mapping. The proxy is responsible for looking up the Struts action in the Spring context. Because the action is under Spring's control, it populates the action's JavaBean properties and leaves the door open to applying features such as Spring's AOP interceptors.
In Listing 5, the Action class is the same as it was in Listing 4. However, the struts-config is a little different:Listing 5. The delegation method of Spring integration
Listing 5 is a typical struts-config.xml file, except for one small difference. Instead of declaring the action's class name, it registers the name of Spring's proxy class, as shown at (1). The DelegatingActionProxy class uses the action mapping name to look up the action in the Spring context. This is the context that was declared with ContextLoaderPlugIn.
Registering a Struts action as a Spring bean is very straightforward, as shown in Listing 6. I simply create a bean using the name of the action mapping using the
The benefits of action delegation:
The action-delegation solution is the best of the three. The Struts action has no knowledge of Spring and could be used in non-Spring applications without changing a single line of code. It's not at the mercy of a change to the RequestProcessor, and it can take advantage of Spring's AOP features.
The benefits of action delegation don't stop there, either. Once you have your Struts action under Spring's control, you can leverage Spring to give them more pizzazz. For example, without Spring, all Struts actions must be threadsafe. If you set the
sources can be found in the original article:
http://www.ibm.com/developerworks/java/library/j-sr2.html
1 comment:
sources can be found in the original article:
http://www.ibm.com/developerworks/java/library/j-sr2.html
Post a Comment