Professional Java Development with the Spring Framework

The sample application featured is more extensively described in Chapter 15. This section will show you how we'll be implementing some of the use cases mentioned in Chapter 15. Using a simple controller,we'll allow users to view information about a specific show. Also, we'll create a form controller with which bookings can be made, and last but not least, we'll create a wizard-style process to add shows to our box office.

Viewing a List of Performances Using the AbstractController

Users can select a show from the list of shows the welcome screen presents them with. The show the user selects will then be displayed along with the performance and availability data. Let's implement an AbstractController modeling this behavior.

Important 

When one of the other controllers Spring offers doesn't suit your needs and you're planning to implement the Controller interface directly, consider extending the AbstractController instead. It offers many convenient configuration parameters enabling you to customize the controller’s behavior.

Workflow of the ViewShowController

Let's first review the workflow the controller will be responsible for:

  1. The user will arrive at the welcome screen where he or she will be presented with a list of genres and corresponding shows. Each show is represented by an identifier that is placed in a link on the screen. Clicking on one of those links will trigger our controller.

  2. The controller will inspect what show the user selected by retrieving the identifier from the HttpServletRequest.

  3. Using the identifier, the middle tier will have to retrieve the show corresponding to the identifier.

  4. Our controller will have to determine a logical view name and the contents of the model with which, for example, a JSP can render the view (the show, including data availability).

Step 2 mentions a request. Assume the request to be the following:

http://www.springframework.org/sample/viewshow.html?id=1234

Implementing the Controller

First, we'll implement our controller. We'll do this using two possible approaches. The first approach starts with extending the AbstractController. Implementing the template method provided by the superclass is all you need to do.

