Spring MVC HandlerInterceptor 简介

Alan

Alan

Maintainer of blog

原文 - Introduction to Spring MVC HandlerInterceptor

Spring MVC处理程序#

为了理解拦截器, 让我们回头看一下HandlerMapping. 它把一个方法映射到一个URL上, 因此当一个请求进来时, DispatcherServlet能够调用对用的方法.

事实上, DispatcherServlet使用HandlerAdapter调用方法(Controller里定义的方法).

现在我们知道了全部上下文 - 拦截器是时候到来了. 我们使用HandlerInterceptor在HTTP请求之前、之后和完成(当视图被渲染)时分别执行一些操作.

拦截器使用了横切概念, 避免了一些重复性代码, 比如日志、改变Spring模型的一些全局参数等.

Maven依赖#

使用拦截器需要天机以下依赖:

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

最新版本点此查看.

Spring Handler Intercetpro#

拦截器和Spring框架提供的HandlerMapping一样, 必须实现HandlerInterceptor接口.

这个接口包含三个主要方法:

  • preHanlde() 在处理程序被调用之前执行, 此时视图还未生成
  • postHandle() 在处理程序被调用之后执行
  • afterCompletion() 在请求结束和视图生成之后执行

HandlerInterceptorHandlerInterceptorAdapter 的区别在于, 第一个需要手动重写上述所有的三个方法, 而第二个只需要实现需要的方法就行.

preHandle()简单实现:

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

需要注意的是preHandle方法返回一个boolean值, 用来告诉Spring请求是否应该被后续继续处理.(true表示请求继续被处理, false表示终止后续处理.)

postHandle()简单实现:

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

这个方法在 HTTP请求被HandlerAdapter处理完成且未生成视图之前 调用.

它可以被以多种场景使用, 比如把登录用的头像添加到model中.

实现HandlerInterceptor需要重写的最后一个方法afterCompletion():

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

当视图被生成, 我们可以使用这个钩子方法做一些像收集请求额外诊断数据的事情.

最后需要记住的是HandlerInterceptor被注册成DefaultAnnotationHandlerMapping bean. DefaultAnnotationHandlerMapping bean被用来负责在所有使用@Controller注解的类上应用拦截器. 此外, 你可以在web应用中指定任意数量的拦截器.

自定义日志拦截器#

首先需要创建一个实现HandlerInterceptorAdapter的类:

public class LoggerInterceptor extends HandlerInterceptorAdapter {
...
}

在拦截器里开启日志:

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

接下来关注拦截器的实现:

preHandle方法#

这个方法在请求被处理之前执行, 如果返回true, Spring框架会把请求发送到下一个处理程序方法(或者下一个拦截器). 如果返回false, Spring假设请求被完成处理, 不再需要继续处理.

我们使用这个钩子(指preHandle())但因请求参数信息:

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

在上面这个李自力我们把密码打印出来了,为了去除敏感数据, 下面是getParameters()方法的实现:

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();
}
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();
}

postHandle方法#

这个钩子在HandlerAdapter调用了处理程序(指URL对应的Controller里的方法), 但是DispatcherServlet尚未渲染视图的时候调用.

我们可以使用这个方法给ModelAndView添加一些额外属性, 或者检查处理程序方法在处理客户端请求的耗时.

在我们的例子里, 我们简单打印一下请求:

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

afterCoimpletion方法#

当请求已经结束, 而且视图已经渲染, 我们可以获取到请求和响应数据, 以及异常信息(如果发生了异常):

@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 + "]");
}

配置#

为了把我们新创建的拦截器添加到Spring配置中, 我们需要重写实现了WebMvcConfigurerWebConfig类中的addInterceptors()方法:

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

配置被激活之后, 这个拦截器将会处理所有请求.

如果有多个Spring拦截器被配置, preHandle 方法将会被按照配置的顺序执行, 而postHandleafterCompletion方法将会被按照相反的顺序执行.

如果你使用Spring Boot代替了 Spring, 需要注意的是不要忘了在配置类上使用 @EnableWebMvc, 否则我们会在启动时丢失自动配置.

:::warn 注意最新版已经不再建议使用HandlerInterceptorAdapter了, 而是实现org.springframework.web.servlet.HandlerInterceptor.

最新版也不再建议直接继承WebConfig, 而是实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer :::