Leverage Spring Web development with Offspring

The Spring Framework is such a brilliant phenomenon of modern Java development that praising it has become common place. However, to start working on Spring projects, one has to invest significant time to obtain the necessary expertise. This article briefly introduces Spring essentials and describes Offspring, a fully functional solution that can help you build Web applications with a minimum learning curve and without compromising Spring ideology.

[@more@]

Spring bare essentials

To follow the course of this article, we must brush up on the two major notions employed by Spring—the Inversion of Control (IoC) paradigm and aspect-oriented programming (AOP). Also of significance is the Spring Web layer. By no means, do the following sections represent a substitute for serious reading on the Spring Framework; but they will get us started building our framework.

IoC

Let's assume we are assigned to design the class CoffeeShop, which obviously serves the instance of the CaffeinatedDrink to the customer:

 public class CoffeeShop  implements Shop{
   CaffeinatedDrink _Drink;
   public void setDrink(CaffeinatedDrink drink){
      _Drink=drink;
   }
   void serveDrink(){
      System.out.println(_Drink.getName()+
            " served. The drink temperature is "+_Drink.getTemperature()+" degrees F.");
   }
   public static void main(String[] args) {
      CoffeeShop shop=new CoffeeShop();
      CaffeinatedDrink drink=Coffeemaker.getInstance().makeCoffee();
      shop.setDrink(drink);
      shop.serveDrink();
   }
}


Let's also assume another team designed the Coffeemaker singleton (shop has only one coffeemaker), the CaffeinatedDrink interface, and the Coffee concrete class. We should be proud of ourselves and our object-oriented skills: we managed to decouple CoffeeShop and CaffeinatedDrink by hiding implementation details behind the interface, and we followed the Big Rule—program to interfaces, not implementations.

It's too soon to gloat! As a test case, we run the CoffeShop.main() method and receive the message Coffee served. The drink temperature is 160 degrees F. The coffee is too cold to justify the price. A war of words and corporate emails erupt. We argue that either the Coffeemaker is broken or the Coffee class implementation is faulty. Other teams insist nothing is wrong with their implementation; CoffeeShop is too slow to serve the hot drinks, and so on and so forth.

The core of the problem is that we did not test the CoffeShop class's pure functionality. Instead, we tested the functional relationship between CoffeeShop and the concrete class Coffee. Let's test our perfect CoffeeShop by serving boiled water instead of coffee. As a result, we eliminate the complexity of the Coffee instance from the equation:

 public class BoiledWater implements CaffeinatedDrink {
   int _Temperature=210; //  Drink temperature 
   String _Name="Boiled water"; // Drink name
   public String getName() {
      return _Name;
   }
   public int getTemperature() {
      return _Temperature;
   }
}


Now the coffeemaker should brew the water:

 public class Coffeemaker {
   static Coffeemaker _Coffemaker=null;
   public synchronized static Coffeemaker getInstance() {
      if(_Coffemaker==null){
         _Coffemaker=new Coffeemaker();
      }
      return _Coffemaker;
   }
   private Coffeemaker() {
   }
   public CaffeinatedDrink makeCoffee(){
      //Return new Coffee(); 
      return new BoiledWater(); //Just water, please
   }
}


We run again CoffeShop.main() and receive the message Boiled water served. The drink temperature is 210 degrees F. We served boiled water, as expected, proving that our store is fine, but at a substantial price of modifying and recompiling Coffeemaker twice; we now must change it back to brew the coffee instead of water. This price is substantiated by the fact that even if CoffeeShop does not deal with the drink's specific implementation, somebody still must create the concrete instance of Coffee. Whether that instance is created by a singleton, factory, or class loader, this dirty job is inevitable.

IoC in Spring wonderfully handles the whole situation. When we deploy Spring in our project, the slightly modified CoffeShop.main() method looks like this:

 public static void main(String[] args) {
        /* Instantiate the Spring context */

ApplicationContext context = new ClassPathXmlApplicationContext("coffeeshop.xml"); CoffeeShop shop=new CoffeeShop(); /* Spring container has instantiated the drink */ CaffeinatedDrink drink = (CaffeinatedDrink)context.getBean("drink"); shop.setDrink(drink); shop.serveDrink(); }


We have just instantiated the Spring context from the application-context file coffeeshop.xml, asked the Spring context to instantiate the drink object, called the shop's setter method, and served the drink. File coffeeshop.xml contains the definition of the bean drink:

 <?xml version="1.0" encoding="UTF-8"?>