import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.sample.boxoffice.logic.BoxOffice; .... public class ViewShowController extends AbstractController {          /** The boxoffice domain object we’ll     private BoxOffice boxOffice;          /** And a setter to be able to wire it up in our application context */     public void setBoxOffice(BoxOffice boxOffice) {         this.boxOffice = boxOffice;     }

The following template method will retrieve the show the user selected and return an appropriate model:

    protected ModelAndView handleRequestInternal(         HttpServletRequest request,         HttpServletResponse response) {              // first, inspect the request for an identifier         long showId = RequestUtils.getRequiredLongParameter(request, "id");              Show show = boxOffice.getShow(showId);              return new ModelAndView("showdetails").addObject("show", show);     } }

We're using Spring's RequestUtils convenience class (located in the org.springframework.web.bind package) to retrieve the show identifier. This is a useful class to use in simple cases where advanced data binding is overkill. If the required request parameter isn't present, the getRequiredLongParameter() method will throw an exception, which you should preferably handle by adding a HandlerExceptionResolver to your application context (discussed later in this chapter).

Important 

Note that we're creating and returning the ModelAndView in a single line of code. In general, you should be careful with this because it makes debugging more difficult. If NullPointerExceptions are thrown, it's difficult to see what the cause is. In this case it's obvious, however, that this code won't throw any exceptions.

Important 

Another note about exceptions: In the previous example we're not including any exception in the throws clause of the handleRequestInternal() method. Most methods in Spring Web MVC are allowed to throw arbitrary exceptions. However, as noted in the discussion of exception signatures earlier in this chapter, we recommend using the narrowest throws clause you can, in accordance with good Java style.

We could have also chosen to use the ThrowawayController approach to implementing the functionality. The ThrowawayController offers more of a command-style approach in that for each request, a controller is instantiated and populated with the values from the request, after which the controller should have enough information to proceed and return a ModelAndView. The advantage when using these kinds of controllers is that you won't be tied to the Servlet API because all information from the HttpServletRequest is already transferred to the command object (the controller itself) by the framework and no reference to the request itself is given to the controller. This means testing outside a container without mock objects. For example, the HttpServletRequest is pretty easy.

import org.springframework.web.servlet.mvc.throwaway.ThrowawayController; import org.springframework.sample.boxoffice.logic.BoxOffice;      public class ViewShowController implements ThrowAwayController {          private Long id;          /** upon execution, the HTTP request parameter 'id’     public void setId() {         this.id = id;     }          /** method to implement, specified by ThrowawayController interface */     public ModelAndView execute() {         if (id != null) {                        Show show = boxOffice.getShow(Long.parseLong(showId));             ModelAndView mav = new ModelAndView("showdetails");             mav.addObject("show", show);             return mav;         } else {             return new ModelAndView("error", "message", "unexpected.noshowid");         }     } }

Wiring Up the Controller

Now that we've created our controller, we're going to wire it up using our application context located in the WEB-INF directory of our web application.

<bean name="/viewshow.html"         >     <property name="boxOffice">         <ref bean="boxOffice"/>     </property> </bean>

This is all you need to do to be able to retrieve shows using your middle tier and allow the view technology of your choice to render an HTML page or perhaps create PDF, Excel, or other content.

Making a Reservation

The application allows users to reserve seats and purchase tickets. From the screen presenting the show and corresponding performances and availability, the user can select a specific performance, reserve a certain number of seats, and purchase the actual tickets. Before we go on, let's have a more detailed look at what workflow is involved with doing the actual purchase:

  1. From the screen presenting the user with the details of the show, he can select a specific performance and a price band. This will take the user to a screen where he can select the number of seats he wants to reserve.

  2. At this moment, the application will present the user with the number of seats available. The user will be taken to a form that will allow him to purchase the tickets.

  3. The form will be presented alongside some details about the performance the user is purchasing tickets for. The form consists of text fields and checkboxes and other widgets with which the user will be able to fill in his personal information (first name, last name, and so on) and payment details (credit card number, type of credit card).

  4. Submitting the form will trigger some additional processing, and after that, validation will be performed. If errors are encountered, the form will be redisplayed, now containing error messages. The user will then be able to fill in the fields that contained errors.

  5. After the validation succeeds, the actual payment will be done and the tickets will be purchased. The user will then be taken to another page telling him the purchase has been completed successfully.

SimpleFormController

In a typical form-editing and -submission process, users are first presented with a form, which they can fill out and submit, after which data binding and validation will be done. If all goes well, the application might, for example, store something in a database after which it tells the user everything went just fine.

The org.springframework.web.servlet.mvc.SimpleFormController does exactly this. It is one of the most useful and often-used controllers Spring provides and can be almost completely configured using Spring's Dependency Injection features.

The process of implementing a simple form controller can be summarized as follows (items between parentheses are not always necessary):

There is, however, a lot more to tell. Before we begin, let's review the properties and some of the call-backs and other customization possibilities involved with the SimpleFormController.

Important 

Don't let the flexibility of the command controllers and form controllers scare you off! The authors usually find themselves overriding only three methods: formBackingObject(), referenceData(), and doSubmitAction(); however, we often come across requirements where the construction of a command object is so expensive that doing it twice is just too much. That’s where the sessionForm property comes into play. You’ll find that working with Spring’s controller infrastructure is easy if you don’t have complex requirements, and extremely powerful otherwise.

Properties to Configure the Controller With

The SimpleFormController inherits a lot of its functionality from the AbstractFormController and the BaseCommandController. To tweak its behavior, the following properties are at your disposal:

Callbacks and Template Methods for Custom Behavior

There are several callback methods you can implement to get custom behavior or to extend Spring's default behavior. These include methods called right before and after validation and methods to be implemented when you want to perform custom logic involved with the submitting of a form.

Important 

Whenever doing things such as custom binding, for example in the onBind() method, you probably need to access the HttpServletRequest. Your best option to do so is using the org.springframework.web.bind.RequestUtils, offering methods such as getIntParameter(), getBooleanParameter(), and corresponding variants that will throw an exception if the parameter does not exist. Using the HttpServletRequest directly is tedious and you need to do lots of checking that can better be avoided (or moved to a utility class).

Form Workflow

The form workflow is graphically laid out in Figures 12-6 and 12-7. Figure 12-6 explains the workflow involved with requests that need to display a form.

Figure 12-6

Figure 12-7

Important 

When a form controller services a request, based upon the outcome of the isFormSubmission() method, it starts the form submission process or the work- flow for showing a new form. By default, all GET requests are associated with new forms; all POST requests are treated as form submission requests. You can always modify the behavior by overriding the isFormSubmission(HttpServletRequest) method.

  1. The form display process starts with the creation of a new command. The method called to do this is formBackingObject(). By default, it instantiates a new instance of the command class you have configured. If you override this method, you might instead retrieve an object from the database. For example, include an additional parameter in the request that identifies a certain object you want to edit. Retrieve this parameter in the formBackingObject() method and load the corresponding object using a service object or DAO.

  2. The binder is created and you're allowed to further customize it (add additional property editors, and so on) in the initBinder() method.

  3. The form displaying process continues by determining whether or not binding needs to be done. Binding when displaying a new form can be turned on by setting the bindOnNewForm property to true.

  4. The next thing is to store the object in the session (but only if sessionForm is set to true).

  5. The last thing the controller does is create the model. It actually does so by calling BindException.getModel() or Errors.getModel(). The BindException wraps the actual command object and can create an appropriate model for you. You will probably do this yourself as well once in a while, as we'll see while reviewing the submit process.

The process of submitting a form (laid out in Figure 12-7) is slightly more complicated. It involves checking for the command object, invocation of the callbacks, validation (if needed), and the actual calling of the submit methods.

import org.springframework.web.servlet.mvc.SimpleFormController;      public ConfirmBookingController extends SimpleFormController {          public void initBinder(         HttpServletRequest request, ServletRequestDataBinder binder)     throws Exception {              binder.registerCustomEditor(new ShowEditor());     }

As you can see, the binder now knows how to convert shows to String and String back to show — all because we've registered the ShowEditor.

  1. When a form is submitted (usually this is the case when a POST request is done to the controller), the controller first determines whether or not sessionForm is set to true. If so, it retrieves the command object from the session. If none is found, it will call handleInvalidSubmit(), which by default resubmits the form. Overriding is of course possible.

  2. If sessionForm was set to false, the controller will call formBackingObject() again to retrieve a command object.

  3. After the binder has been created (see the preceding code example), binding and validation will be done and the corresponding callbacks will be called to allow for custom processing. Validation, by the way, can be turned off by setting suppressValidation to true.

  4. After the binding has been done and validation succeeded, the onSubmit() callbacks will be called. By default, if you override none of them, the last method called is doSubmitAction(), which returns void and uses the successView configured with the controller.

When validation fails, the process is, of course, a bit different because we have to show the form again, including validation errors. The process somewhat resembles the displaying of the form for the first time. As you know, all Spring controllers result in a ModelAndView being created and so do the form controllers. The model a form controller returns consists of three things:

The fact that Spring maintains binding errors separately from the form object means that domain objects can be used for form objects — a contrast with Struts, which requires the use of ActionForm subclasses to hold both form information and any accompanying errors.

When overriding one of the callbacks such as onSubmit(), you have the option to return to the form view (in case of errors during saving or other situations that might require it). You can do so by calling showForm(HttpServletRequest request, BindException errors, String viewName). In its turn this will trigger the reference data method and return the appropriate view. One thing to keep in mind is that the BindException actually wraps the target (or command) object and if you capture the model returned from the showForm() call and go and meddle with the model yourself, you'll run into trouble. Replacing the command object somewhere during the process of submitting the form should be done prior to the onSubmit() callbacks, or by creating a new BindException, wrapping the new target in it, and passing it to the showForm() method.

Категории