DekGenius.com
Previous Section  < Day Day Up >  Next Section

8.5 Presentation

In most places, the Spring framework doesn't reinvent working technologies. In the area of presentation logic, though, Spring introduces a simple model-view-controller framework called MVC Web that has many competing architectures, like Struts and Java Server Faces (JSF). Take solace, though. You don't have to use MVC Web to use Spring. But if you decide to do so, you will find a few advantages:

  • MVC Web is based on interfaces rather than inheritance. As we discussed in Chapter 3, interfaces often give better flexibility and looser coupling than inheritance-based designs.

  • MVC Web does not dictate your choice of view. Other frameworks tend to provide better support for favored view technologies, such as Velocity (proprietary) and Struts (JSP). For example, Struts exposes the model via request attributes. As a result, you need to build a bridge servlet to use a technology such as Velocity that doesn't understand the Servlet API. Spring exposes the model through a generic map, so it can work with any view technology.

  • MVC Web provides consistent configuration across all aspects of a Spring application. It uses the same inversion-of-control paradigm that the other frameworks use.

  • MVC Web makes testing easier. Since you don't have to extend another class (like Action or ActionForm in Struts), you can easily mock the request and response.

If you've ever used Struts, you're familiar with the basic paradigm of MVC Web. Figure 8-4 shows how it works. Controllers basically handle all incoming requests from input views. If the input request is a submitted form, the controller calls a business validation routine, created and configured by the programmer, and sends either the associated error view or success view back to the user, based on results.

Figure 8-4. The MVC Web framework works much like Struts
figs/bflJ_0804.gif


8.5.1 Configuration

As with other elements of the Spring framework, when you're trying to understand a new application, start with the configuration files and drill down from there. In this example, the user interface is configured in the petstore-servlet.xml file.

Consider HTML pages that search for products in a category, and search for products based on keywords. The configuration file needs two controllers to the application context file. Each entry specifies a controller and the model object, like in Example 8-10.

Example 8-10. Excerpt from web.xml
  <bean name="/shop/searchProducts.do" 
    class="jpetstore.web.spring.SearchProductsController">
    <property name="petStore"><ref bean="petStore"/></property>
  </bean>

  <bean name="/shop/viewProduct.do" class="org.springframework.samples.jpetstore.web.
spring.ViewProductController">
    <property name="petStore"><ref bean="petStore"/></property>
  </bean>

Recall that all access to our data layer goes through the façade. As you'd expect, these bean ID entries specify the façade, called petstore. Each form in the application works in the same way. Let's drill down further and look at the controller for searchProducts.

8.5.2 Controllers

For MVC Web, each form generally shares a single instance of a controller, which routes all requests related to a given form. It also marshals the form to the correct validation logic and returns the appropriate view to the user. Example 8-11 shows the controller for the searchProducts view.

Example 8-11. SearchProductsController.java
      public class SearchProductsController implements Controller {


        [1] private PetStoreFacade petStore;

        public void setPetStore(PetStoreFacade petStore) {
          this.petStore = petStore;
        }

[2] public ModelAndView handleRequest(HttpServletRequest request,
                                          HttpServletResponse response) 
          throws Exception {

        [3] if (request.getParameter("search") != null) {
            String keyword = request.getParameter("keyword");
            if (keyword == null || keyword.length( ) == 0) {
               return new ModelAndView("Error", 
                                "message", 
        "Please enter a keyword to search for, then press the search button.");
            }
            else {
                [4]  PagedListHolder productList = new PagedListHolder(
                     this.petStore.searchProductList(keyword.toLowerCase( )));
           productList.setPageSize(4);
           request.getSession( ).setAttribute(
                     "SearchProductsController_productList", productList);
           [5]  return new ModelAndView("SearchProducts", "productList", productList);
          }
         }
         else {
        [6] String page = request.getParameter("page");  
           PagedListHolder productList = (PagedListHolder) request.getSession( ).
             getAttribute("SearchProductsController_productList");
           if ("next".equals(page)) {
             productList.nextPage( );
           }
           else if ("previous".equals(page)) {
             productList.previousPage( );
           }
           return new ModelAndView("SearchProducts", "productList", productList);
         }
       }

     }

Here's what the annotations mean:

[1] Each controller has access to the appropriate domain model. In this case, it's natural for the view to access the model through our façade.
[2] A controller has an interface like a servlet, but isn't actually a servlet. User requests instead come in through a single dispatcher servlet, which routes them to the appropriate controller, populating the request membermeter. The controller merely responds to the appropriate request, invoking business data and routing control to the appropriate page.
[3] In this case, the request is to "search." The controller must parse out the appropriate keywords.
[4] The controller invokes the business logic with the keywords provided by the user.
[5] The controller routes the appropriate view back to the user (with the appropriate model).
[6] In this case, the request is "page." Our user interface supports more products than might fit on a single page.

8.5.3 Forms

Just like Struts, Spring can map HTML forms onto Java objects. Example 8-12 is the Java bean that's returned when a Pet Store user registers an account.

Example 8-12. AccountForm.java
public class AccountForm {

  private Account account;

  private boolean newAccount;

  private String repeatedPassword;

  public AccountForm(Account account) {
    this.account = account;
    this.newAccount = false;
  }

  public AccountForm( ) {
    this.account = new Account( );
    this.newAccount = true;
  }

  public Account getAccount( ) {
    return account;
  }

  public boolean isNewAccount( ) {
    return newAccount;
  }

  public void setRepeatedPassword(String repeatedPassword) {
    this.repeatedPassword = repeatedPassword;
  }

  public String getRepeatedPassword( ) {
    return repeatedPassword;
  }

}

Each of these bean fields corresponds directly to an HTML input field or control. The Spring framework translates a submit request to the form, which can then be accessed as a POJO for validation, mapping input data, or other purposes. With Spring, unlike Struts, form objects can be any Java bean. There's no need to extend ActionForm. That's important, because you don't need to copy properties from an ActionForm to a domain object or value object.

8.5.4 Validation

You may have noticed validation logic within the original applciationContext.xml. These beans are generally considered business logic, but they've got a tight relationship to the user interface and they're invoked directly by the Spring framework. When a user submits a form, Spring fires the validation logic. Based on the result, Spring routes control to the appropriate page. Example 8-13 shows the AccountValidator class, which validates the account form.

Example 8-13. AccountValidator.java
public class AccountValidator implements Validator {

        public boolean supports(Class clazz) {
                return Account.class.isAssignableFrom(clazz);
        }

        public void validate(Object obj, Errors errors) {
                ValidationUtils.rejectIfEmpty(errors, "firstName", "FIRST_NAME_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "lastName", "LAST_NAME_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "email", "EMAIL_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "phone", "PHONE_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "address1", "ADDRESS_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "city", "CITY_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "state", "STATE_REQUIRED", 
                ValidationUtils.rejectIfEmpty(errors, "zip", "ZIP_REQUIRED", "ZIP is required.");
                ValidationUtils.rejectIfEmpty(errors, "country", "COUNTRY_REQUIRED", 
        }
}

In this example, the Spring framework makes life easier for developers in several ways. The developer does not need to write validation methods by hand. Also, many prepackaged methods exist. The framework takes care of validation and routing. The framework takes care of routing control based on success or failure.

One chapter on the Spring framework cannot do it justice, but you've seen the overall gist of it. The advantages of the framework—and more importantly, this coding style—should jump off the page at you if you haven't seen it before. In particular, notice the clarity and simplicity that a cleanly layered architecture provides. You can probably imagine how easy it is to incorporate business logic with a transparent framework like Spring.

    Previous Section  < Day Day Up >  Next Section