Introduction to Spring MVC HandlerInterceptor

1. Introduction

In this tutorial we’ll focus on understanding the Spring MVC HandlerInterceptor and how to use it correctly.

2. Spring MVC Handler

In order to understand the interceptor, let’s take a step back and look at the HandlerMapping. This maps a method to a URL, so that the DispatcherServlet will be able to invoke it when processing a request.

And the DispatcherServlet uses the HandlerAdapter to actually invoke the method.

Now that we understand the overall context – this is where the handler interceptor comes in. We’ll use the HandlerInterceptor to perform actions before handling, after handling, or after completion (when the view is rendered), of a request.

The interceptor can be used for cross-cutting concerns and to avoid repetitive handler code like logging, changing globally used parameters in Spring model etc.

In the next few sections that’s exactly what we’re going to be looking at – the differences between various interceptor implementations.

3. Maven Dependencies

In order to use Interceptors, you need to include the following section in a dependencies section of your pom.xml file:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

The latest version can be found here.

4. Spring Handler Interceptor

Interceptors working with the HandlerMapping on the framework must implement the HandlerInterceptor interface.

This interface contains three main methods:

  • prehandle() – called before the actual handler is executed, but the view is not generated yet
  • postHandle() – called after the handler is executed
  • afterCompletion() – called after the complete request has finished and view was generated

These three methods provide flexibility to do all kinds of pre- and post-processing.

And a quick note – the main difference between HandlerInterceptor and HandlerInterceptorAdapter is that in the first one we need to override all three methods: preHandle()postHandle() and afterCompletion(), whereas in the second we may implement only required methods.

A quick note before we go further – if you want to skip the theory and jump straight to examples, jump right into section 5.

Here’s what a simple preHandle() implementation will look like:

@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
    // your code
    return true;
}

Notice the method returns a boolean value – which tells Spring if the request should be further processed by a handler (true) or not (false).

Next, we have an implementation of postHandle():

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    // your code
}

This method is called immediately after the request is processed by HandlerAdapter, but before generating a view.

And it can of course be used in many ways – for example, we may add an avatar of a logged-in user into a model.

The final method we need to implement in the custom HandlerInterceptor implementation is afterCompletion():

@Override
public void afterCompletion(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, Exception ex) {
    // your code
}

When the view is successfully generated, we can use this hook to do things like gather additional statistics related to the request.

A final note to remember is that a HandlerInterceptor is registered to the DefaultAnnotationHandlerMapping bean, which is responsible for applying interceptors to any class marked with a @Controller annotation. Moreover, you may specify any number of interceptors in your web application.

5. Custom Logger Interceptor

In this example we will focus on logging in our web application. First of all, our class needs to extend HandlerInterceptorAdapter:

public class LoggerInterceptor extends HandlerInterceptorAdapter {
    ...
}

We also need to enable logging in our interceptor:

private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);

This allows Log4J to display logs, as well as indicate, which class is currently logging information to the specified output.

Next, let’s focus on custom interceptor implementations:

5.1. Method preHandle()

This method is called before handling a request; it returns true, to allow the framework to send the request further to the handler method (or to the next interceptor). If the method returns false, Spring assumes that request has been handled and no further processing is needed.

We can use the hook to log information about the requests’ parameters: where the request comes from, etc.

In our example, we are logging this info using a simple Log4J logger:

@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
    
    log.info("[preHandle][" + request + "]" + "[" + request.getMethod()
      + "]" + request.getRequestURI() + getParameters(request));
    
    return true;
}

As we can see, we’re logging some basic information about the request.

In case we run into a password here, we’ll need to make sure we don’t log that of course.

A simple option is to replace passwords, and any other sensitive type of data, with stars.

Here’s a quick implementation of how that can be done:

private String getParameters(HttpServletRequest request) {
    StringBuffer posted = new StringBuffer();
    Enumeration<?> e = request.getParameterNames();
    if (e != null) {
        posted.append("?");
    }
    while (e.hasMoreElements()) {
        if (posted.length() > 1) {
            posted.append("&");
        }
        String curr = (String) e.nextElement();
        posted.append(curr + "=");
        if (curr.contains("password") 
          || curr.contains("pass")
          || curr.contains("pwd")) {
            posted.append("*****");
        } else {
            posted.append(request.getParameter(curr));
        }
    }
    String ip = request.getHeader("X-FORWARDED-FOR");
    String ipAddr = (ip == null) ? getRemoteAddr(request) : ip;
    if (ipAddr!=null && !ipAddr.equals("")) {
        posted.append("&_psip=" + ipAddr); 
    }
    return posted.toString();
}

