In this recipe, we will highlight how we have implemented a WebContentInterceptor
superclass for Controllers.
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.
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>
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 { } }
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 { } }
DefaultController
and InfoTagController
now both inherit CloudstreetWebAppWCI
:public class InfoTagController extends CloudstreetWebAppWCI { ... } public class DefaultController extends CloudstreetWebAppWCI { ... }
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>
dispatcher-context.xml
, the other interceptor has also been registered:<mvc:interceptors> <bean class="edu.zc...controllers.CloudstreetApiWCI"/> </mvc:interceptors>
dispatcher-context.xml
, the RequestMappingHandlerAdapter
bean has been given the synchronizeOnSession
property:<bean class="org.sfw...annotation.RequestMappingHandlerAdapter"> <property name="synchronizeOnSession" value="true"/> </bean>
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.
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.
A WebContentInterceptor
, handling requests provides the ability to control how the application should react with HTTP 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.
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.
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.
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
.
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.
Throughout the interceptor(s), each request is processed according to the following lifecycle:
postHandle
methodsafterCompletion
methodsTo better understand the sequence, especially when Exceptions occur, the following workflow is very useful:
From this diagram, you can see that:
preHandle
methods throws an exception.postHandle
method is called when the controller's handler finishes without throwing an exception and if no preceding postHandler
method has thrown an exception.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 to say about the WebContentGenerator
class.
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:
METHOD_GET
, METHOD_POST
, and METHOD_HEAD
refer to the values GET
, POST
, and HEAD
setUseExpiresHeader
, setUseCacheControlHeader
, setUseCacheControlNoStore
, setAlwaysMustRevalidate
, and preventCaching
Also, with
WebApplicationObjectSupport
, WebContentGenerator
provides:
ServletContext
out of the request or response object through getServletContext()
.getTempDir()
.WebApplicationContext
through getWebApplicationContext()
.ServletContext
and the WebApplicationContext
, even if these tools are initially intended for use within the Framework itself.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.
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.