拦截器是 SpringMVC 的技术,用于拦截控制层处理的 HTTP 请求,起作用有两种:
-
在请求前和请求后执行特定代码,对请求进行功能增强。
-
阻止请求的执行(用于鉴权等)。
过滤器(Filter
)是 Tomcat 的技术,拦截器是 SpringMVC 的技术:
关于拦截器的详细说明可以观看这个。
2.快速入门
这里使用的示例项目是 ,对应的测试数据是。
2.1.定义拦截器
创建拦截器需要实现HandlerInterceptor
接口:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
这个接口有三个方法:
-
preHandle
,在控制层方法调用前执行。 -
postHandle
,在控制层方法调用后执行。 -
afterCompletion
,在 HTTP 请求结束后执行。
这三个方法的调用过程可以表示为:
这三个方法都有默认实现,这里实现这个方法,并进行一些简单打印:
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
拦截器是用于控制层的,可以放在控制层的包下(比如
xxx.controller.interceptor
),这样也方便 SpringMVC 配置类的组件扫描。
为了能让 Spring 管理这个拦截器,用@Component
标记。
2.2.注册拦截器
需要在 Spring 配置中注册这个拦截器:
public class SpringMvcSupport extends WebMvcConfigurationSupport {
private MyInterceptor myInterceptor;
// ...
protected void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(myInterceptor).addPathPatterns("/books","/books/*");
}
}
addPathPatterns
方法说明了拦截器作用的请求路径。在上面这个示例中,对于所有的/books
或/books/*
请求都有效,比如GET http://localhost/books/2
这个请求,控制台输出:
preHandle... getBookInfo... postHandle... afterCompletion...
getBookInfo...
是在接口调用中添加的打印信息。
2.3.阻止请求
如果拦截器的preHandle
方法返回false
,HTTP 请求将被阻止,控制层方法就不会被调用:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return false;
}
控制台输出:
preHandle...
HTTP 响应报文:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Wed, 30 Aug 2023 11:37:21 GMT
返回的 HTTP 报文体为空。
2.4.WebMvcConfigurer
最后,SpringMVC 的相关注册方法除了用WebMvcConfigurationSupport
实现外,还可以选择让配置类实现WebMvcConfigurer
接口:
"cn.icexmoon.mvcdemo.controller"})
({
public class SpringMvcConfig implements WebMvcConfigurer {
private MyInterceptor myInterceptor;
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
public void addInterceptors(InterceptorRegistry registry) {
WebMvcConfigurer.super.addInterceptors(registry);
registry.addInterceptor(myInterceptor).addPathPatterns("/books", "/books/*");
}
}
现在SpringMvcConfig
只需要扫描cn.icexmoon.mvcdemo.controller
包即可。
3.拦截器参数
3.1.HttpServletRequest
利用这个参数,我们可以访问 HTTP 请求报文中的内容,比如打印请求报文头信息:
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
HttpUtil.printHeaders(request);
return true;
}
// ...
}
这里使用了一个工具函数,用于打印头信息:
public class HttpUtil {
/**
* 打印 HTTP 请求报文头
* @param request
*/
public static void printHeaders(HttpServletRequest request){
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String name = headerNames.nextElement();
Enumeration<String> headerValues = request.getHeaders(name);
List<String> headerValuesList = new ArrayList<>();
while (headerValues.hasMoreElements()){
headerValuesList.add(headerValues.nextElement());
}
String values = headerValuesList.stream().collect(Collectors.joining(","));
System.out.println(String.format("%s:%s", name, values));
}
}
}
打印结果:
user-agent:PostmanRuntime-ApipostRuntime/1.1.0 cache-control:no-cache content-type:application/json accept:*/* accept-encoding:gzip, deflate, br connection:keep-alive host:localhost
3.2.HttpServletResponse
类似的,可以在拦截器中利用HttpServletResponse
类型的参数获取响应报文信息,比如打印响应报文头:
@Component
public class MyInterceptor implements HandlerInterceptor {
// ...
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
HttpUtil.printResponseHeaders(response);
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}
工具类
HttpUtil
的相关方法这里不再赘述,可以参考文末的完整示例。
3.3.HandlerMethod
控制层中的handler
参数实际上是控制层方法的句柄,其真实类型是HandlerMethod
,包含一个反射类型Method
的属性,因此我们可以用它来获取拦截到的控制层方法信息:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
HttpUtil.printRequestHeaders(request);
HandlerMethod handlerMethod = (HandlerMethod) handler;
HttpUtil.printHandlerMethod(handlerMethod);
return true;
}
3.4.Exception
afterCompletion
方法中有个Exception
类型的参数,它代表控制层方法调用产生的异常。像说的那样,一般我们会使用异常拦截器来处理异常,所以这里的异常对象一般不会使用。
5.拦截器链
对同一个请求,可以配置多个拦截器进行处理。
比如这里定义三个拦截器:
@Component
public class MyInterceptor1 implements HandlerInterceptor {
// ...
}
@Component
public class MyInterceptor2 implements HandlerInterceptor {
// ...
}
@Component
public class MyInterceptor3 implements HandlerInterceptor {
// ...
}
对三个拦截器进行注册:
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor1 myInterceptor1;
@Autowired
private MyInterceptor2 myInterceptor2;
@Autowired
private MyInterceptor3 myInterceptor3;
// ...
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebMvcConfigurer.super.addInterceptors(registry);
registry.addInterceptor(myInterceptor1).addPathPatterns("/books", "/books/*");
registry.addInterceptor(myInterceptor2).addPathPatterns("/books", "/books/*");
registry.addInterceptor(myInterceptor3).addPathPatterns("/books", "/books/*");
}
}
执行请求后控制台输出:
preHandle1... preHandle2... preHandle3... getBookInfo... postHandle3... postHandle2... postHandle1... afterCompletion3... afterCompletion2... afterCompletion1...
preHandle
的调用顺序与拦截器的注册顺序是一致的,postHandle
和afterCompletion
的调用顺序与拦截器的注册顺序是相反的。
特殊的是,如果有拦截器的preHandle
方法返回false
,其后面的拦截器的preHandle
方法不会被执行,所有拦截器的postHandle
方法都不会被执行。其之前的afterCompletion
方法会执行。
这里举例说明。
如果让MyInterceptor3
的preHandle
方法返回false
:
preHandle1... preHandle2... preHandle3... afterCompletion2... afterCompletion1...
如果让MyInterceptor2
的preHandle
方法返回false
:
preHandle1... preHandle2... afterCompletion1...
如果让MyInterceptor1
的preHandle
方法返回false
:
preHandle1...
这个过程可以用下图表示:
The End,谢谢阅读。
本文的完整示例可以从获取。
文章评论