原文 - Introduction to Spring MVC HandlerInterceptor
Spring MVC处理程序
为了理解拦截器, 让我们回头看一下HandlerMapping. 它把一个方法映射到一个URL上, 因此当一个请求进来时, DispatcherServlet能够调用对用的方法.
事实上, DispatcherServlet使用HandlerAdapter调用方法(Controller里定义的方法).
现在我们知道了全部上下文 - 拦截器是时候到来了. 我们使用HandlerInterceptor在HTTP请求之前、之后和完成(当视图被渲染)时分别执行一些操作.
拦截器使用了横切概念, 避免了一些重复性代码, 比如日志、改变Spring模型的一些全局参数等.
Maven依赖
使用拦截器需要天机以下依赖:
<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() 在请求结束和视图生成之后执行
HandlerInterceptor 和 HandlerInterceptorAdapter 的区别在于, 第一个需要手动重写上述所有的三个方法, 而第二个只需要实现需要的方法就行.
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配置中, 我们需要重写实现了WebMvcConfigurer的WebConfig类中的addInterceptors()方法:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggerInterceptor());
}
配置被激活之后, 这个拦截器将会处理所有请求.
如果有多个Spring拦截器被配置, preHandle 方法将会被按照配置的顺序执行, 而postHandle和afterCompletion方法将会被按照相反的顺序执行.
如果你使用Spring Boot代替了 Spring, 需要注意的是不要忘了在配置类上使用 @EnableWebMvc
, 否则我们会在启动时丢失自动配置.
注意最新版已经不再建议使用HandlerInterceptorAdapter
了, 而是实现org.springframework.web.servlet.HandlerInterceptor
.
最新版也不再建议直接继承WebConfig
, 而是实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer