三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

第27篇-拦截器与过滤器

第27篇-拦截器与过滤器

【Kotlin + Spring Boot 4 从零到架构师】第 27 篇:拦截器与过滤器

本系列定位:零基础入门,从 Kotlin 语法一路到 Spring Boot 4 高级架构(DDD + Modulith),适合 Java 开发者转型,也适合纯新手系统学习。


本篇你将学到

  • Filter(过滤器)与 Interceptor(拦截器)的区别
  • HandlerInterceptor的三个生命周期方法
  • OncePerRequestFilter的使用
  • 执行顺序与使用场景

学完本篇,你将能为 mini-shop 添加请求日志、耗时统计、访问控制等横切功能。


一、Filter vs Interceptor

1.1 执行位置

Spring MVC层

Servlet容器层

客户端请求

Filter (doFilter)

DispatcherServlet

Interceptor (preHandle)

Controller

Interceptor (postHandle)

Interceptor (afterCompletion)

响应返回客户端

1.2 核心区别

维度FilterInterceptor
所属Servlet 容器(Tomcat)Spring MVC
执行时机DispatcherServlet 前后Controller 前后
能否访问 Controller不能能(知道要执行哪个 Controller 方法)
能否访问 Spring Bean需要手动获取可以直接注入
使用场景编码、CORS、日志、安全权限校验、日志、性能监控
注册方式FilterRegistrationBeanWebMvcConfigurer

二、HandlerInterceptor

2.1 三个生命周期方法

interfaceHandlerInterceptor{// Controller 执行前funpreHandle(request,response,handler):Boolean// Controller 执行后、视图渲染前funpostHandle(request,response,handler,modelAndView)// 请求完成后(无论成功失败)funafterCompletion(request,response,handler,ex)}
方法执行时机返回值典型用途
preHandleController 之前true:继续;false:中断权限校验、限流
postHandleController 之后void修改 Model、添加 Header
afterCompletion全部完成后void清理资源、记录日志

2.2 实战示例:请求日志 + 耗时统计

packagecom.example.minishop.interceptorimportjakarta.servlet.http.HttpServletRequestimportjakarta.servlet.http.HttpServletResponseimportorg.slf4j.LoggerFactoryimportorg.springframework.stereotype.Componentimportorg.springframework.web.servlet.HandlerInterceptor@ComponentclassRequestLoggingInterceptor:HandlerInterceptor{privatevallog=LoggerFactory.getLogger(RequestLoggingInterceptor::class.java)privatevalstartTimeAttribute="requestStartTime"overridefunpreHandle(request:HttpServletRequest,response:HttpServletResponse,handler:Any):Boolean{valstartTime=System.currentTimeMillis()request.setAttribute(startTimeAttribute,startTime)log.info("→${request.method}${request.requestURI}from${request.remoteAddr}")returntrue// 继续}overridefunafterCompletion(request:HttpServletRequest,response:HttpServletResponse,handler:Any,ex:Exception?){valstartTime=request.getAttribute(startTimeAttribute)asLongvalduration=System.currentTimeMillis()-startTimevalstatus=response.statusvalexceptionInfo=if(ex!=null)" exception=${ex.message}"else""log.info("←${request.method}${request.requestURI}$status${duration}ms$exceptionInfo")}}

2.3 注册拦截器

packagecom.example.minishop.configimportcom.example.minishop.interceptor.RequestLoggingInterceptorimportorg.springframework.context.annotation.Configurationimportorg.springframework.web.servlet.config.annotation.InterceptorRegistryimportorg.springframework.web.servlet.config.annotation.WebMvcConfigurer@ConfigurationclassWebConfig(privatevalrequestLoggingInterceptor:RequestLoggingInterceptor):WebMvcConfigurer{overridefunaddInterceptors(registry:InterceptorRegistry){registry.addInterceptor(requestLoggingInterceptor).addPathPatterns("/api/**") // 拦截 /api/ 下所有请求 .excludePathPatterns("/api/health") // 排除健康检查端点 } }

2.4 实战示例:简单权限校验

@ComponentclassAuthInterceptor(// 拦截器可以注入 Spring BeanprivatevalauthService:AuthService):HandlerInterceptor{overridefunpreHandle(request:HttpServletRequest,response:HttpServletResponse,handler:Any):Boolean{valtoken=request.getHeader("Authorization")if(token.isNullOrBlank()){response.contentType="application/json;charset=UTF-8"response.status=HttpServletResponse.SC_UNAUTHORIZED response.writer.write("""{"code":401,"message":"未提供认证 Token"}""")returnfalse// 中断请求}valisValid=authService.validateToken(token)if(!isValid){response.contentType="application/json;charset=UTF-8"response.status=HttpServletResponse.SC_UNAUTHORIZED response.writer.write("""{"code":401,"message":"Token 无效或已过期"}""")returnfalse}returntrue// 放行}}

注册时指定拦截路径:

overridefunaddInterceptors(registry:InterceptorRegistry){registry.addInterceptor(authInterceptor).addPathPatterns("/api/**") .excludePathPatterns( "/api/auth/login", // 登录接口不拦截 "/api/auth/register", // 注册接口不拦截 "/api/health", // 健康检查不拦截 "/api/products/**" // 商品浏览不拦截(公开接口) ) }

三、Filter

3.1 OncePerRequestFilter

Spring 提供了OncePerRequestFilter,确保每个请求只执行一次(避免请求转发时重复执行):

packagecom.example.minishop.filterimportjakarta.servlet.FilterChainimportjakarta.servlet.http.HttpServletRequestimportjakarta.servlet.http.HttpServletResponseimportorg.slf4j.LoggerFactoryimportorg.springframework.stereotype.Componentimportorg.springframework.web.filter.OncePerRequestFilter@ComponentclassRequestIdFilter:OncePerRequestFilter(){privatevallog=LoggerFactory.getLogger(RequestIdFilter::class.java)overridefundoFilterInternal(request:HttpServletRequest,response:HttpServletResponse,filterChain:FilterChain){// 1. 从请求头获取或生成请求 IDvalrequestId=request.getHeader("X-Request-Id")?:generateRequestId()// 2. 设置到响应头中(方便客户端排查)response.setHeader("X-Request-Id",requestId)// 3. 存入请求属性(供后续 Interceptor / Controller 使用)request.setAttribute("requestId",requestId)try{// 4. 继续过滤器链filterChain.doFilter(request,response)}finally{log.debug("请求处理完成:requestId=$requestId")}}privatefungenerateRequestId():String{returnjava.util.UUID.randomUUID().toString().take(8)}}

3.2 Filter 的注册

Spring Boot 会自动注册带有@Component的 Filter。如果需要自定义 URL 匹配:

@ConfigurationclassFilterConfig{@BeanfunrequestIdFilterRegistration(filter:RequestIdFilter):FilterRegistrationBean<RequestIdFilter>{valregistration=FilterRegistrationBean(filter)registration.addUrlPatterns("/api/*") // 只拦截 /api/ 路径 registration.order = 1 // 执行顺序(数字越小越先执行) return registration } }

四、执行顺序总结

响应处理

请求处理

请求

Filter (按order升序)

Interceptor.preHandle

Controller

Interceptor.postHandle

Interceptor.afterCompletion

Filter (逆序)

响应

多过滤器场景

请求

Filter A (order=1)

Filter B (order=2)

Interceptor.preHandle

Controller

Interceptor.postHandle

Interceptor.afterCompletion

Filter B (逆序)

Filter A (逆序)

响应


五、选择指南

需求选择原因
CORS 处理Filter在最外层处理,不进入 Spring MVC
字符编码设置Filter最早的时机设置编码
请求日志Interceptor能拿到 Controller 信息
耗时统计Interceptor精确到 Controller 执行时间
权限/角色校验Interceptor能获取 Handler 信息
请求 ID 追踪Filter最外层生成,全链路可用
限流Filter 或 Interceptor都可以,看是否需要 Spring 上下文

本篇小结

知识点核心内容
FilterServlet 层,DispatcherServlet 之前/之后
InterceptorSpring MVC 层,Controller 之前/之后
preHandle返回 false 中断请求
postHandleController 之后,可修改 Model
afterCompletion最终清理,无论成功失败
OncePerRequestFilter确保每个请求只过滤一次
WebMvcConfigurer注册拦截器 + 配置路径
执行顺序Filter → Interceptor → Controller

下篇预告

第 28 篇:CORS 与文件上传

前端跨域请求被拦截怎么办?商品图片怎么上传到服务器?下一篇解决这两个常见的 Web 开发问题。


如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。

← 返回列表