Finally, we’re aiming to get the source IP address of the HTTP request.

Here’s a simple implementation:

private String getRemoteAddr(HttpServletRequest request) {
    String ipFromHeader = request.getHeader("X-FORWARDED-FOR");
    if (ipFromHeader != null && ipFromHeader.length() > 0) {
        log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader);
        return ipFromHeader;
    }
    return request.getRemoteAddr();
}

5.2. Method postHandle()

This hook runs when the HandlerAdapter is invoked the handler but DispatcherServlet is yet to render the view.

We can use this method to add additional attributes to the ModelAndView or to determine the time taken by handler method to process a client’s request.

In our case, we simply log a request just before DispatcherServlet is going to render a view.

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    
    log.info("[postHandle][" + request + "]");
}

5.3. Method afterCompletion()

When a request is finished and the view is rendered, we may obtain request and response data, as well as information about exceptions, if any occurred:

@Override
public void afterCompletion(
  HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) 
  throws Exception {
    if (ex != null){
        ex.printStackTrace();
    }
    log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
}

6. Configuration

To add our interceptors into Spring configuration, we need to override addInterceptors() method inside WebConfig class that implements WebMvcConfigurer:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoggerInterceptor());
}

We may achieve the same configuration by editing our XML Spring configuration file:

<mvc:interceptors>
    <bean id="loggerInterceptor" class="com.maixuanviet.web.interceptor.LoggerInterceptor"/>
</mvc:interceptors>

With this configuration active, the interceptor will be active and all requests in the application will be properly logged.

Please notice, if multiple Spring interceptors are configured, the preHandle() method is executed in the order of configuration, whereas postHandle() and afterCompletion() methods are invoked in the reverse order.

If we’re using Spring Boot instead of vanilla Spring, we should keep in mind to not annotate our configuration class with @EnableWebMvc, or we’ll lose out on Boot’s auto configurations.

7. Conclusion

This tutorial is a quick introduction to intercepting HTTP requests using Spring MVC Handler Interceptor.

All examples and configurations are available here on GitHub.

Related posts:

A Quick JUnit vs TestNG Comparison
Build a REST API with Spring and Java Config
Setting a Request Timeout for a Spring REST API
Java Program to Implement Gaussian Elimination Algorithm
Java Program to Perform integer Partition for a Specific Case
Spring 5 Functional Bean Registration
Java Program to Implement Bellman-Ford Algorithm
Database Migrations with Flyway
Java Program to Check if any Graph is Possible to be Constructed for a Given Degree Sequence
Wiring in Spring: @Autowired, @Resource and @Inject
The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5
Java Program to Implement wheel Sieve to Generate Prime Numbers Between Given Range
wait() and notify() Methods in Java
Từ khóa throw và throws trong Java
Java Program to Implement Euclid GCD Algorithm
Java Program to Implement CopyOnWriteArraySet API
Remove the First Element from a List
Serialize Only Fields that meet a Custom Criteria with Jackson
Tạo chương trình Java đầu tiên sử dụng Eclipse
Hướng dẫn tạo và sử dụng ThreadPool trong Java
JWT – Token-based Authentication trong Jersey 2.x
Simple Single Sign-On with Spring Security OAuth2
Java Program to Compute Discrete Fourier Transform Using Naive Approach
So sánh ArrayList và LinkedList trong Java
Giới thiệu Java 8
Java NIO2 Path API
Java Program to Find Transitive Closure of a Graph
Java Program to Implement Stack using Linked List
Java Program to Implement Karatsuba Multiplication Algorithm
Java Program to Check Cycle in a Graph using Graph traversal
Java Program to Check if a Given Graph Contain Hamiltonian Cycle or Not
Java Program to Find MST (Minimum Spanning Tree) using Kruskal’s Algorithm