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:

Java Program to Implement Bresenham Line Algorithm
Form Validation with AngularJS and Spring MVC
Spring @RequestMapping New Shortcut Annotations
Send email with SMTPS (eg. Google GMail)
Period and Duration in Java
Java Program to Implement Pairing Heap
Giới thiệu Google Guice – Injection, Scope
Java Program to Check Whether a Weak Link i.e. Articulation Vertex Exists in a Graph
Hướng dẫn Java Design Pattern – Abstract Factory
Converting String to Stream of chars
A Custom Data Binder in Spring MVC
Java Program to Implement Quick Sort Using Randomization
Introduction to Spring Cloud Rest Client with Netflix Ribbon
Feign – Tạo ứng dụng Java RESTful Client
Registration – Activate a New Account by Email
Java Program to Implement Patricia Trie
A Guide to Spring Cloud Netflix – Hystrix
The Difference Between Collection.stream().forEach() and Collection.forEach()
Java Program to Implement Find all Cross Edges in a Graph
Java Program to Solve Knapsack Problem Using Dynamic Programming
Derived Query Methods in Spring Data JPA Repositories
Pagination and Sorting using Spring Data JPA
Immutable Objects in Java
Java Program to Implement Binomial Tree
Java Program to Implement Sorted Circular Doubly Linked List
Implementing a Binary Tree in Java
Java Program to Search for an Element in a Binary Search Tree
Java Program to Implement LinkedBlockingQueue API
Convert String to int or Integer in Java
Finding Max/Min of a List or Collection
Guava – Join and Split Collections
Guide to the ConcurrentSkipListMap