在本教程中,我们将专注于理解Spring MVC的拦截器HandlerInterceptor以及如何正确使用它。
为了理解Spring拦截器是如何工作的,让我们退一步看看HandlerMapping。HandlerMapping的目的是将一个处理器方法映射到一个URL。这样,当处理请求时,DispatcherServlet就能够调用它。事实上,DispatcherServlet使用HandlerAdapter实际调用该方法。简而言之,拦截器拦截请求并处理它们。他们有助于避免重复的处理器代码,如日志和授权检查。现在我们理解了整体上下文,让我们看看如何使用HandlerInterceptor来执行一些预处理和后处理操作。
为了使用拦截器,我们需要在我们的pom.xml中包含spring-web依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.13</version>
</dependency>
简单来说,Spring拦截器是一个类,它要么扩展HandlerInterceptorAdapter类,要么实现HandlerInterceptor接口。 HandlerInterceptor包含三个主要方法:
这三种方法提供了进行各种预处理和后处理的灵活性。
下面是一个简单的preHandle()实现:
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// your code
return true;
}
注意方法返回一个boolean值。它告诉Spring是否继续处理请求(true)或不处理(false)。
接下来,我们有一个postHandle()的实现:
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// your code
}
拦截器在处理请求后但在生成视图之前立即调用此方法。 例如,我们可以使用此方法将已登录用户的头像添加到模型中。
我们需要实现的最后一个方法是afterCompletion():
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// your code
}
这个方法允许我们在请求处理完成后执行自定义逻辑。
此外,值得一提的是,我们可以注册多个自定义拦截器。为此,我们可以使用DefaultAnnotationHandlerMapping。
在这个例子中,我们将专注于我们的Web应用程序中的日志记录。
首先,我们的类需要实现HandlerInterceptor:
public class LoggerInterceptor implements HandlerInterceptor {
...
}
我们也需要在我们的拦截器中启用日志记录:
private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);
这允许Log4J显示日志,以及指示当前正在将信息记录到指定输出的类。
接下来,让我们关注我们的自定义拦截器实现。
顾名思义,拦截器在处理请求之前调用preHandle()。默认情况下,此方法返回true以将请求进一步发送到处理器方法。然而,我们可以通过返回false告诉Spring停止执行。我们可以记录关于请求参数的信息,比如请求来自哪里。
在我们的例子中,我们正在使用一个简单的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;
}
如果我们在这里遇到一个密码,我们需要确保我们不记录这个密码,当然。一个简单的选项是将密码和任何其他敏感类型的数据用星号替换。 下面是如何实现这一点的一个快速实现:
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();
}
最后,我们的目标是获取HTTP请求的源IP地址。 这是一个简单的实现:
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();
}
拦截器在处理器执行后但在DispatcherServlet渲染视图之前调用此方法。我们可以使用它向ModelAndView添加额外的属性。另一个用例可能是计算请求的处理时间。
在我们的例子中,我们将在DispatcherServlet渲染视图之前简单地记录我们的请求:
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
log.info("[postHandle][" + request + "]");
}
我们可以在视图渲染后使用此方法获取请求和响应数据:
@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 + "]");
}
现在我们已经实现了自定义拦截器,让我们添加我们的自定义拦截器。
为此,我们需要实现WebMvcConfigurer接口覆盖addInterceptors()方法:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggerInterceptor());
}
}
我们也可以通过编辑我们的XML Spring配置文件实现相同的配置:
<mvc:interceptors>
<bean id="loggerInterceptor" class="xx.xx.LoggerInterceptor"/>
</mvc:interceptors>
请注意,如果配置了多个Spring拦截器,preHandle()方法将按配置顺序执行,而postHandle()和afterCompletion()方法则以相反的顺序调用。
请记住,如果我们使用的是Spring Boot而不是原生的Spring,我们不需要用@EnableWebMvc注释我们的配置类。
原文地址: https://www.baeldung.com/spring-mvc-handlerinterceptor
引用: https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/interceptors.html