Sunday 14 July 2013

Spring MVC

MVC - Model, View, Controller

  • Model - The data of the application (database, web-service, csv file, etc...)
  • View - The view of the data (JSP, XML, HTML, etc...)
  • Controller - The flow of the application (Which page to display, how to display it, etc...)
To implement a controller you need to register it with a Spring DispatcherServlet instance.

An application is made with the following components:
  • One or more controllers
  • A view component such as a JSP
  • A configuration that wires the components together through  XML or annotations
Example-1:
package com.cap.news.web; 

import java.util.List; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import org.springframework.web.servlet.ModelAndView; 
import org.springframework.web.servlet.mvc.AbstractController; 
import com.mycap.mynews.core.MyNewsService; 
import com.mycap.mynews.core.NewsArticle;

/** 
* The HomePageController builds the data model to display on
* the home page. It contains a list of articles. 
*/ 

public class extends AbstractController { 
// Service for business methods 
private GeekNewsService service; 

/** 
* Set the object for presentation (ModelAndView)
*/ 
@Override 
protected ModelAndView handleRequestInternal(
      HttpServletRequest req,
      HttpServletResponse res) 
       throws Exception {
List<NewsArticle> articles = 
    service.getArticles(); 
return new ModelAndView( "home", "articles", articles ); 

/** 
* Injected by Spring 
* @param service
*/ 
public void setGeekNewsService( GeekNewsService service ) { 
  this.service = service; 
}

The HomePageController extends Spring's AbstractController, which provides simple servlet-like functionality. It implements a single method -- handleRequestInternal() -- which accepts HttpServletRequest and HttpServletResponse objects, just as a servlet's service() method would. You could read parameters from the HttpServletRequest, but later I'll show you an easier alternative. The purpose of the AbstractController is to invoke business functionality and to generate a ModelAndViewobject from it.
The ModelAndView object is a container that hosts the model and provides insight to Spring's view resolver about how to locate the view. It is created with three parameters:
  • viewName - Tells the view resolver to show the page identified by "viewName" 
  • modelName - An identifier for the model that will be accessed from the view
  • modelObject - The model itself.
Every Spring MVC application is mapped to a servlet that provides MVC functionality: the DispatcherServlet:
<servlet>
  <servlet-name>news</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

The servlet needs to be mapped to a URI pattern. Lets map all "htm" extensions to it:
<servlet-mapping>
  <servlet-name>news</servlet-name>
  <url-pattern>*.htm</url-pattern>
</servlet-mapping> 

Next, you need to map the MVC beans. By default, Spring MVC looks for them  in an XML file whose name starts with the servlet name followed by -servlet.xml, and location in the WEB-INF directory.
The XML file will contain:
  • The controller
  • The URL mapping strategy (to map URLs to controllers)
  • The view resolver
Listing 2. news-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="/home.htm">homePageController</prop>
      </props>
    </property>
  </bean>

  <bean id="homePageController" class="com.cap.news.web.HomePageController">
    <property name="newsService" ref="newsService" />
  </bean>

  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
      <value>/</value>
    </property>
    <property name="suffix">
      <value>.jsp</value>
    </property>
  </bean>
</beans>

The news-servlet.xml file defines the following elements:
  • HomePageController with the name homePageController. It injects the newsService bean into it, which is created in another Spring mapping file
  • urlMapping bean
  • viewResolver bean.

Handler mappings

Maps incoming web requests to appropriate handlers.
There are some handler mappings you can use out of the box, for example, the SimpleUrlHandlerMapping or the BeanNameUrlHandlerMapping.
A HandlerMapping provides a HandlerExecutionChain. It contains the handler that matches the incoming request, and optionally other handler interceptors (HandlerInterceptor) that are applied to the request. The DispatcherServlet, which is the entry point of URLs to Spring MVC, will hand over requests to the handler mapping, which in turn provides an appropriate HandlerExecutionChain. Then the DispatcherServlet will execute the handler and interceptors in the chain (if any).

Handler mappings (HandlerMapping) are configurable and can optionally contain interceptors (HandlerInterceptor), that are executed before and/or after the actual handler was executed. They can react not only on the URL of the request but also on a state of the session.
The two most common handler mappings are BeanNameUrlHandlerMapping and SimpleUrlHandlerMapping. They share the following properties:
  • interceptors: the list of interceptors to use. See Section 13.4.3, “Intercepting requests - the HandlerInterceptor interface”
  • defaultHandler: the default handler to use. 
  • order: based on the value of the order property, Spring will sort the available handler mappings and apply the first matching handler (see the org.springframework.core.Ordered interface). 
  • alwaysUseFullPath: A boolean property (default: false). if true, the full path within the current servlet context is used (e.g. /testing/viewPage.html); If false, the path within the current servlet mapping is used (e.g. /viewPage.html). 
  • urlDecode: A boolean property (default: true). 
  • lazyInitHandlers: A boolean property (default: false). allows for lazy initialization of singleton handlers. 

BeanNameUrlHandlerMapping

Maps incoming HTTP requests to names of beans, defined in the web application context. Example: To insert an account with an already provided form controller (see Section 13.3.4, “Command controllers”) we could map the HTTP request with the URL http://samples.com/editaccount.form to the appropriate Controller as follows:

<beans>
  <bean id="handlerMapping"
        class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <bean name="/editaccount.form"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
      <property name="formView" value="account"/>
      <property name="successView" value="account-created"/>
      <property name="commandName" value="account"/>
      <property name="commandClass" value="samples.Account"/>
  </bean>
<beans>

All incoming requests for the URL /editaccount.form will now be handled by the form Controller in the source listing above. Of course we have to define a servlet-mapping in web.xml as well, to let through all the requests ending with .form.

<web-app>
  ...
  <servlet>
    <servlet-name>sample</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!-- maps the sample dispatcher to *.form -->
  <servlet-mapping>
    <servlet-name>sample</servlet-name>
    <url-pattern>*.form</url-pattern>
  </servlet-mapping>
  ...
</web-app>


SimpleUrlHandlerMapping

A configurable mapping that has Ant-style path matching capabilities (see org.springframework.util.PathMatcher class).
Example:
Configure the web.xml to enable all requests ending with .html and .form to be handled by the sample dispatcher servlet.






<web-app>
  ...
  <servlet>
    <servlet-name>sample</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- maps the sample dispatcher to *.form -->
  <servlet-mapping>
    <servlet-name>sample</servlet-name>
    <url-pattern>*.form</url-pattern>
  </servlet-mapping>

  <!-- maps the sample dispatcher to *.html -->
  <servlet-mapping>
    <servlet-name>sample</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
  ...
 </web-app>



Configure the handler mapping to:
  • Route the requests for 'help.html' in any directory to the 'helpController', which is a UrlFilenameViewController (See Section 13.3, “Controllers”).
  • Route the requests for a resource beginning with 'view', and ending with '.html' in the directory 'ex' to the 'helpController'.
  • Two further mappings are also defined for 'editAccountFormController'.


<beans>
  <!-- no 'id' required, HandlerMapping beans are automatically detected by the DispatcherServlet -->
  <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <value> /*/account.form=editAccountFormController /*/editaccount.form=editAccountFormController /ex/view*.html=helpController /**/help.html=helpController </value>
    </property>
  </bean>
  <bean id="helpController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
  <bean id="editAccountFormController" class="org.springframework.web.servlet.mvc.SimpleFormController">
    <property name="formView" value="account"/>
    <property name="successView" value="account-created"/>
    <property name="commandName" value="Account"/>
    <property name="commandClass" value="samples.Account"/>
  </bean>
<beans>

Intercepting requests - the HandlerInterceptor interface

Useful when you want to apply specific functionality to certain requests.
Interceptors must implement org.springframework.web.servlet.HandlerInterceptor, which defines three methods that should provide enough flexibility to do all kinds of pre- and post-processing:

  • One that will be called before the actual handler will be executed
  • One that will be called after the handler is executed
  • One that is called after the complete request has finished.
Note: The preHandle(..) method returns a boolean value. You can use this method to break or continue the processing of the execution chain. When this method returns true, the handler execution chain will continue, when it returns false, the DispatcherServlet assumes the interceptor itself has taken care of requests (and, for example, rendered an appropriate view) and does not continue executing the other interceptors and the actual handler in the execution chain.
Example: An interceptor that intercepts all requests and reroutes the user to a specific page if the time is not between 9 a.m. and 6 p.m.





<beans>
  <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
      <list>
        <ref bean="officeHoursInterceptor"/>
      </list>
    </property>
    <property name="mappings">
      <value> /*.form=editAccountFormController /*.view=editAccountFormController </value>
    </property>
  </bean>
  <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
    <property name="openingTime" value="9"/>
    <property name="closingTime" value="18"/>
  </bean>
<beans>


package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {
  private int openingTime;
  private int closingTime;
  public void setOpeningTime(int openingTime) {
    this.openingTime = openingTime;
  }
  public void setClosingTime(int closingTime) {
    this.closingTime = closingTime;
  }
  public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    Calendar cal = Calendar.getInstance();
    int hour = cal.get(HOUR_OF_DAY);
    if (openingTime <= hour < closingTime) {
      return true;
    }
    else {
      response.sendRedirect("http://host.com/outsideOfficeHours.html");
      return false;
    }
  }
}

Any request coming in, will be intercepted by the TimeBasedAccessInterceptor, and if the current time is outside office hours, the user will be redirected to a static html file, saying, for example, he can only access the website during office hours.
As you can see, Spring has an adapter class (the cunningly named HandlerInterceptorAdapter) to make it easier to extend the HandlerInterceptor interface.




Controller Types

  • AbstractController - Provides basic controller functionality. All other controllers extends this controller.
  • MultiActionController - Supports the aggregation of multiple request-handling methods into one controller, which then allows you to group related functionality together. It is capable of mapping requests to method names and then invoking the correct method to handle a particular request.
  • Command controllers - Dynamically bind parameters from HttpServletRequest to data objects. First, lets examine what command controllers are available straight out of the box.
    • AbstractCommandController - Provides writing you own command controller, and binding request parameters to a data object you specify. it also offers validation features and lets you specify what to do with the command object that has been populated with request parameter values.
    • AbstractFormController - Offers form submission support. Using this controller you can model forms and populate them using a command object you retrieve in the controller. The AbstractFormController binds the fields of the form, validates the command object, and hands the object back to the controller to take the appropriate action. Supported features are: invalid form submission (resubmission), validation, and normal form workflow. Use this controller if you need forms, but don't want to specify what views you're going to show the user in the application context.
    • SimpleFormController - Provides more support when creating a form with a corresponding command object. It let's you specify a command object, a viewname for the form, a viewname for page you want to show the user when form submission has succeeded, and more.
    • AbstractWizardFormController - This is an abstract wizard controller that you should extend in order to write your own form controller. You should implement the validatePage(), processFinish() and processCancel() methods. 

No comments:

Post a Comment