Now to switch from coffee to boiled water, we just have to change the bean definition in the XML file: and nothing else. We can even do better. By modifying coffeeshop.xml's content, we can instruct the Spring container to instantiate not only the drink object, but also CoffeeShop itself, and even call the setDrink() method with an appropriate drink argument:

 


As a result, CoffeeShop.main() is simplified significantly:

 public static void main(String[] args) {
   /* Instantiate Spring context */
   ApplicationContext context =

new ClassPathXmlApplicationContext("coffeeshop.xml"); /* Spring container instantiated shop object and called setDrink method */ CoffeeShop shop=(CoffeeShop)context.getBean("shop"); shop.serveDrink(); }


A fundamental event has just quietly occurred: Inversion of Control, or Dependency Injection. CoffeeShop did not look up the dependent drink object, but the Spring container has provided (actually, injected) the dependent drink object into the CoffeeShop in reverse.

AOP

So the problem is fixed: the coffee is hot and the price is justified. Let the client pay. Traditionally, to do that, we must make a call to the billing system. Something like CashRegister.getInstance().chargeItem(drink.getName()) from the method serveDrink(). But this call is bad for many reasons:

  • Our team features the finest experts on how to serve coffee, but nobody is familiar with the billing issues
  • Code tuning and debugging become troubled again, since we introduce new functional dependency (see the boiled water example)
  • If our company decided to replace the billing subsystem, we would have to modify and recompile every billable event or build some adapters from the old APIs to the new ones

In terms of AOP, we have introduced a cross-cutting concern. Cross-cutting concerns (also called orthogonal services) are billing, notifications, logging—everything that is not a part of our core process or logic (serving coffee) and is cut across our system's multiple components.

Let's build a Spring AOP solution to overcome our shop's little problems. We do not modify the CoffeeShop class! Instead, Spring builds us its dynamic proxy. The whole application no longer deals with our CoffeeShop directly; it deals with its proxy. The CoffeeShop class has become a target class. All public methods of the target class's interface are dynamically available on the proxy with identical signatures. All calls to the proxy are directed transparently to the target class, unless we want to intercept them and apply our cross-cutting functionality (or, in AOP terms, advice).

Thus, the setDrink() invocation goes to the proxy and from the proxy, transparently to the target class, CoffeeShop. However, we intercept the execution flow after the proxy calls the target class's serveDrink() method (we can do it before, after, or even instead). There, by applying billing advice, we can get our precious per cup. We have accomplished our sinister plan mostly by modifying the coffeeshop.xml file:

 <?xml version="1.0" encoding="UTF-8"?>





com.offspring.examples.coffeeshop.ShopbillingAdvice





Class BillingAdvice is our cash register:

 public class BillingAdvice implements AfterReturningAdvice {
   public void afterReturning(Object returnValue, Method method,
         Object[] args, Object target) throws Throwable {
      if(method.getName().equals("serveDrink")){
         Shop shop = (Shop)target;
         System.out.println("2 dollar charge applied for the "+shop.getDrink().getName());
      }
   }
}   


Congratulations! The execution of our application produced the desirable message: Coffee served. The drink temperature is 160 degrees F. 2 dollar charge applied for the Coffee.

What have we have achieved so far? A lot. Our implementation is a truly object-oriented feat. Without modifying and recompiling our application, just by editing coffeeshop.xml, we can serve any possible drink, even boiled water, and can change/plug/unplug the cross-component services (cash register). We have eliminated the cross-cutting concerns and focused without distraction on our major passion—serving coffee. Most importantly, we followed the Second Big Rule—keep your classes open for functional extension, but closed for content modification.

The Spring Web layer

Also important to our discussion is the Spring Web layer, which provides the rich hierarchy for the controllers to handle user requests.

The AbstractCommandController is the most common choice for accommodating the Web application's real-life requirements, where we must process commands with parameters. Let's build the notorious "Hello World" application: The jw-0313-offspring.war file is a simple illustration of this standard Spring implementation and is available for download from Resources. I deployed it in the Tomcat 5 environment and typed in my browser the URI http://localhost:8080/offspring/helloworld?name=Edward (alternatively, http://www.whowillbethere.com/helloworld?name=Edward). The highly anticipated result is a page with the sophisticated message: "Hello World! Regards, Edward."

