Skip to content

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 应用程序。记住,拦截器是你的"守门员",让它们帮你处理那些重复性的工作,而你则专注于核心业务逻辑的实现! 🚀