Defining a common WebContentInterceptor

In this recipe, we will highlight how we have implemented a WebContentInterceptor superclass for Controllers.

Getting ready

We are about to present a Controller superclass having the specificity of being registered as a WebContentInterceptor. This superclass allows us to globally control sessions and to manage caching options.

It will help us understanding the request lifecycle throughout the Framework and through other potential interceptors.

How to do it...

  1. Registering a default WebContentInterceptor with its specific configuration can be done entirely with the configuration approach:
    <mvc:interceptors>
      <bean id="webContentInterceptor" class="org.sfw.web.servlet.mvc.WebContentInterc	eptor">
        <property name="cacheSeconds" value="0"/>  
        <property name="requireSession" value="false"/>  
        ...
      </bean>
    <mvc:interceptors>

    Tip

    In our application, we have registered custom WebContentInterceptors to override the behaviors of the default one.

  2. In the codebase, still from the previously checked-out v2.x.x branch, a new cloudstreetApiWCI class can be found in cloudstreetmarket-api:
    public class CloudstreetApiWCI extends WebContentInterceptor {
      public CloudstreetApiWCI(){
        setRequireSession(false);
        setCacheSeconds(0);
      }
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
          super.preHandle(request, response, handler);
          return true;
      }
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 	ModelAndView modelAndView) throws Exception {
      }
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      }
    }
  3. A similar CloudstreetWebAppWCI is also present in cloudstreetmarket-webapp:
    public class CloudstreetWebAppWCI extends WebContentInterceptor {
      public CloudstreetWebAppWCI(){
        setRequireSession(false);
        setCacheSeconds(120);
        setSupportedMethods("GET","POST", "OPTIONS", "HEAD");
      }
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse  response, Object handler) throws ServletException {
          super.preHandle(request, response, handler);
          return true;
      }
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView 	modelAndView) throws Exception {
      }
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      }
    }
  4. In cloudstreetmarket-webapp, DefaultController and InfoTagController now both inherit CloudstreetWebAppWCI:
    public class InfoTagController extends CloudstreetWebAppWCI {
    ...
    }
    public class DefaultController extends CloudstreetWebAppWCI {
    ...
    }
  5. In cloudstreetmarket-webapp the dispatcher-context.xml context file registers the interceptor:
    <mvc:interceptors>
      <bean 	class="edu.zc...controllers.CloudstreetWebAppWCI">
        <property name="cacheMappings">
          <props>
            <prop key="/**/*.js">86400</prop>
            <prop key="/**/*.css">86400</prop>
            <prop key="/**/*.png">86400</prop>
            <prop key="/**/*.jpg">86400</prop>
          </props>
        </property>
      </bean> 
    </mvc:interceptors>
  6. In the cloudstreetmarket-api, dispatcher-context.xml, the other interceptor has also been registered:
      <mvc:interceptors>
        <bean class="edu.zc...controllers.CloudstreetApiWCI"/>
      </mvc:interceptors>
  7. Finally, in both dispatcher-context.xml, the RequestMappingHandlerAdapter bean has been given the synchronizeOnSession property:
    <bean class="org.sfw...annotation.RequestMappingHandlerAdapter">
        <property name="synchronizeOnSession" value="true"/>
        </bean>

How it works...

In each web module, we have created a superclass for Controllers. In the cloudstreetmarket-webapp module for example, both InfoTagController and DefaultController now inherit the CloudstreetWebAppWCI superclass.

Common behaviors for Controllers

Beyond the WebContentInterceptor capabilities, it is more than a good practice to share common logic and attributes between controllers if they relate to configuration (application or business); the idea is to avoid creating another service layer. We will see with further implementations that it is a good place for defining user contexts.

A WebContentInterceptor through its WebContentGenerator superclass offers useful request and session management tools that we are going to present now. As an interceptor, it must be registered declaratively. This is the reason why we have added two <mvc:interceptors> entries in our context files.

Global session control

A WebContentInterceptor, handling requests provides the ability to control how the application should react with HTTP sessions.

Requiring sessions

The WebContentInterceptor through WebContentGenerator offers the setRequireSession(boolean) method. This allows defining whether or not a session should be required when handling a request.

If there is no session bound to the request (if the session has expired for example), the controller will throw a SessionRequiredException method. In such cases, it is good to have a global ExceptionHandler defined. We will set up a global exception mapper when we will build the REST API. By default, the sessions are not required.

Synchronizing sessions

Another interesting feature comes with the synchronizeOnSession property that we have set to true in the RequestMappingHandlerAdapter definition. When set it to true, the session object is serialized and access to it is made in a synchronized block. This allows concurrent access to identical sessions and avoids issues that sometimes occur when using multiple browser windows or tabs.

Cache-header management

With the setCacheSeconds(int) method that we have used in the constructors of CloudstreetWebAppWCI and CloudstreetApiWCI; the WebContentInterceptor with WebContentGenerator can manage a couple of HTTP response headers related to caching.

Set to zero, it adds the extra headers in the response such as Pragma, Expires, Cache-control, and so on.

We have also defined custom caching for static files at the configuration level:

<props>
  <prop key="/**/*.js">86400</prop>
  <prop key="/**/*.css">86400</prop>
  <prop key="/**/*.png">86400</prop>
  <prop key="/**/*.jpg">86400</prop>
</props>

All our static resources are cached in this way for 24 hours, thanks to the native WebContentInterceptor.preHandle method.

HTTP method support

We have also defined a high-level restriction for HTTP methods. It can be narrowed down by the @RequestMapping method attribute at the Controller level. Accessing a disallowed method will result in 405 HTTP error: Method not supported.

A high-level interceptor

In the Interceptor registration in dispatcher-context.xml, we haven't defined a path mapping for the interceptor to operate on. It is because by default Spring applies the double wildcard operator /** on such standalone interceptor definitions.

It is not because we have made DefaultController, extending an interceptor, that the interceptor is acting on the Controller @RequestMapping path. The interceptor's registration is only made through configuration. If the covered path mapping needs to be modified, we could override our registration in the following way:

  <mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="edu.zc.csm.portal...CloudstreetWebAppWCI">
    <property name="cacheMappings">
      <props>
      <prop key="/**/*.js">86400</prop>
      <prop key="/**/*.css">86400</prop>
      <prop key="/**/*.png">86400</prop>
      <prop key="/**/*.jpg">86400</prop>
      </props>
    </property>
    </bean>
    </mvc:interceptor>
  </mvc:interceptors>

We have also overridden the WebContentInterceptor method's preHandle, postHandle, and afterCompletion. It will allow us later to define common business related operations before and after the Controller request handling.

Request lifecycle

Throughout the interceptor(s), each request is processed according to the following lifecycle:

  • Prepare the request's context
  • Locate the Controller's handler
  • Execute interceptor's preHandle methods
  • Invoke the Controller's handler
  • Execute interceptor's postHandle methods
  • Handle the Exceptions
  • Process the View
  • Execute interceptor's afterCompletion methods

To better understand the sequence, especially when Exceptions occur, the following workflow is very useful:

Request lifecycle

Reference: Spring And Hibernate by Santosh Kumar K.

From this diagram, you can see that:

  • The controller handler is invoked, unless one of the interceptors' preHandle methods throws an exception.
  • An interceptor's postHandle method is called when the controller's handler finishes without throwing an exception and if no preceding postHandler method has thrown an exception.
  • An interceptor's afterCompletion is always called, unless a preceding afterCompletion throws an exception.

Obviously, if no Interceptor is registered, the same sequence applies, skipping the interceptors' steps.

There is more...

There is more to say about the WebContentGenerator class.

More features offered by WebContentGenerator

Again, WebContentGenerator is a superclass of WebContentInterceptor. From its JavaDoc page: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/support/WebContentGenerator.html you can find the following for example:

  • Three constants (String) METHOD_GET, METHOD_POST, and METHOD_HEAD refer to the values GET, POST, and HEAD
  • Some caching specific methods such as setUseExpiresHeader, setUseCacheControlHeader, setUseCacheControlNoStore, setAlwaysMustRevalidate, and preventCaching

Also, with WebApplicationObjectSupport, WebContentGenerator provides:

  • Access to ServletContext out of the request or response object through getServletContext().
  • Access to the temporary directory for the current web application, as provided by the servlet container through getTempDir().
  • Access to the WebApplicationContext through getWebApplicationContext().
  • Also, a couple of tools to set and initialize the ServletContext and the WebApplicationContext, even if these tools are initially intended for use within the Framework itself.

See also...

We quickly passed through web caching. There are a lot of customizations and standards in this domain. Also, a new RequestMappingHandlerAdapter has been created with Spring MVC 3.1. It will be helpful to understand the change.

Web caching

Find out more about web caching through this very complete caching tutorial:

https://www.mnot.net/cache_docs

New support classes for @RequestMapping since Spring MVC 3.1

We have used the RequestMappingHandlerAdapter with its bean definition in dispatcher-context.xml. This bean is a new feature with Spring MVC 3.1 and has replaced the former AnnotationMethodHandlerAdapter. Also, the support class DefaultAnnotationHandlerMapping has now been replaced by RequestMappingHandlerMapping.

We will go deeper into RequestMappingHandlerAdapter in Chapter 4, Building a REST API for a Stateless Architecture.

In the meantime, you can read the official change note:

http://docs.spring.io/spring-framework/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-31-vs-30

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset