In all our previous chapters, we have only seen how to map a request to the Controller's method; once the request reaches the Controller method, we execute some logic and return a logical View name, which can be used by the view resolver to resolve Views, but what if we want to execute some logic before actual request processing happens? Similarly, what if we want to execute some other instruction before dispatching the response?
Spring MVC interceptor intercepts the actual request and response. Interceptors are a special web programming technique, where we can execute a certain piece of logic before or after a web request is processed. In this chapter, we are going to learn more about interceptors. After finishing this chapter, you will know:
As I have already mentioned, interceptors are used to intercept actual web requests before or after processing them. We can relate the concept of interceptors in Spring MVC to the filter concept in Servlet programming. In Spring MVC, interceptors are special classes that must implement the org.springframework.web.servlet.HandlerInterceptor
interface. The HandlerInterceptor
interface defines three important methods, as follows:
preHandle
: This method will get called just before the web request reaches the Controller for executionpostHandle
: This method will get called just after the Controller method executionafterCompletion
: This method will get called after the completion of the entire web request cycleOnce we create our own interceptor, by implementing the HandlerInterceptor
interface, we need to configure it in our web application context in order for it to take effect.
Every web request takes a certain amount of time to get processed by the server. In order to find out how much time it takes to process a web request, we need to calculate the time difference between the starting time and ending time of a web request process. We can achieve that by using the interceptor concept. Let's see how to configure our own interceptor in our project to log the execution time of every web request:
pom.xml
; you can find pom.xml
under the root directory of the project itself.pom.xml
file; select the Dependencies tab and click on the Add button in the Dependencies section.log4j
, Artifact Id as log4j
, and Version as 1.2.17
, select Scope as compile, click the OK button, and save pom.xml
.ProcessingTimeLogInterceptor
under the com.packt.webstore.interceptor
package in the src/main/java
source folder, and add the following code to it:package com.packt.webstore.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class ProcessingTimeLogInterceptor implements HandlerInterceptor { private static final Logger LOGGER = Logger.getLogger(ProcessingTimeLogInterceptor.class); public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { long startTime = System.currentTimeMillis(); request.setAttribute("startTime", startTime); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { String queryString = request.getQueryString() == null ? "" : "?" + request.getQueryString(); String path = request.getRequestURL() + queryString; long startTime = (Long) request.getAttribute("startTime"); long endTime = System.currentTimeMillis(); LOGGER.info(String.format("%s millisecond taken to process the request %s.",(endTime - startTime), path)); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exceptionIfAny){ // NO operation. } }
WebApplicationContextConfig.java
, add the following method to it, and save the file:@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ProcessingTimeLogInterceptor()); }
log4j.properties
under the src/main/resources
directory, and add the following content. Then, save the file:# Root logger option log4j.rootLogger=INFO, file, stdout # Direct log messages to a log file log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File= C:\webstore\webstore- performance.log log4j.appender.file.MaxFileSize=1MB log4j.appender.file.MaxBackupIndex=1 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n # Direct log messages to stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
http://localhost:8080/webstore/market/products
URL, and navigate to some pages; you should able to see the logging in the console as follows:C:webstorewebstore-performance.log
; you can see the same log message in the logging file as well.Our intention was to record the execution time of every request coming to our web application, so we decided to record the execution times in a log file. In order to use a logger, we needed the log4j
library, so we added the log4j
library as a Maven dependency in step 3.
In step 4, we defined an interceptor class named ProcessingTimeLogInterceptor
by implementing the HandlerInterceptor
interface. As we already saw, there are three methods that need to be implemented. We will see each method one by one. The first method is preHandle()
, which is called before the execution of the Controller method:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { long startTime = System.currentTimeMillis(); request.setAttribute("startTime", startTime); return true; }
In the previously shown preHandle
method, we just set the current time value in the request
object for later retrieval. Whenever a request comes to our web application, it first comes through this preHandle
method and sets the current time in the request object before reaching the Controller. We are returning true
from this method because we want the execution chain to proceed with the next interceptor or the Controller itself. Otherwise, DispatcherServlet
assumes that this interceptor has already dealt with the response itself. So if we return false
from the preHandle
method, the request won't proceed to the Controller or the next interceptor.
The second method is postHandle
, which will be called after the Controller method's execution:
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { String queryString = request.getQueryString() == null ? "" : "?" + request.getQueryString(); String path = request.getRequestURL() + queryString; long startTime = (Long) request.getAttribute("startTime"); long endTime = System.currentTimeMillis(); LOGGER.info(String.format("%s millisecond taken to process the request %s.",(endTime - startTime), path)); }
In the preceding method, we are simply logging the difference between the current time, which is considered to be the request processing finish time, and the start time, which we got from the request object. Our final method is afterCompletion
, which is called after the View is rendered. We don't want to put any logic in this method, that's why I left the method empty.
If you don't want to implement all the methods from the HandlerInterceptor
interface in your interceptor class, you could consider extending your interceptor from org.springframework.web.servlet.handler.HandlerInterceptorAdapter
, which is a convenient class provided by Spring MVC as a default implementation of all the methods from the HandlerInterceptor
interface.
After creating our ProcessingTimeLogInterceptor
, we need to register our interceptor with Spring MVC, which is what we have done in step 5 through the addInterceptors
overridden method of WebMvcConfigurerAdapter
:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ProcessingTimeLogInterceptor()); }
In step 6, we have added a log4j.properties
file in order to specify some logger-related configurations. You can see we have configured the log file location in log4j.properties
as follows:
log4j.appender.file.File= C:\webstore\webstore-performance.log
Finally, in step 7 we ran our application in order to record some performance logging, and we were able to see that the logger is just working fine via the console. You can open the log file to view the performance logs.
So we understood how to configure an interceptor and saw ProcessingTimeLogInterceptor
in action. In the next exercise, we will see how to use some Spring-provided interceptors.
Consider the following interceptor:
public class SecurityInterceptor extends HandlerInterceptorAdapter{ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // just some code related to after completion } }
Is this SecurityInterceptor
class a valid interceptor?
HandlerInterceptor
interface.HandlerInterceptorAdapter
class.Within the interceptor methods, what is the order of execution?
preHandle
, afterCompletion
, postHandle
.preHandle
, postHandle
, afterCompletion
.