Appearance
Spring MVC 拦截器(Interceptors):请求处理的守门员 🛡️
什么是拦截器?为什么需要它?
想象一下,你在一家高档餐厅工作,每当客人进入餐厅时,都需要有人在门口检查预订信息、引导就座、记录用餐时间等。在 Spring MVC 中,拦截器(Interceptor) 就扮演着这样的"门卫"角色。
NOTE
拦截器是 Spring MVC 提供的一种机制,允许我们在请求到达控制器之前、控制器处理完成后、以及视图渲染完成后执行自定义逻辑。
🤔 没有拦截器会遇到什么问题?
在没有拦截器的世界里,我们可能需要在每个控制器方法中重复编写相同的代码:
kotlin
@RestController
class UserController {
@GetMapping("/users")
fun getUsers(): List<User> {
// 每个方法都要重复这些代码
logRequest("GET /users")
checkAuthentication()
validatePermission()
recordMetrics()
return userService.getAllUsers()
}
@PostMapping("/users")
fun createUser(@RequestBody user: User): User {
// 又要重复一遍...
logRequest("POST /users")
checkAuthentication()
validatePermission()
recordMetrics()
return userService.createUser(user)
}
}
kotlin
@RestController
class UserController {
@GetMapping("/users")
fun getUsers(): List<User> {
// 拦截器自动处理了日志、认证、权限检查等
return userService.getAllUsers()
}
@PostMapping("/users")
fun createUser(@RequestBody user: User): User {
// 业务逻辑更加纯粹和专注
return userService.createUser(user)
}
}
拦截器的工作原理 🔄
拦截器基于 责任链模式,在请求处理的不同阶段提供钩子方法:
如何配置拦截器?
基础配置方式
kotlin
@Configuration
class WebConfiguration : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
// 添加国际化拦截器
registry.addInterceptor(LocaleChangeInterceptor())
// 添加主题切换拦截器,并配置路径规则
registry.addInterceptor(ThemeChangeInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/admin/**") // 排除管理员路径
}
}
java
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
}
}
xml
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
创建自定义拦截器 🛠️
让我们创建一个实用的请求日志拦截器:
kotlin
@Component
class RequestLoggingInterceptor : HandlerInterceptor {
private val logger = LoggerFactory.getLogger(RequestLoggingInterceptor::class.java)
/**
* 请求处理前调用
* @return true 继续处理,false 中断请求
*/
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val startTime = System.currentTimeMillis()
request.setAttribute("startTime", startTime)
logger.info(
"请求开始 - 方法: {}, URI: {}, 参数: {}",
request.method,
request.requestURI,
request.queryString ?: "无"
)
// 可以在这里进行权限检查
if (!checkPermission(request)) {
response.status = HttpStatus.FORBIDDEN.value()
return false
}
return true
}
/**
* 控制器方法执行后,视图渲染前调用
*/
override fun postHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
modelAndView: ModelAndView?
) {
// 可以修改模型数据或视图
modelAndView?.addObject("requestTime", Date())
logger.info("控制器处理完成,准备渲染视图")
}
/**
* 整个请求完成后调用(包括视图渲染)
*/
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
val startTime = request.getAttribute("startTime") as Long
val endTime = System.currentTimeMillis()
val processingTime = endTime - startTime
logger.info(
"请求完成 - 状态码: {}, 处理时间: {}ms, 异常: {}",
response.status,
processingTime,
ex?.message ?: "无"
)
// 清理资源
request.removeAttribute("startTime")
}
private fun checkPermission(request: HttpServletRequest): Boolean {
// 简单的权限检查示例
val token = request.getHeader("Authorization")
return !token.isNullOrBlank()
}
}
注册自定义拦截器
kotlin
@Configuration
class WebConfiguration(
private val requestLoggingInterceptor: RequestLoggingInterceptor
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
// 注册自定义拦截器
registry.addInterceptor(requestLoggingInterceptor)
.addPathPatterns("/api/**") // 只拦截 API 路径
.excludePathPatterns(
"/api/health", // 排除健康检查
"/api/public/**" // 排除公开接口
)
.order(1) // 设置执行顺序,数字越小优先级越高
}
}
实际业务场景应用 🎯
场景1:API 限流拦截器
kotlin
@Component
class RateLimitInterceptor : HandlerInterceptor {
private val rateLimiter = ConcurrentHashMap<String, RateLimiter>()
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val clientIp = getClientIp(request)
val limiter = rateLimiter.computeIfAbsent(clientIp) {
RateLimiter.create(10.0) // 每秒最多10个请求
}
if (!limiter.tryAcquire()) {
response.status = HttpStatus.TOO_MANY_REQUESTS.value()
response.writer.write("""{"error": "请求过于频繁,请稍后再试"}""")
return false
}
return true
}
private fun getClientIp(request: HttpServletRequest): String {
return request.getHeader("X-Forwarded-For")
?: request.getHeader("X-Real-IP")
?: request.remoteAddr
}
}
场景2:用户认证拦截器
kotlin
@Component
class AuthenticationInterceptor(
private val jwtService: JwtService,
private val userService: UserService
) : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
// 检查是否需要认证
if (handler is HandlerMethod &&
handler.hasMethodAnnotation(PublicApi::class.java)) {
return true // 公开接口,无需认证
}
val token = extractToken(request)
if (token == null) {
sendUnauthorizedResponse(response, "缺少认证令牌")
return false
}
try {
val userId = jwtService.validateToken(token)
val user = userService.findById(userId)
// 将用户信息存储到请求属性中,供控制器使用
request.setAttribute("currentUser", user)
return true
} catch (e: Exception) {
sendUnauthorizedResponse(response, "无效的认证令牌")
return false
}
}
private fun extractToken(request: HttpServletRequest): String? {
val authHeader = request.getHeader("Authorization")
return if (authHeader?.startsWith("Bearer ") == true) {
authHeader.substring(7)
} else null
}
private fun sendUnauthorizedResponse(response: HttpServletResponse, message: String) {
response.status = HttpStatus.UNAUTHORIZED.value()
response.contentType = "application/json;charset=UTF-8"
response.writer.write("""{"error": "$message"}""")
}
}
拦截器 vs 过滤器 🆚
特性 | 拦截器 (Interceptor) | 过滤器 (Filter) |
---|---|---|
作用范围 | Spring MVC 框架内 | Servlet 容器级别 |
执行时机 | 控制器前后 | 请求/响应的最外层 |
访问Spring容器 | ✅ 可以注入Bean | ❌ 需要手动获取 |
异常处理 | 可被Spring统一处理 | 需要自行处理 |
配置方式 | Spring配置 | web.xml或注解 |
最佳实践与注意事项 ⚠️
IMPORTANT
拦截器不适合作为安全层使用,因为可能与注解控制器路径匹配存在不匹配的情况。通常建议使用 Spring Security 或类似的 Servlet 过滤器链方案。
TIP
拦截器执行顺序:
- 多个拦截器按注册顺序执行
preHandle()
- 按相反顺序执行
postHandle()
和afterCompletion()
WARNING
性能考虑:
- 避免在拦截器中执行耗时操作
- 合理使用路径匹配规则,避免不必要的拦截
- 及时清理 ThreadLocal 变量,防止内存泄漏
拦截器链执行示例
kotlin
// 假设注册了三个拦截器:A、B、C
// 正常执行顺序:
// A.preHandle() -> B.preHandle() -> C.preHandle()
// -> Controller
// -> C.postHandle() -> B.postHandle() -> A.postHandle()
// -> C.afterCompletion() -> B.afterCompletion() -> A.afterCompletion()
@Configuration
class InterceptorConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(LoggingInterceptor()).order(1) // 最先执行
registry.addInterceptor(AuthInterceptor()).order(2) // 其次执行
registry.addInterceptor(RateLimitInterceptor()).order(3) // 最后执行
}
}
总结 📝
Spring MVC 拦截器是一个强大的工具,它让我们能够:
✅ 统一处理横切关注点:日志、认证、权限检查等
✅ 保持控制器简洁:专注于业务逻辑
✅ 灵活的路径匹配:精确控制拦截范围
✅ 完整的生命周期钩子:请求处理的各个阶段都可介入
通过合理使用拦截器,我们可以构建更加优雅、可维护的 Web 应用程序。记住,拦截器是你的"守门员",让它们帮你处理那些重复性的工作,而你则专注于核心业务逻辑的实现! 🚀