Introduction to Spring MVC HandlerInterceptor – Spring MVC处理程序拦截器介绍

最后修改: 2016年 8月 16日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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

在本教程中,我们将重点了解Spring MVC的HandlerInterceptor以及如何正确使用它。

2. Spring MVC Handler

2.SpringMVC处理程序

In order to understand how a Spring interceptor works, let’s take a step back and look at the HandlerMapping.

为了理解Spring拦截器是如何工作的,让我们退一步看一下 HandlerMapping

The purpose of HandlerMapping is to map a handler method to a URL. That way, the DispatcherServlet will be able to invoke it when processing a request.

HandlerMapping的目的是将一个处理方法映射到一个URL。这样,DispatcherServlet就能在处理请求时调用它。

As a matter of fact, the DispatcherServlet uses the HandlerAdapter to actually invoke the method.

事实上,DispatcherServlet使用HandlerAdapter来实际调用该方法。

In short, interceptors intercept requests and process them. They help to avoid repetitive handler code such as logging and authorization checks.

简而言之,拦截器拦截请求并处理它们。它们有助于避免重复的处理程序代码,如日志和授权检查。

Now that we understand the overall context, let’s see how to use a HandlerInterceptor to perform some pre- and post-processing actions.

现在我们了解了整体背景,让我们看看如何使用HandlerInterceptor来执行一些前处理和后处理的动作。

3. Maven Dependencies

3.Maven的依赖性

In order to use interceptors, we need to include the spring-web dependency in our pom.xml:

为了使用拦截器,我们需要在我们的pom.xml中包含spring-web依赖性。

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

4. Spring Handler Interceptor

4.Spring处理程序拦截器

Simply put, a Spring interceptor is a class that either extends the HandlerInterceptorAdapter class or implements the HandlerInterceptor interface.

简单地说,Spring拦截器是一个扩展了HandlerInterceptorAdapter类或实现了HandlerInterceptor接口的类。

The HandlerInterceptor contains three main methods:

HandlerInterceptor包含三个主要方法。

  • prehandle() – called before the execution of the actual handler
  • postHandle() – called after the handler is executed
  • afterCompletion() – called after the complete request is finished and the view is generated

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

这三种方法为做各种前后处理提供了灵活性。

A quick note before we go further: To skip the theory and jump straight to examples, skip right ahead to Section 5.

在我们进一步讨论之前,有一个简短的说明。要跳过理论,直接跳到例子,请直接跳到第5节。

Here’s a simple preHandle() implementation:

下面是一个简单的preHandle()实现。

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

Notice that the method returns a boolean value. It tells Spring to further process the request (true) or not (false).

请注意,该方法返回一个boolean值。它告诉Spring进一步处理该请求(true)或不处理(false)。

Next, we have an implementation of postHandle():

接下来,我们有一个postHandle()的实现。

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

The interceptor calls this method immediately after processing the request but before generating the view.

拦截器在处理完请求后但在生成视图之前立即调用该方法。

For example, we may use this method to add an avatar of a logged-in user into a model.

例如,我们可以用这个方法将一个登录用户的头像添加到一个模型中。

The final method we need to implement is afterCompletion():

我们需要实现的最后一个方法是afterCompletion()

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

This method allows us to execute custom logic after the completion of the request processing.

这个 方法允许我们在完成请求处理后执行自定义逻辑。

Moreover, it’s worth mentioning that we can register multiple custom interceptors. To do that, we can use DefaultAnnotationHandlerMapping.

此外,值得一提的是,我们可以注册多个自定义拦截器。要做到这一点,我们可以使用DefaultAnnotationHandlerMapping

5. Custom Logger Interceptor

5.自定义记录器拦截器

In this example, we’ll focus on logging in our web application.

在这个例子中,我们将专注于在我们的Web应用程序中的日志记录。

First, our class needs to implement HandlerInterceptor:

首先,我们的类需要实现HandlerInterceptor

public class LoggerInterceptor implements HandlerInterceptor {
    ...
}

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.

这允许Log4J显示日志,以及指出哪个类目前正在向指定的输出端记录信息。

Next, let’s focus on our custom interceptor implementations.

接下来,让我们关注一下我们的自定义拦截器实现。

5.1. preHandle() Method

5.1 preHandle()方法

As the name implies, the interceptor invokes preHandle() before handling a request.

顾名思义,拦截器在处理一个请求之前调用preHandle()

By default, this method returns true to send the request further to the handler method. However, we can tell Spring to stop the execution by returning false.

默认情况下,该方法返回true,将请求进一步发送到处理方法。然而,我们可以通过返回false告诉Spring停止执行。

We can use the hook to log information about the request’s parameters, such as where the request comes from.

我们可以使用钩子来记录请求的参数信息,比如请求来自哪里。

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

在我们的例子中,我们使用一个简单的Log4J记录器来记录这些信息。

@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 would be to replace passwords, and any other sensitive type of data, with stars.

万一我们在这里遇到了密码,我们需要确保不记录,当然。一个简单的选择是用星星代替密码,以及任何其他敏感类型的数据。

Here’s a quick implementation of how to do this:

下面是如何做的一个快速实现。

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.

最后,我们的目标是获得HTTP请求的源IP地址。

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. postHandle() Method

5.2 postHandle()方法

The interceptor calls this method after the handler execution but before the DispatcherServlet renders the view.

拦截器在处理程序执行之后,但在DispatcherServlet渲染视图之前调用这个方法。

We can use it to add additional attributes to ModelAndView. Another use case would be to compute the request’s processing time.

我们可以用它来为ModelAndView添加额外的属性。另一个用例是计算请求的处理时间。

In our case, we’ll simply log our request just before the DispatcherServlet renders a view:

在我们的案例中,我们将简单地在DispatcherServlet渲染视图之前记录我们的请求。

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

5.3. afterCompletion() Method

5.3.afterCompletion() 方法

We can use this method to obtain request and response data after the view is rendered:

我们可以使用这个方法在视图渲染后获得请求和响应数据。

@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

6.配置

Now that we’ve put all the pieces together, let’s add our custom interceptor.

现在我们已经把所有的部件放在一起,让我们来添加我们的自定义拦截器。

To do that, we need to override the addInterceptors() method:

要做到这一点,我们需要覆盖addInterceptors()方法。

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

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

我们可以通过编辑我们的XML Spring配置文件实现同样的配置。

<mvc:interceptors>
    <bean id="loggerInterceptor" class="com.baeldung.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 that if multiple Spring interceptors are configured, the preHandle() method is executed in the configuration order, whereas postHandle() and afterCompletion() methods are invoked in the reverse order.

请注意,如果配置了多个Spring拦截器,preHandle()方法会按照配置顺序执行,而postHandle()afterCompletion()方法会按照相反顺序调用。

Please bear in mind that we don’t need to annotate our configuration class with @EnableWebMvc if we’re using Spring Boot instead of vanilla Spring.

请记住,如果我们使用Spring Boot而不是vanilla Spring,我们就不需要用@EnableWebMvc 注释我们的配置类。

7. Conclusion

7.结语

This article provided a quick introduction to intercepting HTTP requests using Spring MVC Handler Interceptors.

本文快速介绍了使用Spring MVC处理程序拦截HTTP请求的方法。

All examples and configurations are available over on GitHub.

所有的例子和配置都可以在GitHub上找到