1. Introduction
You might have asked yourself the following questions one day How can we intercept a request before it hits the controller? How can I add an attribute, modify something, perform a security check, and so on? In this article, we will explore how to achieve these tasks using the Spring MVC interface HandlerInterceptor
. Additionally, we will understand when handler interceptors get invoked within the request processing workflow. Without further ado, let’s dive into our amazing article!
2. Code Example
Please find the working code example on GitHub.
3. HandlerMapping & HandlerExecutionChain
3.1. Understand the workflow
Before we begin implementing our interceptors, it’s crucial to understand the HandlerMapping and HandlerExecutionChain Spring MVC components.
Here is a straightforward diagram that visually explains the workflow of a request within Spring MVC.
- The client sends an HTTP request.
- DispatcherServlet consults the configured HandlerMappings to determine which handler should handle the request(controller, endpoint, …..).
- If a suitable handler is found, the corresponding HandlerExecutionChain is created (HandlerExecutionChain includes the handler and some interceptors)
- DispatcherServlet calls the appropriate HandlerAdapter based on the handler type.
- If the handler returns a logical view name, DispatcherServlet resolves it to an actual view using configured ViewResolvers.
- view rendering using the model object
- The rendered view is sent back as an HTTP response to the client.
Please refer to this article for more information.
3.2. HandlerExecutionChain Overview
In the previous section, we explored the optimized workflow of the Spring MVC framework, with a focus on the handler execution process. However, we didn’t delve into the interceptors that are part of the HandlerExecutionChain alongside the handler returned from the handler mapping. Let’s now examine these interceptors and understand their purpose in the request processing flow.
3.3. Understanding Interceptors
Interceptors are classes that are invoked before hitting the handler. We utilize interceptors when we need to execute specific logic before or after the handler (endpoint method or controller). For example, we can develop an IPInterceptor interceptor, which checks if the HTTP request originates from a blacklisted IP address.
Now, let’s visualize the workflow including these interceptors:
4. Spring MVC HandlerInterceptor interface
To create an interceptor, we need to implement the HandlerInterceptor
interface provided by Spring MVC. Here is the source code for our HandlerInterceptor
interface, which comprises three methods. We will implement these methods according to our specific use case:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
Now, let’s delve into each method and understand when they are invoked:
preHandle()
- This method is invoked before the actual handler method of the controller is called.
- It can be used to perform pre-processing tasks such as logging, IP whitelisting/blacklisting, etc.
- If this method returns true, the handler method is executed. If it returns false, the handler execution is skipped, and the response is sent directly (useful for blocking access based on certain conditions).
postHandle()
- This method is invoked after the handler method has been called but before the view is rendered.
- It allows you to modify the ModelAndView object before the view is rendered.
- Useful for tasks like adding attributes to the model, modifying the view, or logging post-processing actions.
afterCompletion()
- This method is invoked after the view has been rendered (after rendering the view to the client).
- It is typically used for cleanup tasks or resource release tasks that need to be performed after the request has been completed, regardless of the outcome.
- The Exception parameter (ex) allows you to handle exceptions that occurred during the execution of the handler method or view rendering.
5. IPInterceptor Example
5.1. Setting Up the Project
In this Spring MVC series, we opt for a non-Spring Boot project to have full control over our setup. Let’s begin by creating a simple Maven web project.
Once the project is created, add the Spring Web MVC dependency to your pom.xml
file and synchronize the project:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.0</version>
</dependency>
To run the project, ensure you have Tomcat installed and deploy the project to it. After deployment, access your project via the URL http://localhost:8080/project-name/
In a previous article, we discussed the three methods for configuring the DispatcherServlet in Spring 6. You can choose your preferred method; I’ll use the AbstractAnnotationConfigDispatcherServletInitializer approach here.
public class DSConfiguration extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{AppConfig.class};
}
}
AppConfig is our configuration class used for component scanning and declaring our beans ( ex: ViewResolver Bean).
@Configuration
@ComponentScan(basePackages = "com.javalaunchpad")
public class AppConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Having completed the setup, we’re now ready to dive into creating our interceptor!
5.2. IPInterceptor Interceptor
In this example, we'll create an interceptor to intercept requests and verify if they are not coming from a blacklisted IP address.
- IPInterceptor class
The IPInterceptor
class implements the HandlerInterceptor
interface, providing methods to intercept requests at different stages of processing.
public class IPInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Logic to check if the request IP is blacklisted
if (IPUtils.isIpBlacklisted(IPUtils.getClientIpAddr(request))) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // Set status to 403 if blacklisted
return false; // Stop further processing
}
return true; // Continue processing
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// Post-handle logic (not used in this example)
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// After-completion logic (not used in this example)
}
}
- IPUtils class
The IPUtils
class provides utility methods to handle IP-related operations, such as retrieving the client’s IP address and checking if an IP is blacklisted.
public class IPUtils {
static String[] blacklistedIps = {"", "", ""}; // Placeholder for blacklisted IPs
// Method to get the IP address of the client
public static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
System.out.println("Request IP address: " + ip);
return ip;
}
// Method to test if the IP address is blacklisted
public static boolean isIpBlacklisted(String ip) {
for (String blacklistedIp : blacklistedIps) {
if (blacklistedIp.equals(ip)) {
return true;
}
}
return false;
}
}
5.2. Intercepter Registration
In the AppConfig
class, we register IPInterceptor
as an interceptor for all requests.
@Configuration
@ComponentScan(basePackages = "com.javalaunchpad")
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
// Other config Beans
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new IPInterceptor());
}
}
In non-Spring Boot applications, using ‘@EnableWebMvc’ is important. Without it, the interception won’t work!
NOTE: we can apply IPInterceptor
only to specific URLs by using addPathPatterns
method. This allows you to define patterns for which the interceptor should be applied, providing more fine-grained control over request interception based on URL patterns.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new IPInterceptor()).addPathPatterns("/test");
}
In this example, we are registering the interceptor only for the /test
path.
6. Conclusion
In this Spring MVC guide, we’ve explored the power of the HandlerInterceptor interface in Spring MVC, to implement interceptors for pre-processing and post-processing HTTP requests. Incorporating interceptors adds a layer of control and customization.