在 Spring MVC 中,拦截器可以在控制器的执行过程中进行预处理和后处理操作,从而对请求的处理进行控制和增强。Spring MVC 中提供
HandlerInterceptor
接口用于定义拦截器。
在 HandlerInterceptor
接口中定义了以下三个方法。
-
preHandle
:在控制器执行之前调用。可以在这个方法中进行一些预处理操作,比如身份验证、权限检查等。如果返回值为
true
,则继续执行后续的拦截器链或进入控制器方法;如果返回值为false
,则中断请求处理流程,不执行后续的拦截器链和控制器方法。 -
postHandle
:在控制器执行之后、视图渲染之前调用。可以在这个方法中进行一些后处理操作,比如修改数据模型、添加额外的数据等。该方法中可以对
ModelAndView
对象进行修改。 -
afterCompletion
:在视图渲染完成之后调用。可以在这个方法中进行一些资源清理的操作,比如释放资源、记录日志等。可以获取到
Exception
对象,用于记录异常信息。
单个拦截器的实现
以下是实现单个拦截器的步骤。
定义拦截器
通过实现 HandlerInterceptor
接口定义一个拦截器:
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预处理逻辑,如权限检查
log.info("preHandle方法执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑,可以修改数据模型
log.info("postHandle方法执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 资源清理逻辑,比如释放资源或记录日志
log.info("afterCompletion方法执行");
}
}
配置拦截器
在 Spring MVC 中,需要声明实现 WebMvcConfigurer
接口的配置类,并在配置类中配置拦截器才能使拦截器生效。以下是一个示例,将拦截器配置为拦截所有请求(/**
)。
package test;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Resource
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**");
}
}
测试拦截器
定义一个控制器,用于测试拦截器是否生效。
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class MyController {
@GetMapping("/test")
public String test() {
log.info("控制器方法执行");
return "OK";
}
}
通过 HTTP 请求方式访问控制器方法,控制台输出的结果为:
多个拦截器的实现
定义拦截器
定义多个拦截器,如下例中定义 3 个拦截器。
第一个拦截器:
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预处理逻辑,如权限检查
log.info("【我的拦截器1】preHandle方法执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑,可以修改数据模型
log.info("【我的拦截器1】postHandle方法执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 资源清理逻辑,比如释放资源或记录日志
log.info("【我的拦截器1】afterCompletion方法执行");
}
}
第二个拦截器:
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预处理逻辑,如权限检查
log.info("【我的拦截器2】preHandle方法执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑,可以修改数据模型
log.info("【我的拦截器2】postHandle方法执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 资源清理逻辑,比如释放资源或记录日志
log.info("【我的拦截器2】afterCompletion方法执行");
}
}
第三个拦截器:
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class MyInterceptor3 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预处理逻辑,如权限检查
log.info("【我的拦截器3】preHandle方法执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑,可以修改数据模型
log.info("【我的拦截器3】postHandle方法执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 资源清理逻辑,比如释放资源或记录日志
log.info("【我的拦截器3】afterCompletion方法执行");
}
}
配置拦截器
为上述 3 个拦截器进行配置,指定每个拦截器拦截指定的路径。
package test;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MyMultipleInterceptorConfig implements WebMvcConfigurer {
@Resource
private MyInterceptor1 myInterceptor1;
@Resource
private MyInterceptor2 myInterceptor2;
@Resource
private MyInterceptor3 myInterceptor3;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor1)
.addPathPatterns("/test1");
registry.addInterceptor(myInterceptor2)
.addPathPatterns("/test2");
registry.addInterceptor(myInterceptor3)
.addPathPatterns("/test3");
}
}
测试拦截器
在控制器中为上述的路径增加映射。
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class MyMultipleInterceptorTestController {
@GetMapping("/test1")
public String test1() {
log.info("控制器test1方法执行");
return "OK";
}
@GetMapping("/test2")
public String test2() {
log.info("控制器test2方法执行");
return "OK";
}
@GetMapping("/test3")
public String test3() {
log.info("控制器test3方法执行");
return "OK";
}
}
通过 HTTP 请求方式访问映射路径为 /test2
的控制器方法,控制台输出的结果为:
说明访问指定路径(/test2
)时,只会生效指定的拦截器(MyInterceptor2
)。
多个拦截器的执行顺序
如果配置了多个拦截器,它们之间的执行顺序是什么样的?
修改上例拦截器配置,指定将多个拦截器拦截同一个路径:
package test;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MyMultipleInterceptorConfig implements WebMvcConfigurer {
@Resource
private MyInterceptor1 myInterceptor1;
@Resource
private MyInterceptor2 myInterceptor2;
@Resource
private MyInterceptor3 myInterceptor3;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor1)
.addPathPatterns("/test2");
registry.addInterceptor(myInterceptor2)
.addPathPatterns("/test2");
registry.addInterceptor(myInterceptor3)
.addPathPatterns("/test2");
}
}
输出的结果为:
说明在定义多个拦截器拦截同一个路径时,拦截器的 preHandle
方法是按照拦截器的定义顺序执行,而 postHandle
和 afterCompletion
方法则是按照拦截器的定义顺序倒序执行。
某个拦截器拒绝放行的情况
如果请求被某个拦截器拦截,即在某个拦截器中被拒绝放行,那么会出现什么情况?
继续对上例中的 MyInterceptor2
拦截器进行修改,将 preHandle
方法的返回值修改为 false
,使请求在 MyInterceptor3
中被拦截。
package test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预处理逻辑,如权限检查
log.info("【我的拦截器2】preHandle方法执行");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑,可以修改数据模型
log.info("【我的拦截器2】postHandle方法执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 资源清理逻辑,比如释放资源或记录日志
log.info("【我的拦截器2】afterCompletion方法执行");
}
}
通过 HTTP 请求访问 /test2
,输出的结果为:
上述结果说明在定义了多个拦截器的情况下:
- 请求被任何一个拦截器拦截,目标控制器方法都不会执行;
- 对于拦截请求的拦截器之前的拦截器,它们的
preHandle
方法将会执行,afterCompletion
方法也会执行; - 对于拦截请求的拦截器,它自身的
preHandle
方法将会执行,但是afterCompletion
方法不会执行; - 对于拦截请求的拦截器之后的拦截器,所有的方法都不会执行;
- 对于所有的拦截器,它们的
postHandle
方法都不会执行。