一、拦截器核心概念
一、定义
拦截器(Interceptor)是框架级别的组件,用于在请求的不同阶段(如到达控制器之前(也就是接口)、处理完成之后)动态地拦截和处理 HTTP 请求。
二、使用场景
一、用户认证和授权
if (!isValidToken(request.getHeader("Authorization"))) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Unauthorized");
return false;
}
二、请求日志记录
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();
long executionTime = endTime - (long) request.getAttribute("startTime");
System.out.println("URI: " + request.getRequestURI() + " | 耗时: " + executionTime + " ms");
}
三、性能监控
统计每个请求的执行时间并记录慢请求日志。
四、跨域处理
设置跨域响应头(更推荐用
CorsRegistry
实现)。
三、执行流程
preHandle
:请求到达控制器之前执行。- 控制器逻辑(也就是接口):拦截器放行后,执行目标控制器的方法。
postHandle
:控制器逻辑处理完成后(视图渲染前)执行。afterCompletion
:视图渲染完成后执行(或者每个拦截器执行出现异常后执行),用于资源清理或日志输出。
二、拦截器的实现与配置
一、创建拦截器
拦截器通过实现 HandlerInterceptor
接口来定义。该接口包含以下三个核心方法:
preHandle
在目标方法调用之前执行,用于权限验证或日志记录。
返回
true
表示继续处理,返回false
表示中断请求。
postHandle
在目标方法执行之后,视图渲染之前执行。
适合处理返回数据或修改模型数据。
afterCompletion
在视图渲染完成后执行,用于资源清理或捕获异常。
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle: 开始拦截请求");
// 检查用户是否登录
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 拦截请求
}
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 请求处理完成");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 视图渲染完成或拦截器出现异常");
}
}
二、注册拦截器
拦截器需要在配置类中注册,并指定拦截路径和排除路径。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/error", "/static/**"); // 排除的路径
}
}
三、拦截器与过滤器的区别
四 、拦截器的高级用法
一、链式拦截器
可以注册多个拦截器,Spring 会按注册顺序依次执行。
在 preHandle
方法中,任一拦截器返回 false
都会中断后续处理。
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/**");
执行顺序:
preHandle
:AuthInterceptor
->LoggingInterceptor
postHandle
:LoggingInterceptor
->AuthInterceptor
afterCompletion
:LoggingInterceptor
->AuthInterceptor
二、拦截器与异步请求
对于异步请求(如使用
@Async
),postHandle
可能不会被触发。如果需要确保拦截器对异步请求生效,可以使用AsyncHandlerInterceptor
接口。
import org.springframework.web.servlet.AsyncHandlerInterceptor;
public class AsyncLoggingInterceptor implements AsyncHandlerInterceptor {
// 同步和异步请求都可拦截
}
三、动态拦截路径
拦截器的路径规则可以通过配置文件或数据库动态加载。
@Value("${interceptor.paths}")
private List<String> paths;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DynamicPathInterceptor())
.addPathPatterns(paths.toArray(new String[0]));
}
interceptor:
paths:
- /api/**
- /admin/**
五、常见问题
一、preHandle
拦截中断后,如何返回自定义响应?
可以直接通过 HttpServletResponse
写入 JSON 或其他格式的响应:
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"Unauthorized\"}");
return false;
二、拦截器的执行顺序与性能优化
- 尽量减少复杂计算或阻塞操作。
- 使用
excludePathPatterns
排除无需拦截的静态资源或公共接口。- 结合 AOP 实现增强功能
六、拦截器底层原
请求一进来都是通过前端控制器DispatcherServlet进行处理,然后处理器映射器找到具体的handler,在通过处理器适配器,适配到具体的handler中进行处理。
// 直接进入到DispatcherServlet这个类里面的这个方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request. 这里就是获取具体的handler,也就是知道了具体的controller中的那个方法可以处理
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request. 这里就是获取处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 这里就是处理拦截器的preHandle方法。如果返回为false,这里取反,就进入,然后直接退出。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. 这里就是之前研究过的去执行具体的handler,也就是具体的接口,nv就是执行完接口后的返回数据封装的ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 这里就是开始执行拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 这里就开始进行视图解析等各种操作
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 以上操作如果出现异常,都会执行拦截器的afterCompletion发明合法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 以上操作如果出现异常,都会执行拦截器的afterCompletion发明合法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
// HandlerExecutionChain类下的方法,这里会循环执行拦截器链中的preHandle方法,注意,这里是顺序执行,也就是从第一个开始执行
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 如果拦截器链中的方法preHandle,返回为false,进入这里执行拦截器中给的异常方法afterCompletion,然后返回false,中断循环,之后的拦截器都不执行了,如果为true,则执行下一个拦截器
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
// HandlerExecutionChain类下的方法,注意这里就是拦截器出现异常开始执行的方法,这里是倒序执行,也就是开始是先执行preHandle方法的拦截器,如果出现异常就会倒序执行
// afterCompletion方法,也就是之前限制性preHandle方法的拦截器,变成了后执行afterCompletion方法。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
// HandlerExecutionChain类下的方法,这里执行完目标方法,也就是接口中的方法后,执行拦截器中的PostHandle方法,这个也是倒序执行
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
// HandlerExecutionChain类下的方法,无论哪里处理出了异常,都会执行拦截器的AfterCompletion方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
// DispatcherServlet类里面的方法,这里就开始进行视图解析等各种操作
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// 页面渲染完也会执行拦截器的AfterCompletion方法
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
总结:
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
2、先来顺序执行 所有拦截器的 preHandle方法
- 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
3、如果任何一个拦截器返回false。直接跳出不执行目标方法
4、所有拦截器都返回True。执行目标方法
5、倒序执行所有拦截器的postHandle方法。
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion