Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 42 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
42
Dung lượng
495,32 KB
Nội dung
This controller has a complex work flow, and we will break it down into its core compo- nents. Then, we will show you the best extension points for altering the work flow in the controller. We begin by illustrating all possible paths through the controller. Figure 6-1 illustrates the many paths through the work flow of handling forms with SimpleFormController. Figure 6-1. SimpleFormController activity diagram We will focus on the initial retrieval of the HTML form first. The controller uses the method isFormSubmission() to determine if the HTTP request is either a form viewing or form submission. The default implementation of this method merely checks the HTTP method, and if it is a POST then isFormSubmission() returns true. The controller does not consider a HTTP GET request a form submission. However, this method is not final, so your implementation is free to define whatever criteria is appropriate to indicate the request should be treated as a submission. If indeed the request is not a form submission, the Resource requested Retrieve form object from session Everything from AbstractController Is a form submission? Is a session form? Is form bean in session? Yes Yes Yes Create form object Create and init binder No Create NewForm object Create and init binder No No Remove from session Bind request parameters to form object Bind to form object Store form in session Should bind on NewForm? Yes No Validate form object Yes onBindAndValidate Validate on bind? Suppress validation? Yes No No Yes Collect reference data No Is a session form? Display form view Handle form submit No Errors from binding or validation? Yes Done CHAPTER 6 ■ THE CONTROLLER MENAGERIE 151 584X_Ch06_FINAL 1/30/06 1:40 PM Page 151 controller will then consider this request as the first of two requests (the second being the actual form submission). It will create an instance of the form object (a.k.a. command bean) using the formBackingObject() method. By default, this method will return an instance of the class specified with setCommandClass(). The formBackingObject() method is not final, so you will use this method to configure, if necessary, the form object class, possibly setting any dependencies or properties. This is the time to manipulate the form object before it enters the work flow. Once the instance of the form object is ready, the controller then creates the DataBinder and calls the initBinder() life cycle method. Override this method to register any custom PropertyEditors required when binding to the form object. By default, this method does nothing. Now that the DataBinder instance is ready, you have the choice of performing a binding using any parameters sent with this initial HTTP GET request. This action is determined by the bindOnNewForm property, enabled by (you guessed it) calling the setBindOnNewForm() method. Typically, on the first view of a form, no parameters have been sent with the request, so it is safe to leave bindOnNewForm equal to false (the default). However, if you set the property to true, and any errors occurred from the binding process, those errors will be available to the initial form view. The SimpleFormController has the ability to store the form object in the session for the duration of the controller’s work flow. That is, the form will live in the session between the ini- tial form view and the form submission. To enable this feature, simply call setSessionForm() with a value of true. Once the form object has been created, and after it is possibly bound by the DataBinder, it will be stored in the session if this property is true. If you load the form bean from persistence using an object-relational mapping (ORM) tool, you may find the session form functionality useful. Many ORM tools support the concept of reattaching, or merging, a detached bean back into persistence. If you require this type of behav- ior, using the session form functionality is a nice way to load the form bean only once during initial form view. That same instance will then be used during the form submission stage, avoid- ing the need to pull the instance from persistence again. Most persistence strategies can handle pulling the instance twice (once on form view and once on form submission), so weigh the pros with the cons of increased memory usage for the session, among other issues. ■Caution Storing the form in the session should not be chosen lightly if the application is to be clustered. Typically, in a clustered environment, session data is serialized and often replicated among different nodes in the cluster. Serialization is a costly procedure, and it can place a heavy burden on cluster bandwidth. Check with your application server’s clustering strategies, and be sure to understand the impact of session storage. At this point, the form view is about to be returned to the user. Before the view is ren- dered, the referenceData() callback is called. This life cycle method allows you to assemble and return any auxiliary objects required to render the view. The form object will automati- cally be sent to the form view, so use this method to put anything else into the model the form page might need. By default, this method does not manipulate the model in any way. CHAPTER 6 ■ THE CONTROLLER MENAGERIE152 584X_Ch06_FINAL 1/30/06 1:40 PM Page 152 If you’ve made it this far, the Controller will assemble the full model with the form object, any possible errors from potential binding, and the model from referenceData(). It will then send this combined model to the View named by the formView property. This is the name of the View that contains the actual HTML form. Form Submission with SimpleFormController The SimpleFormController has completed half of its job by displaying the HTML form to the user. Once the user hits the submit button of the form, the SimpleFormController roars back to life to handle the submission. It will bind the request parameters to the form bean, validate the bean, and choose to show either the original form again if there were errors or the form success view if everything worked as expected. We will walk you through this process now, pointing out the useful life cycle callback methods along the way. The first method in this controller that determines the form bean’s fate is again the isFormSubmission() method. Your HTML forms should use the POST method for form submis- sions for both technical (it can handle much more data) and architectural (POST actions are intrinsically non-idempotent) reasons. Also, the default implementation of isFormSubmission() returns true if the action is a POST. You are free to override this method to further define what a form submission looks like, but the default should work fine for most situations. In this case, because we are tracking the form submission work flow, this method will return true. The SimpleFormController will now check whether or not the form bean is stored in the session, via the isSessionForm() method. If the form bean is stored in the session, then the Controller will retrieve the form bean, put there originally when the user viewed the form. The Controller then removes the form bean from the session, as it is likely the form bean will be successfully submitted during this work flow (thus no longer needed in the session). If, in fact, there are errors during the submission process, the form bean will be placed back into the session later. If the form bean was not stored in the session, then the controller will simply create another instance of the form bean using the formBackingObject() method. This is the same method used to create the form bean during the initial form view. ■Tip If your form bean requires dependencies to be injected by Spring’s ApplicationContext, overriding formBackingObject() provides the opportunity to request the bean from the BeanFactory. Of course, to avoid having to manually pull the bean using the getBean() method on the BeanFactory you can use Spring’s support for method injection (refer to Pro Spring by Rob Harrop and Jan Machacek (Apress, 2005) for more information). In any case, don’t restrict your form beans to simple POJOs as you may use Spring’s dependency injection for your form beans quite easily. At this point, the form bean instance has been obtained. The Controller now creates the DataBinder and calls the initBinder() callback method. As with the work flow for viewing the form, use this method to register any custom PropertyEditors you need during binding. CHAPTER 6 ■ THE CONTROLLER MENAGERIE 153 584X_Ch06_FINAL 1/30/06 1:40 PM Page 153 With the form bean created, and the DataBinder created with custom PropertyEditors regis- tered, the request parameters are now bound to the form bean. The binding process will also capture any binding errors, such as type conversion errors or any configured required fields. Of course, most forms require more complicated validation than what is provided by the DataBinder. At this point in the work flow, the Controller consults the method isValidateOnBinding() to determine if it should now run the form bean through the Validators. This method defaults to true, and it is marked final so the only way to change its behavior is through setValidateOnBinding(). If your situation requires a more exact control over when validation is performed after a binding, you may override the suppressValidation() method. While this method defaults to false, this method allows you to choose on a request-by-request basis whether or not to run through the validators. By default, the controller will allow each configured validator to validate the form bean. After all the validators have run, the controller will then call the onBindAndValidate() life cycle method. This callback method is your chance to perform any custom validation logic or gen- eral logic after binding and validation. ■Note The onBindAndValidate() method will run even if suppressValidation() returns true or if isValidateOnBinding() returns false. In other words, onBindAndValidate() will always run, even if validation did not. After onBindAndValidate() runs, the Controller makes a decision based on whether any errors exist. These errors would have resulted from the binding process or through automatic validation or custom validation in onBindAndValidate(). If there are any errors, the Controller then begins the process of displaying the original form. If no errors exist, then the form bean can finally be processed. If errors are present and the isSessionForm() method returns true, then the form bean is placed back into the session. Remember that the form bean is removed from the session at the beginning of the form submission handling work flow. However, when there are errors, the original form is displayed again, and so the form bean is stored in the session again to be bound again once the errors are addressed by the user. The referenceData() method is called once more to populate the model with objects for the form. Finally, the form view is displayed again, with the errors and the form bean. If there are no errors, the controller then calls the onSubmit() life cycle method. This sig- nals that the form bean is ready to be processed. There are several overloaded onSubmit() methods, each method simply calling the other with one fewer method argument. This flow, illustrated in Figure 6-2, is arranged this way to allow you to pick the method with the exact number of parameters you will need to process the form. There is no need to implement all onSubmit() methods; just choose the one that will work for you. There is also a very simple doSubmitAction(), which is the most simple callback method to override and implement. If you do not implement any of the onSubmit() methods, you will need to implement this method. This method simply provides the form bean to be processed. You may use this method when there is no model to construct and when the default success view is appropriate. If you need to construct a model, or if you need to choose the view to show dynamically, implement one of the onSubmit() methods. CHAPTER 6 ■ THE CONTROLLER MENAGERIE154 584X_Ch06_FINAL 1/30/06 1:40 PM Page 154 Figure 6-2. Order in which callback methods are called ■Tip Choose the best form submit callback to implement based on which objects you need and whether the default success view is always appropriate. Implement only one callback method. SimpleFormController Examples It is time to see the SimpleFormController in action. To begin, we will create a form for a sim- ple person. This example brings together what we have covered from the DataBinder and the work flow of the SimpleFormController. The Person class used in this example (shown in Listing 6-39) has three properties: a Name, when he was born, and his favorite programming language. To make things more interesting, the name property is an example of a nested Name object (shown in Listing 6-40), and the bornOn property will require a custom PropertyEditor. Listing 6-39. Person Bean public class Person { private Name name = new Name(); private Date bornOn; private String favoriteProgrammingLanguage; public Date getBornOn() { return bornOn; } public void setBornOn(Date bornOn) { this.bornOn = bornOn; } onSubmit(request, response, formBean, errors) onSubmit(formBean, errors) onSubmit(formBean) doSubmitAction(formBean) CHAPTER 6 ■ THE CONTROLLER MENAGERIE 155 584X_Ch06_FINAL 1/30/06 1:40 PM Page 155 public String getFavoriteProgrammingLanguage() { return favoriteProgrammingLanguage; } public void setFavoriteProgrammingLanguage(String favoriteProgrammingLanguage) { this.favoriteProgrammingLanguage = favoriteProgrammingLanguage; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Name: ["); sb.append(name.toString()); sb.append("], "); sb.append("Born On: ["); sb.append(bornOn); sb.append("], "); sb.append("Favorite Programming Language: ["); sb.append(favoriteProgrammingLanguage); sb.append("]"); return sb.toString(); } } Listing 6-40. Name Bean public class Name { private String first; private String last; public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getLast() { return last; } public void setLast(String last) { this.last = last; } @Override public String toString() { CHAPTER 6 ■ THE CONTROLLER MENAGERIE156 584X_Ch06_FINAL 1/30/06 1:40 PM Page 156 return first + " " + last; } } Listing 6-41 contains the XHTML that matches the Person object, while Figure 6-3 shows how the form is rendered. Note that Listing 6-41 deliberately omits the use of the <spring:bind> tags, in order to illustrate the raw XHTML required. This is the XHTML as the browser would see it. Obviously, this also illustrates that the use of <spring:bind> and related tags are not required, though they are recommended for any real projects. Listing 6-41. XHTML Form for Person <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Person Form</title> </head> <body> <form method="post" action=""> <table> <tr> <td>First Name:</td> <td><input type="text" name="name.first" /></td> </tr> <tr> <td>Last Name:</td> <td><input type="text" name="name.last" /></td> </tr> <tr> <td>Born On:</td> <td><input type="text" name="bornOn" /></td> </tr> <tr> <td>Favorite Programming Language:</td> <td><input type="text" name="favoriteProgrammingLanguage" /></td> </tr> <tr> <td /> <td><input type="submit" /></td> </tr> </table> </form> </body> </html> CHAPTER 6 ■ THE CONTROLLER MENAGERIE 157 584X_Ch06_FINAL 1/30/06 1:40 PM Page 157 Figure 6-3. Initial form view The XHTML shown in Listing 6-41 has some interesting aspects. First, notice that we are using the POST method for the form. This should be the preferred method for form submis- sions, as discussed earlier. Second, notice how we used action="" instead of specifying a particular URI. This is a convenience trick to avoid specifying the page’s URI, which would tightly couple the page to the URI that indicates it. By not specifying an action, the browser will submit the form back to the originating URI (which, in the case of SimpleFormController, is just what we want). Lastly, notice how the form input element names correspond to the property names from the Person object. This should be familiar from the discussion about the DataBinder. Next, we show the SimpleFormController implementation (Listing 6-42) for the form in Listing 6-41. For our example, it will simply print out the Person via toString(), but it’s easy to imagine using a data access object to persist the person. Listing 6-42. PersonFormController public class PersonFormController extends SimpleFormController { public PersonFormController() { setCommandName("person"); setCommandClass(Person.class); setFormView("newPerson"); setSuccessView("newPersonSuccess"); } @Override protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false)); } @Override CHAPTER 6 ■ THE CONTROLLER MENAGERIE158 584X_Ch06_FINAL 1/30/06 1:40 PM Page 158 protected void doSubmitAction(Object command) throws Exception { Person person = (Person) command; // persist the object, or some other business logic } } The bornOn parameter of the Person object is of type java.util.Date, so we are required to use a custom PropertyEditor to convert the string parameter into a true Date object. This Controller relies on the default behavior of forwarding to the successView once the form is submitted, so we chose to implement doSubmitAction() because it requires the least amount of code. By default, the form bean will placed into the model on successful form submission. Therefore, the form success view can access the form bean with the identifier set via setCommandName(). The following XHTML page (Listing 6-43) shows this in action. Listing 6-43. newPersonSuccess XHTML Page <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>New Person Success</title> </head> <body> <p>${person}</p> </body> </html> Listing 6-43’s JSP page will render the following result to the browser (Figure 6-4). Figure 6-4. Form success view CHAPTER 6 ■ THE CONTROLLER MENAGERIE 159 584X_Ch06_FINAL 1/30/06 1:40 PM Page 159 The Controller in this example is using logical View names to identify which views should be rendered. This provides a nice decoupling between the controller and how the Views are actually implemented. The DispatcherServlet will delegate these View names to a ViewResolver to resolve the actual View instances. For our example, we will use an InternalResourceViewResolver. This ViewResolver works well with JSP files and lets us hide the actual JSP files behind the /WEB-INF directory to prohibit unauthorized client access. ■Tip You should place any restricted or otherwise hidden files inside the /WEB-INF directory of your web application. That directory, and all of its subdirectories, is protected by the servlet container. The ViewResolver is declared and configured with a prefix and suffix, used to create a fully qualified filename for the view. This bean definition (Listing 6-44) will typically reside in the spring-servlet.xml file with the rest of the controller definitions and other web-specific beans. Listing 6-44. ViewResolver Configuration <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> With our example controller using a view name of newPerson, the ViewResolver in Listing 6-45 will attempt to locate the file /WEB-INF/jsp/newPerson.jsp. We obviously haven’t mentioned Validators or what happens if there is a validation error. Validation is a large topic, so we are postponing a full discussion about validation to Chapter 9. To continue with this example, we now want to modify the code to restrict the choices of favorite programming language. The predetermined choices must be loaded before the initial page view; thus we will override the referenceData() method. First, we will create the list of approved languages to pick a favorite from. For the example, the List’s contents are static, so we will create a simple array to be shared among requests. It is common to require objects from persistence to be returned by referenceData(). If that information is static, for performance reasons, load the objects only once at startup. This will save on pulling them from the database for every request. In this case, your controller may implement InitializingBean, which is a Spring-specific interface indicating that the bean requires initializing before servicing requests. ■Tip For more information about the InitializingBean interface, consult the book Pro Spring or the online documentation. An alternative to InitializingBean is the init-method attribute in the XML bean definition, which avoids the need to implement a framework interface. CHAPTER 6 ■ THE CONTROLLER MENAGERIE160 584X_Ch06_FINAL 1/30/06 1:40 PM Page 160 [...]... pages This Controller was not designed to support arbitrary decision branching, and in no way can it handle a generic state machine For that, you should turn to Spring Web Flow, covered later in this book With Spring Web Flow, you can declaratively create arbitrarily complex work flows, allowing the user to travel back and forth through the state machine To summarize, use AbstractWizardFormController... advantage of many different Spring JSP tags when building these pages Along with the usual tag, we are also going to use and You’ll see more about these tags in Chapters 7 and 9, but for now it’s important to know that spring: hasBindErrors exposes an errors attribute to the JSP page if there are validation errors, and spring: message performs i18n... to specify its bean definition Listing 6 -56 contains a sample configuration Listing 6 -56 Example Configuration of an InternalPathMethodNameResolver ... "billingDetails"}); } Or inside the bean definition, as is the case with Listing 6- 65 Listing 6- 65 XML Configuration 58 4X_Ch06_FINAL 1/30/06 1:40 PM Page 179 CHAPTER 6 ■ THE CONTROLLER MENAGERIE usernameAndEmail billingDetails ... mapping • command bean binding • support for one or more Validators • per-action “last modified” timestamp control • exception handling ■ Caution Even though this Controller supports Validators and command bean binding, it does not define a form handling work flow On initialization, this class searches all of its methods for any that conform to the request handler signature Any method that will handle a... request.getParameter("searchBy")); return new ModelAndView("accountFindError", "errorMessages", errorMessages); } The name of the method, in this case accountNotFound, has no bearing on the work flow involved The exception handling method must, however, return ModelAndView and have three parameters: HttpServletRequest, HttpServletResponse, and an instance of Throwable If no exception handling method is found for a type... allows you to write one exception handling method to encompass an entire exception class hierarchy 1 75 584X_Ch06_FINAL 176 1/30/06 1:40 PM Page 176 CHAPTER 6 ■ THE CONTROLLER MENAGERIE Summary In classic Spring Framework style, the MultiActionController looks simple and straightforward, but can be very configurable and flexible It allows for one Controller instance to handle many different requests by... different ways to accomplish a client redirect with Spring MVC and the Servlet API The first method, HttpServletResponse.sendRedirect(), uses the Servlet API to correctly send the redirect response You may use this method only if the response has not been committed, however Spring MVC treats redirects as just another type of view with its org.springframework web. servlet.view.RedirectView class This class . MENAGERIE 152 58 4X_Ch06_FINAL 1/30/06 1:40 PM Page 152 If you’ve made it this far, the Controller will assemble the full model with the form object, any possible errors from potential binding, and the. errors) onSubmit(formBean) doSubmitAction(formBean) CHAPTER 6 ■ THE CONTROLLER MENAGERIE 155 58 4X_Ch06_FINAL 1/30/06 1:40 PM Page 155 public String getFavoriteProgrammingLanguage() { return favoriteProgrammingLanguage; } public. ■ THE CONTROLLER MENAGERIE 158 58 4X_Ch06_FINAL 1/30/06 1:40 PM Page 158 protected void doSubmitAction(Object command) throws Exception { Person person = (Person) command; // persist the object,