Appearance
Spring MVC 拦截器 (HandlerInterceptor) 深度解析 🚀
什么是拦截器?为什么需要它?
想象一下,你正在经营一家餐厅。每当客人进门时,你希望:
- 检查客人是否有预约(身份验证)
- 为客人提供菜单(准备工作)
- 在客人用餐后清理桌子(清理工作)
- 记录客人的用餐时间(日志记录)
在 Web 应用中,拦截器就像餐厅的服务员,它们在请求到达控制器之前、之后以及完全处理完成后执行特定的逻辑。
IMPORTANT
拦截器是 Spring MVC 提供的一种 AOP(面向切面编程)机制,允许我们在不修改业务代码的情况下,横向地为多个请求添加通用功能。
拦截器的核心原理
拦截器的三个关键方法 🔧
1. preHandle() - 前置处理
这是请求处理的"门卫",在控制器方法执行前调用。
kotlin
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
// 返回 true:继续执行后续流程
// 返回 false:中断执行,直接返回响应
return true
}
2. postHandle() - 后置处理
在控制器方法执行后,但在视图渲染前调用。
kotlin
override fun postHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
modelAndView: ModelAndView?
) {
// 可以修改 ModelAndView
// 添加通用的模型数据
}
3. afterCompletion() - 完成后处理
在整个请求处理完成后调用,包括视图渲染。
kotlin
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
// 清理资源、记录日志等
// 即使发生异常也会执行
}
实战案例:构建一个完整的拦截器
让我们创建一个实用的拦截器,它能够:
- 记录请求日志
- 验证用户权限
- 统计请求耗时
- 添加通用响应头
kotlin
@Component
class LoggingInterceptor : HandlerInterceptor {
private val logger = LoggerFactory.getLogger(LoggingInterceptor::class.java)
private val startTimeAttribute = "startTime"
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val startTime = System.currentTimeMillis()
request.setAttribute(startTimeAttribute, startTime)
// 记录请求开始日志
logger.info("请求开始 - URL: ${request.requestURL}, 方法: ${request.method}")
// 简单的权限检查示例
val userRole = request.getHeader("User-Role")
if (isProtectedResource(request) && userRole != "ADMIN") {
response.status = HttpServletResponse.SC_FORBIDDEN
response.writer.write("权限不足")
return false // 中断执行
}
return true
}
override fun postHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
modelAndView: ModelAndView?
) {
// 添加通用的模型数据
modelAndView?.addObject("serverTime", LocalDateTime.now())
// 添加通用响应头
response.setHeader("X-Powered-By", "Spring Boot Kotlin")
}
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
val startTime = request.getAttribute(startTimeAttribute) as Long
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
// 记录请求完成日志
logger.info("请求完成 - 耗时: ${duration}ms, 状态码: ${response.status}")
// 如果有异常,记录异常信息
ex?.let {
logger.error("请求处理异常", it)
}
}
private fun isProtectedResource(request: HttpServletRequest): Boolean {
return request.requestURI.startsWith("/admin/")
}
}
kotlin
@Configuration
class WebMvcConfig(
private val loggingInterceptor: LoggingInterceptor
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns( // 排除静态资源
"/static/**",
"/css/**",
"/js/**",
"/images/**"
)
}
}
多个拦截器的执行顺序 📋
当配置多个拦截器时,它们的执行顺序遵循特定规律:
kotlin
@Configuration
class WebMvcConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
// 执行顺序:SecurityInterceptor -> LoggingInterceptor -> PerformanceInterceptor
registry.addInterceptor(SecurityInterceptor()).order(1)
registry.addInterceptor(LoggingInterceptor()).order(2)
registry.addInterceptor(PerformanceInterceptor()).order(3)
}
}
实际业务场景应用 💼
场景1:API 限流拦截器
kotlin
@Component
class RateLimitInterceptor : HandlerInterceptor {
private val rateLimiter = RateLimiter.create(10.0) // 每秒10个请求
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
if (!rateLimiter.tryAcquire()) {
response.status = 429 // Too Many Requests
response.writer.write("请求过于频繁,请稍后再试")
return false
}
return true
}
}
场景2:用户会话验证拦截器
kotlin
@Component
class SessionValidationInterceptor : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val token = request.getHeader("Authorization")
if (token.isNullOrBlank()) {
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.writer.write("缺少认证令牌")
return false
}
// 验证 token 有效性
if (!isValidToken(token)) {
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.writer.write("无效的认证令牌")
return false
}
// 将用户信息存储到请求属性中
val userInfo = getUserInfoFromToken(token)
request.setAttribute("currentUser", userInfo)
return true
}
private fun isValidToken(token: String): Boolean {
// 实现 token 验证逻辑
return true
}
private fun getUserInfoFromToken(token: String): UserInfo {
// 从 token 中解析用户信息
return UserInfo("user123", "张三")
}
}
重要注意事项 ⚠️
1. ResponseBody 和 ResponseEntity 的特殊情况
WARNING
对于使用 @ResponseBody
或返回 ResponseEntity
的控制器方法,响应在 postHandle
调用之前就已经写入并提交了。这意味着在 postHandle
中无法修改响应内容。
如果需要修改这类响应,应该使用 ResponseBodyAdvice
:
kotlin
@ControllerAdvice
class CustomResponseBodyAdvice : ResponseBodyAdvice<Any> {
override fun supports(
returnType: MethodParameter,
converterType: Class<out HttpMessageConverter<*>>
): Boolean = true
override fun beforeBodyWrite(
body: Any?,
returnType: MethodParameter,
selectedContentType: MediaType,
selectedConverterType: Class<out HttpMessageConverter<*>>,
request: ServerHttpRequest,
response: ServerHttpResponse
): Any? {
// 在响应体写入前修改响应
response.headers.add("X-Custom-Header", "Custom Value")
return body
}
}
2. 安全性考虑
CAUTION
拦截器不适合作为安全层,因为它们可能与注解控制器的路径匹配存在不一致。建议使用 Spring Security 或在 Servlet 过滤器链中尽早应用安全措施。
拦截器 vs 过滤器 vs AOP 🤔
特性 | 拦截器 (Interceptor) | 过滤器 (Filter) | AOP |
---|---|---|---|
执行时机 | Spring MVC 处理过程中 | Servlet 容器级别 | 方法调用前后 |
访问 Spring 上下文 | ✅ | ❌ | ✅ |
访问 Handler 信息 | ✅ | ❌ | ✅ |
粒度 | 请求级别 | 请求级别 | 方法级别 |
配置方式 | Spring 配置 | web.xml 或注解 | Spring 配置 |
最佳实践建议 💡
性能优化建议
- 避免在拦截器中执行耗时操作:拦截器会影响所有匹配的请求
- 合理使用路径匹配:精确配置拦截路径,避免不必要的拦截
- 异常处理:在
afterCompletion
中进行资源清理,即使发生异常也能执行
设计原则
- 单一职责:每个拦截器只负责一个特定功能
- 链式设计:多个拦截器可以组合使用
- 可配置性:通过配置控制拦截器的启用和路径匹配
总结 📝
Spring MVC 拦截器是一个强大的工具,它让我们能够:
- ✅ 在不修改业务代码的情况下添加横切关注点
- ✅ 实现统一的日志记录、权限验证、性能监控
- ✅ 提供灵活的配置和组合方式
- ✅ 与 Spring 生态系统完美集成
通过合理使用拦截器,我们可以构建更加模块化、可维护的 Web 应用程序。记住,拦截器是 Spring MVC 架构中的重要组成部分,掌握它将让你的 Spring Boot 应用更加强大和优雅! 🎉