The core of the solution is the HelloWorldController class. It extends AbstractCommandController:

 public class HelloWorldController extends AbstractCommandController{
   public HelloWorldController(){
      setCommandClass(HelloWorldCommand.class);
   }
   protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
         Object command, BindException errors) throws Exception {
      Map model=new HashMap();
      HelloWorldCommand helloCommand =(HelloWorldCommand)command;
      String name=helloCommand.getName();
      model.put("name",name);
      return new ModelAndView("helloworld",model);
      /* Alternative solution for the single value pair */
      //Return new ModelAndView("helloworld","name",name);

} }


The line setCommandClass(HelloWorldCommand.class) tells the controller to bind URI parameters (we have only one, name) to the HelloWorldCommand class's command object. It's just a simple bean for holding request parameters:

 public class HelloWorldCommand {
   String _Name;
   public String getName() {
      return _Name;
   }
   public void setName(String name) {
      _Name = name;
   }
}


As a result, when Spring invokes the handle() method, the value Edward is already assigned to class member _Name. The handle() method contains our implementation's specifics. The most important line is return new ModelAndView("helloworld",model);. It returns an instance of the object ModelAndView. Now Spring knows that view "helloworld" should display the model's content. The model is represented by the Map object, which has an entry with key name and value Edward. The view "helloworld" maps to the JavaServer Pages helloworld.jsp:

 

Hello World!

Regards, ${name}



When this page is rendered, the reference ${name} will be substituted by the value Edward.

The whole application is glued together by the XML application context in the helloworld.xml file:

 <?xml version="1.0" encoding="UTF-8"?>

helloWorldController
class="org.springframework.web.servlet.view.InternalResourceViewResolver"> /WEB-INF/jsp/.jsp


The urlMapping maps the "/helloworld" path to the proper controller defined by the bean helloWorldController, and viewResolver maps the view to the JSP page {viewname}.jsp.

Another important configuration piece is the web.xml file, which is available from Resources.

Offspring

We have just scratched the surface of the rich Spring Framework's functionality; but we have enough to get the basic idea and even to build our first application.

The big question haunting any good programmer is: How can we do better with less effort? Let's make a giant leap toward reality and assume we must implement 50 different greeting tasks with different "hello" business logics, such as "Hello Europe," "Hello Mars," or "Hello Milky Way." If we decide to follow Spring fundamentals, we must create 50 different classes (all greeting logic differs in each case). Each class extends AbstractCommandController and overrides the handle() method to implement specific greeting business logic. Our real and potential problems are:

  • Ten programmers working on the same project will create 10 different interfaces (each solution is superior, according to its author). This heterogeneous environment is bug-prone and difficult to maintain.
  • To implement the global changes affecting the whole application, we must modify 50 tasks and/or apply AOP against 10 different interfaces.
  • Applying AOP against 10 different interfaces to cut cross-reference concerns will create complex XML definitions—usually, a messy mix of references to the different tasks inside the same XML fragments.
  • The final blow comes when management finds out that JavaServer Faces (JSF) or another technology provides better looking greeting gadgets. Now we have to rewrite all 50 tasks almost from scratch because they no longer extend the Spring controller class.

Offspring offers a generic controller to address those issues.

Offspring generic controller

A generic controller simplifies the development process and resolves the problems described above. It extends AbstractCommandController and becomes the main gateway for any user- or scheduler-generated requests. It centralizes the request flow and achieves decoupling from the native Spring Web layer. A generic controller is truly generic because it lacks knowledge of the specific tasks, interfaces, managers, and so on. It deals only with the common Action interface by calling its methods. This way, the class that implements Action becomes a business flow entry for the specific task. The whole schema perfectly complies with the Second Big Rule: the generic controller is closed for modifications, but opened for functional extensions by plugging in new actions. Let's look closer at the Action interface:

 public interface Action {
   //Execution flow
   public boolean initAction() throws Exception;
   public void endAction() throws Exception;
   public boolean performAction() throws Exception;
   public void failedAction() throws Exception;
   public void failedValidation() throws Exception;
   public void succeedAction() throws Exception;
   //Web layer support
   public String getView();
   public void setView(String view);
   public String getModelName();
   public Object getModel();
   public void setCommand(Object command);
   public Object getCommand();
   public BindException getErrors();

public void setErrors(BindException errors);

public Map getActionContext(); }


The Action interface consists of two parts—execution flow and Web layer support. Execution flow reflects the task's lifecycle: initiation, success/failure, and action demise. Web layer support is responsible for the interaction with the generic controller. It should be growing clear what the generic controller is supposed to do: it calls the execution flow methods to govern the action object's lifecycle.

We can do even better. We can create an extra level of abstraction between controller and action—the chain of executors. When the controller needs to perform an action, it invokes the performAction() method of the first executor in the chain. Each subsequent executor is called by the previous one, until the last executor in the chain invokes the corresponding method of the action object. This falling-dominos structure allows us to plug into the controller the various executors with value-adding functionality such as logging, transaction management, notifications, billing, or whatever the creative product-management imagination may conceive of in the future. Not surprisingly, all executors should implement the Executor interface:

 public interface Executor {
   public boolean initAction(Action action,Iterator it) throws Exception;
   public void endAction(Action action,Iterator it) throws Exception;
   public boolean performAction(Action action,Iterator it) throws Exception;
   public void failedAction(Action action,Iterator it) throws Exception;
   public void failedValidation(Action action,Iterator it) throws Exception;
   public void succeedAction(Action action,Iterator it) throws Exception;
}


The BaseExecutor class implements default extendable functionality of chaining and action invocation:

 public class BaseExecutor implements Executor{
   public boolean initAction(Action action, Iterator it) throws Exception{

if(it.hasNext()){ return ((Executor)it.next()).initAction(action,it); }else{ return action.initAction(); } } public void endAction(Action action,Iterator it) throws Exception {

if(it.hasNext()){ ((Executor)it.next()).endAction(action,it);

}else{ action.endAction(); } } public boolean performAction(Action action, Iterator it) throws Exception{ if(it.hasNext()){ return ((Executor)it.next()).performAction(action,it); }else{ return action.performAction();

} } public void failedAction(Action action, Iterator it ) throws Exception { if(it.hasNext()){ ((Executor)it.next()).failedAction(action,it); }else{ action.failedAction(); } } public void failedValidation(Action action, Iterator it) throws Exception{ if(it.hasNext()){ ((Executor)it.next()).failedValidation(action,it);

}else{ action.failedValidation(); }

} public void succeedAction(Action action, Iterator it) throws Exception { if(it.hasNext()){ ((Executor)it.next()).succeedAction(action,it); }else{ action.succeedAction(); } } }


Later, we will see how to apply AOP and extend this class to modify task behavior. Figure 1's sequence diagram illustrates the chain-of-executors concept:

jw-0313-offspring1-thumb.gif

Figure 1. Chain-of-executors sequence diagram. Click on thumbnail to view full-sized image.

The generic controller's source code is available for download in the package com.offspring.frame. The business logic is in the overridden handle method:

 public class GenericController extends AbstractCommandController {
   String _ActionName = null;
   ArrayList _Executors;
   protected String _ViewSuccess;
   protected String _ViewFailure;
   protected Map _ViewSuccessMap;
   protected Map _ViewFailureMap;
   
   public GenericController() {
      _Executors=new ArrayList();
      _Executors.add(new BaseExecutor());
   }   
   public GenericController(Object command, String actionName) {
/*
Command is a simple bean to hold request parameter.
Each value pair in URI query string has the corresponding
setter and getter methods. Spring Framework will assign command
object values by calling corresponding setter method. Command
object is defined in the XML file and injected by the the Spring container. 
*/
/*
Action name is the name of Action bean defined in XML descriptor.
It extends Action interface and implements business logic of specific task.
*/
      this();
      setCommandClass(command.getClass());
      _ActionName = actionName;
   }
   public GenericController(String actionName) {
      this();
      setCommandClass(BaseCommand.class);
      _ActionName = actionName;
   }

public void setExecutor(Executor executor){ _Executors.add(executor); } public void setExecutors(ArrayList executors){ _Executors.addAll(executors); } public void setViewSuccess(String viewSuccess) { _ViewSuccess = viewSuccess; } public String getViewSuccess() { return _ViewSuccess; } public void setViewFailure(String viewFailure) { _ViewFailure = viewFailure; } public String getViewFailure() { return _ViewFailure; } public void setViewSuccessMap(Map viewSuccessMap) { _ViewSuccessMap = viewSuccessMap; } public String getViewSuccess(String mode){ if(_ViewSuccessMap==null) return null; if(mode==null) mode=""; return (String)_ViewSuccessMap.get(mode); } public void setViewFailureMap(Map viewFailureMap) { _ViewFailureMap = viewFailureMap; } public String getViewFailure(String mode){ if(_ViewFailureMap==null) return null; if(mode==null) mode=""; return (String)_ViewFailureMap.get(mode); } public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { Util.LogTraffic(request,response); Log log=LogFactory.getLog(this.getClass()); ApplicationContext context = WebApplicationContextUtils .getWebApplicationContext(getServletContext()); /* Spring container instantiates Action object. It should not be defined as a singleton. This way, we can carry request-specific values as the class members. */ Action action = (Action) context.getBean(_ActionName); if(command instanceof HttpCommand){ HttpCommand httpCmd = (HttpCommand)command; httpCmd.setRequest(request); httpCmd.setResponse(response); httpCmd.setServletContext(getServletContext()); } action.setCommand(command); action.setErrors(errors); boolean isSuccess=true; Executor firstExecutor =(Executor) _Executors.get(0);

/*Start the chain of executors. Last executor in the chain will call action corresponded method. */ if (!firstExecutor.initAction(action,_Executors.iterator() )){ firstExecutor.failedAction(action,_Executors.iterator()); isSuccess=false; }else if (errors.hasErrors()) { log.debug(errors); firstExecutor.failedValidation(action,_Executors.iterator()); isSuccess=false; } else if (!firstExecutor.performAction(action,_Executors.iterator())) { firstExecutor.failedAction(action,_Executors.iterator()); isSuccess=false; } else { firstExecutor.succeedAction(action,_Executors.iterator());

} firstExecutor.endAction(action,_Executors.iterator()); String view=action.getView(); String mode=((BaseCommand)command).getMode(); /* Depending on the mode parameter and success or failure of execution, we will extract the name of the corresponded view and render it. Views are mapped in _ViewSuccessMap and _ViewFailureMap and defined in the controller XML definition. */ if((view==null) && isSuccess){ view = getViewSuccess(); if(view==null){ view=getViewSuccess(mode); } } if((view==null) && !isSuccess){ view = getViewFailure(); if(view==null){ view=getViewFailure(mode); } } Map modelMap = errors.getModel(); Object model = action.getModel(); if(model instanceof Map){ modelMap.putAll((Map)model); }else{ String modelName = action.getModelName(); if (modelName != null) { modelMap.put(modelName, model); } } if(view==null){ throw new Exception("Generic Controller failed to resolve view. IsSuccess="+ isSuccess +" mode="+mode +" command"+command.getClass().getName()+ " action="+action.getClass().getName()); } boolean isRedirect=false; //If view name starts with "redirect:" prefix, we will redirect through the user browser. if(view.startsWith("redirect:")){ isRedirect=true; } if(!isRedirect){

//Automatically put command object in the model. modelMap.put("command",command); view = Util.PopulateModel(view,modelMap); }else{ view = Util.getRedirectView(view,(Map)model); } return new ModelAndView(view, modelMap); } }


The generic controller also provides simple dynamic mapping of the mode parameter and success/failure execution results to the different views and redirections. The subsequent examples demonstrate how to employ this mechanism through the controller XML definition. The concept can be visualized through Figure 2's class diagram:

jw-0313-offspring2-thumb.jpg

Figure 2. Offspring class diagram. Click on thumbnail to view full-sized image.

The Offspring class diagram deals with two helper classes: BaseAction and BaseCommand. We can save a lot of typing by extending them. The BaseAction and BaseCommand classes are mostly made up of setters and getters and are available for download from Resources. Class BaseAction implements the Action interface. Each time we have to create a new task, we just create a new specific action and extend BaseAction:

 public abstract class BaseAction implements Action {
   protected Object _Command;
   BindException _Errors;
   protected String _ModelName;
   protected Object _Model;
   protected Map _Results;
   protected String _View;
   
   public BaseAction(){
   }
   public boolean initAction() throws Exception {
      return true;
   }
   public void endAction()throws Exception {
   }
   public void succeedAction()throws Exception {
   }
   public void failedAction()throws Exception {
   }
   public void failedValidation() throws Exception {
   }
   public void setCommand(Object command){
      _Command=command;
   }
   public Object getCommand(){
      return _Command;
   }
   public String getModelName() {
      return _ModelName;
   }
   public void setModelName(String modelName) {
      _ModelName=modelName;
   }
   public Object getModel() {
      return _Model;
   }
   public void setModel(Object model){
      _Model=model;
   }
   public BindException getErrors() {
      return _Errors;
   }
   public void setErrors(BindException errors) {
      _Errors = errors;
   }
   public Map getActionContext(){
      return _Results;
   }
   public String getView() {
      return _View;
   }
   public void setView(String view) {
      _View = view;
   }
}


Offspring example

Let's unleash the power of the Offspring technology to build our Greeting Enterprise Solution. As is often the case, the loud commercial name is over compensation for the primitive two-page Web application.

The first page is a form based on helloOffspring.jsp (complete files are available for download from Resources):

 

Hello world entry form

Thank you for your interest in the greeting process.
Please sign up.

Name: ${status.errorMessage}


The user is supposed to enter his/her name and press the Meet the World button. The mode parameter is a key to dispatch execution flow to the right code and view. Its value is assigned to the string greeting.

The fragment above illustrates another important Spring concept—validation. We employed the Spring tag library (tag ) to validate the parameter name. The class HelloOffspringValidator is plugged into the controller through the application context XML definition (we will see later how) and makes sure that the user entered his/her name:

 public class HelloOffspringValidator implements Validator {
   public boolean supports(Class clazz) {
      return clazz.equals(HelloOffspringCommand.class);
   }
   public void validate(Object form, Errors errors) {
      HelloOffspringCommand command = (HelloOffspringCommand) form;
      String mode=command.getMode();
      if(mode!=null && mode.equals("greeting")){
         ValidationUtils.rejectIfEmpty(errors, "name", "required.name", "Name is required");
      }
   }
}


It's important to validate the form only if the user is in the "greeting" mode. The mode parameter is assigned in the JSP page and carried by the Spring Web framework as a property of the Command object.

The second page of our application is the actual greeting itself, helloOffspringGreeting.jsp:

 

Hello World!

Regards, ${command.name}

Note:
${command.name}, You have met the world ${command.visits} time(s). <!-- alternatively with HelloOffspringAction.java modfication: ${name}, You have met the world ${visits} time(s). --&gt



It resembles the helloworld.jsp page, but with extra flavor: it displays the number of visits corresponding to the username stored in the session.

Action is better than words

As we know already, Action is a class where we make the transition from generic behavior to the specific business logic implementation. It can be complex and deal with all possible services, managers, remote invocations, subsystems, and APIs. Our Greeting Enterprise Solution is much simpler. The HelloOffspringAction has only one class:

 public class HelloOffspringAction extends BaseAction {
   
        public boolean performAction() throws Exception {
      boolean answer=false;
      HelloOffspringCommand command =(HelloOffspringCommand)getCommand();
      String mode=command.getMode();
      if(mode==null){
         mode="form";
      }
      if(mode.equals("greeting")){
         answer=performGreeting();
      } else if(mode.equals("form")){
         answer=true;
      }
      return answer;
   }
   private boolean performGreeting(){
      HelloOffspringCommand command =(HelloOffspringCommand)getCommand();
      HttpSession session= command.getRequest().getSession();
      String name=command.getName();
      Integer visits =logGuest(session,name);
      command.setVisits(visits.toString());
       //Generic controller will automatically treat command instance as a model.
        Map model=new HashMap();
        model.put("name",name);
        model.put("visits",visits.toString());
        setModel(model);
        //To render values in the JSP page, we would use notation:
        //${name}, ${visits} 
      return true;
   }
   private Integer  logGuest(HttpSession session, String name){
      Map namesLog=(HashMap) session.getAttribute("guests");
      if(namesLog==null){
         namesLog=new HashMap();
      }
      Integer visits= (Integer)namesLog.get(name);
      boolean isFirstVisit=false;
      if(visits==null){
         isFirstVisit=true;
         visits= new Integer(0);
      }
      this.getActionContext().put("isFirstVisit",new Boolean(isFirstVisit));
      int value=visits.intValue();

visits=new Integer(++value);

namesLog.put(name,visits); session.setAttribute("guests",namesLog); return visits; } }


The method logGuest() maintains the number of visits per name in the Session object. The performAction() method populates the command object with the number of visits. The command object is automatically associated with the model by GenericController. Alternatively, we could set our model manually (see code comments).

Glue it all together with XML

Since we are dealing with a Spring framework, without proper bean definitions in the helloworld.xml file, nothing would work. The most valuable additions to this file are action, command, and controller beans:

 helloOffspringActionhelloOffspringFormhelloOffspringGreetinghelloOffspringForm

The XML definition above instructs the Spring container to:

  1. Instantiate the helloOffspringCommand object.
  2. Instantiate the generic controller object, helloOffspringController. Its constructor arguments are the name of the action bean helloOffspringAction and command object helloOffspringCommand.
  3. Validate command object by using the com.offspring.examples.helloworld.HelloOffspringValidator class.
  4. Map mode parameter values greeting and form to the views helloOffspringForm and helloOffspringGreeting.

Two maps, viewSuccessMap and viewFailureMap, are used to map mode parameter values to the view name. In this particular example, let's assume that the user did not provide his/her name in the entry form. The JSP page will set the mode parameter to the value greeting. Routine validation will fail the execution. As a result, the viewFailureMap property's /prop> takes effect, and the generic controller renders the view helloOffspringForm repeatedly, until the user submits his/her name. We should not forget to modify bean urlMapping. The line helloOffspringController will map the hellooffspring path to helloOffspringController. The web.xml file should also be modified to map our path to the Spring dispatcher.

Tests and redirections

Downloading the war file, deploying it in the Web server environment (Tomcat 5 is a good choice), and typing URL http://localhost:8080/offspring/hellooffspring?mode=form will kick off the Greeting Enterprise Solution. Alternatively, it is available on http://www.whowillbethere.com/hellooffspring. The site whowillbethere.com was entirely built as a proof of the Offspring concept. I have deployed the Greeting Enterprise Solution there as a tutorial aid.

Now, what if want to redirect execution through the user browser? The generic controller provides a fairly simple way to do that, as illustrated in the following examples.

I have built a simple action class JspAction:

 public class JspAction extends BaseAction {
   public JspAction(){
   }
   public boolean performAction() {
      HttpCommand command =(HttpCommand)getCommand();
      String viewName=command.getRequest().getParameter("view");
      setView(viewName);
      return true;
   }
}


I plug it in jspViewController through the helloworld.xml definition and map jspContoller to the path jspView:

 helloWorldControllerhelloOffspringControllerjspViewController

jspActionmenutest


I modify my web.xml file to map jspView to the Spring Dispatcher servlet:

 Dispatcher/jspview

Now we are armed with a universal way of viewing the arbitrary JSP page through the Offspring execution flow. Just say "/jspView?view=anyJspPage" and magic happens (no clicking heals and Kansas residency required).

Finally, let's build the familiar helloPlain.jsp page. This time, name and visits are resolved from the HTTP request parameters:

 

Hello World trough the plain JSP!

Regards, ${param.name}

Note:
${param.name}, You have met the world ${param.visits} time(s).

Let's test it with the following URI on a local machine: http://localhost:8080/offspring/jspview?view=helloPlain&name=Dorothy&visits=1 or http://www.whowillbethere.com/jspview?view=helloPlain&name=Dorothy&visits=1.

It works fine; therefore, we can change the execution flow of our Greeting Enterprise Solution and redirect through the user browser to the helloPlain.jsp page. This simple operation requires a slight modification to the helloOffspringController bean via the helloworld.xml file:

 helloOffspringFormredirect:jspview?view=helloPlain?name?visits

This modification maps the success view to the value redirect:jspview?view=helloOffspringGreetingPlain?name?visits. The view name starts with the redirect: prefix; therefore, Offspring redirects through the user browser and assigns HTTP request parameters name and visits to the values from the model. If we want to redirect to the other server, we would use in the mapping the view name value redirect:http://whowillbethere.com/jspview?view=helloPlain?name?visits.

What if helloPlain.jsp was designed to deal with parameters namePlain and visitPlain, instead of name and visit? Another notation resolves this problem. The mapping redirect:jspview?view=helloPlain?namePlain=${name}?visitsPlain=${visits} will dereference values name and visits from the model map and assign them to request parameters namePlain and visitsPlain.

Chain of executors: When chain is light and pleasant

I introduced executors as reusable modules that we could plug into the controllers to modify task logic and eliminate cross-cutting concerns. For the sake of demonstration and to offer a simple tracking example, let's assume we must track some tasks' activities. Here is the simple plan:

  1. Create an advice class and implement tracking logic
  2. Define BaseExecutor's tracking bean in the Spring XML definition
  3. Plug in the tracking bean as an executor in all controllers that require tracking functionality

Follow your own advice
The TrackingManager advice class is an AOP advice that logs out the action activity. We track only successful execution after the succeedAction invocation:

 public class TrackingManagerAdvice implements AfterReturningAdvice {
   public void afterReturning(Object returnValue, Method method,
         Object[] args, Object target) throws Throwable {
      if(method.getName().equals("succeedAction")){
         Action action=(Action)args[0];
         trackExecution(action);
      }
   }
   private void trackExecution(Action action) throws Exception{
      Log log=LogFactory.getLog(this.getClass());
      log.info("***** Tracking***");
      log.info("Action="+ action.getClass().getName());
      log.info("ActionContext="+action.getActionContext());
      log.info("Action:");
      dumpBean(action.getCommand());
      log.info("Model:");
      dumpModel(action.getModel());
      log.info("***** EndTracking***");
   }
         
} 

The call action.getActionContext() extracts the intermediate result values that Action did not put in the model, but nevertheless are important to track.

Create tracking bean executor and apply AOP via the XML definitions
The helloworld.xml file contains new definitions:

 

com.offspring.frame.ExecutortrackingAdvice

According to the definition above, trackingAdvice will intercept all calls to the Executor interface methods of the executorTrackingTarget object.

Plug in the tracking bean as an executor in the controller
When a task requires tracking functionality, we must plug in the reusable tracking executor in the corresponding generic controller. One extra line in the controller XML definition adds all desirable functionality:

 
   ...
   
        ...
        

Interestingly enough, we did not have to use AOP for the tracking-executor implementation. The essence of the executor is to proxy calls from the generic controller to the action, which means we can build executorTracking just by extending the BaseExecutor class:

 public class SimpleTrackingExecutor extends BaseExecutor {

public void succeedAction(Action action, Iterator it) throws Exception { super.succeedAction(action, it); Log log=LogFactory.getLog(this.getClass()); log.info("***** Simple Tracking (no AOP)***"); log.info("Action="+ action.getClass().getName()); log.info("ActionContext="+action.getActionContext()); log.info("Action:"); dumpBean(action.getCommand());

log.info("Model:"); dumpModel(action.getModel()); log.info("***** EndTracking***"); } ... }

The XML definition is even simpler:

 
   …
   
        …
        


Transaction manager executor

Offspring transaction management provides a compelling example of how we can benefit from plugging specific executors in the generic controller. Transaction management is one of the most challenging tasks we may face in developing the application's persistence layer. To secure critical financial transactions, we must set a high database isolation level. However, applying a high database isolation level for less critical, but frequent operations could cause concurrency problems such as lock escalations, deadlocks, or timeouts. In the e-commerce world, usually, when one user browses available sport cards, other concurrent users are prevented from paying for a subscription because both tasks have the same high-isolation level and block each other's concurrent database access. The chain-of-executor concept gives us technical means for how to handle the problem gracefully.

Using rich Spring transaction-support functionality, we define datasource and transaction executor beans in the application context helloworld.xml:

 

java:comp/env/jdbc/dbhelloworld





com.offspring.frame.ExecutorPROPAGATION_REQUIREDISOLATION_READ_UNCOMMITTED


The complexity of the executorTrans bean definition does not affect the one-line effort to plug it into the generic controller's chain of executors. Assuming that instead of the sessions, we are using the MySQL database to log our visitors, the definition of the helloOffspringController will have an extra executor:

 
   ...
   
      ...

rom now on, we can use the very same executorTrans bean to support low-isolation-level transactions for all tasks by plugging it into the corresponding chain of the executors. If we need high-isolation-level transactions for some critical tasks, we would define and plug in another transaction bean with the isolationLevelName attribute equal to ISOLATION_REPEATABLE_READ. This approach is so flexible that we can even switch from one mode to another by changing one line of the controller definition in the application context.

Scheduling the actions

Let's look at another common functionality where Offspring delivers a simple and reliable solution: when synchronous response is not required, some tasks should be scheduled. When I implement email notifications, I store email body and attributes in the database table and let the scheduler send bunches of emails every few minutes asynchronously. This way, my application stays up even when the SMPT server is down.

Let's build a scheduler generic controller—the generic and simple way of running the cron jobs. It's designed to kick off actions through OpenSymphony's Quartz scheduler. The code resembles that of generic controller:

 public class SchGenericController extends CronTriggerBean  implements ApplicationContextAware {
    ApplicationContext _ApplicationContext;

String _ActionName = null; String _CommandName = null; Object _Command = null; ArrayList _Executors; public SchG

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/71047/viewspace-996765/,如需转载,请注明出处,否则将追究法律责任。

user_pic_default.png
请登录后发表评论 登录
全部评论
<%=items[i].createtime%>

<%=items[i].content%>

<%if(items[i].items.items.length) { %>
<%for(var j=0;j
<%=items[i].items.items[j].createtime%> 回复

<%=items[i].items.items[j].username%>   回复   <%=items[i].items.items[j].tousername%><%=items[i].items.items[j].content%>

<%}%> <%if(items[i].items.total > 5) { %>
还有<%=items[i].items.total-5%>条评论 ) data-count=1 data-flag=true>点击查看
<%}%>
<%}%>
<%}%>

转载于:http://blog.itpub.net/71047/viewspace-996765/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值