Skip to content

Spring Web MVC 过滤器详解 ⚙️

概述

在 Spring Web MVC 应用中,过滤器(Filters)扮演着"守门员"的角色,它们在请求到达控制器之前和响应返回客户端之前进行各种预处理和后处理工作。想象一下,如果把 Web 应用比作一个餐厅,那么过滤器就像是门口的服务员,负责接待客人、检查预约、引导入座等工作。

NOTE

Spring Web 模块提供了多个实用的过滤器,帮助开发者解决常见的 Web 开发问题,如表单数据处理、代理头信息处理、缓存优化等。

核心过滤器详解

1. FormContentFilter - 表单数据处理器 📝

解决的痛点

在标准的 HTML 表单中,浏览器只支持 GET 和 POST 方法。但在 RESTful API 设计中,我们经常需要使用 PUT、PATCH、DELETE 等 HTTP 方法。这就产生了一个问题:如何在这些非标准方法中处理表单数据?

工作原理

FormContentFilter 专门拦截 application/x-www-form-urlencoded 类型的 PUT、PATCH、DELETE 请求,将请求体中的表单数据解析出来,让开发者可以通过标准的 ServletRequest.getParameter*() 方法访问这些数据。

kotlin
@RestController
class UserController {
    
    @PutMapping("/users/{id}")
    fun updateUser(
        @PathVariable id: Long,
        request: HttpServletRequest
    ): ResponseEntity<String> {
        // 通过 FormContentFilter,我们可以直接获取表单参数
        val name = request.getParameter("name")  
        val email = request.getParameter("email")  
        
        // 执行用户更新逻辑
        userService.updateUser(id, name, email)
        
        return ResponseEntity.ok("用户更新成功")
    }
}
kotlin
@Configuration
class FilterConfig {
    
    @Bean
    fun formContentFilter(): FormContentFilter {
        return FormContentFilter()  
    }
}
kotlin
@PutMapping("/users/{id}")
fun updateUserWithoutFilter(
    @PathVariable id: Long,
    request: HttpServletRequest
): ResponseEntity<String> {
    // 没有 FormContentFilter,需要手动解析请求体
    val inputStream = request.inputStream  
    val formData = parseFormData(inputStream)  
    // 大量繁琐的解析代码...
    
    return ResponseEntity.ok("更新成功")
}

2. ForwardedHeaderFilter - 代理头信息处理器 🌐

解决的核心问题

在现代 Web 架构中,请求通常会经过多层代理(负载均衡器、反向代理等)才到达应用服务器。这带来一个严重问题:应用服务器看到的请求信息(主机名、端口、协议)往往不是客户端的真实信息

支持的头信息类型

IMPORTANT

ForwardedHeaderFilter 支持标准的 Forwarded 头和多种非标准但广泛使用的 X-Forwarded-* 头信息。

头信息作用示例
X-Forwarded-Host原始主机名api.example.com
X-Forwarded-Port原始端口443
X-Forwarded-Proto原始协议https
X-Forwarded-For客户端真实IP203.0.113.1
X-Forwarded-PrefixURL路径前缀/api

实际应用场景

kotlin
@RestController
class ApiController {
    
    @GetMapping("/info")
    fun getRequestInfo(request: HttpServletRequest): Map<String, Any> {
        return mapOf(
            "scheme" to request.scheme,        // 通过过滤器修正后的协议
            "serverName" to request.serverName, // 通过过滤器修正后的主机名
            "serverPort" to request.serverPort, // 通过过滤器修正后的端口
            "requestURL" to request.requestURL.toString()
        )
    }
    
    @GetMapping("/generate-link")
    fun generateSelfLink(request: HttpServletRequest): String {
        // ForwardedHeaderFilter 确保生成的链接使用正确的协议和主机
        val baseUrl = "${request.scheme}://${request.serverName}"
        return "$baseUrl/api/resource/123"
    }
}

安全配置

WARNING

代理头信息可能被恶意客户端伪造,因此必须在可信边界处理这些头信息。

kotlin
@Configuration
class SecurityFilterConfig {
    
    @Bean
    fun forwardedHeaderFilter(): ForwardedHeaderFilter {
        val filter = ForwardedHeaderFilter()
        // 在生产环境中,建议只移除而不使用不可信的头信息
        filter.setRemoveOnly(true)  
        return filter
    }
}

3. ShallowEtagHeaderFilter - 浅层 ETag 缓存 ⚡

缓存优化的价值

想象你经营一个新闻网站,同一篇文章可能被成千上万的用户访问。如果每次都重新生成完整的 HTML 响应,服务器压力会很大,用户体验也不好。ETag 机制就像给每个响应贴上一个"指纹",如果内容没有变化,就告诉浏览器:"用你缓存的版本就行了!"

工作机制

代码实现

kotlin
@RestController
class ArticleController {
    
    @GetMapping("/articles/{id}")
    fun getArticle(@PathVariable id: Long): Article {
        // ShallowEtagHeaderFilter 会自动处理 ETag 逻辑
        return articleService.findById(id)  
    }
}

@Configuration
class CacheConfig {
    
    @Bean
    fun shallowEtagHeaderFilter(): ShallowEtagHeaderFilter {
        val filter = ShallowEtagHeaderFilter()
        // 生成弱 ETag(推荐)
        filter.setWriteWeakETag(true)  
        return filter
    }
}

TIP

ShallowEtagHeaderFilter 虽然节省了网络带宽,但仍需要执行完整的业务逻辑。对于更高效的缓存策略,建议在控制器层面实现条件请求处理。

4. CorsFilter - 跨域资源共享 🌍

跨域问题的本质

现代 Web 应用经常需要从不同域名获取资源。比如前端应用部署在 https://app.example.com,而 API 服务部署在 https://api.example.com。浏览器的同源策略会阻止这种跨域请求,这就需要 CORS 机制来解决。

kotlin
@Configuration
@EnableWebMvc
class CorsConfig : WebMvcConfigurer {
    
    override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://app.example.com")  
            .allowedMethods("GET", "POST", "PUT", "DELETE")  
            .allowedHeaders("*")
            .allowCredentials(true)
    }
}

IMPORTANT

当与 Spring Security 一起使用时,建议使用内置的 CorsFilter,并确保它在 Security 过滤器链之前执行。

5. UrlHandlerFilter - URL 处理器 🔗

解决尾部斜杠问题

在 Web 开发中,URL 的尾部斜杠经常引起困扰。用户可能访问 /blog/my-post//blog/my-post,但应用只配置了其中一种映射。UrlHandlerFilter 提供了灵活的解决方案。

kotlin
@Configuration
class UrlConfig {
    
    @Bean
    fun urlHandlerFilter(): UrlHandlerFilter {
        return UrlHandlerFilter
            // 博客路径:重定向到无尾斜杠版本
            .trailingSlashHandler("/blog/**")  
            .redirect(HttpStatus.PERMANENT_REDIRECT)  
            
            // 管理路径:内部处理,不重定向
            .trailingSlashHandler("/admin/**")  
            .wrapRequest()  
            .build()
    }
}

不同处理策略对比

kotlin
// 用户访问: /blog/spring-tutorial/
// 服务器响应: 308 重定向到 /blog/spring-tutorial
.trailingSlashHandler("/blog/**")
.redirect(HttpStatus.PERMANENT_REDIRECT)
kotlin
// 用户访问: /admin/users/
// 内部处理为: /admin/users(用户无感知)
.trailingSlashHandler("/admin/**")
.wrapRequest()

过滤器配置最佳实践 🎯

Spring Boot 中的配置

kotlin
@Configuration
class FilterConfiguration {
    
    @Bean
    @Order(1)  // 确保在其他过滤器之前执行
    fun forwardedHeaderFilter(): FilterRegistrationBean<ForwardedHeaderFilter> {
        val registration = FilterRegistrationBean<ForwardedHeaderFilter>()
        registration.filter = ForwardedHeaderFilter()
        registration.addUrlPatterns("/*")
        
        // 支持异步请求和错误分发
        registration.setDispatcherTypes(  
            DispatcherType.REQUEST,
            DispatcherType.ASYNC,
            DispatcherType.ERROR
        )
        
        return registration
    }
    
    @Bean
    fun formContentFilter(): FilterRegistrationBean<FormContentFilter> {
        val registration = FilterRegistrationBean<FormContentFilter>()
        registration.filter = FormContentFilter()
        registration.addUrlPatterns("/api/*")
        return registration
    }
}

执行顺序的重要性

WARNING

过滤器的执行顺序至关重要。ForwardedHeaderFilter 必须在其他依赖请求信息的过滤器之前执行。

kotlin
// 推荐的过滤器顺序
// 1. ForwardedHeaderFilter (修正请求信息)
// 2. FormContentFilter (处理表单数据)  
// 3. ShallowEtagHeaderFilter (缓存处理)
// 4. 自定义业务过滤器
// 5. Spring Security 过滤器

实际应用场景示例 🚀

微服务架构中的网关配置

kotlin
@RestController
class GatewayController {
    
    @GetMapping("/health")
    fun healthCheck(request: HttpServletRequest): Map<String, Any> {
        return mapOf(
            "status" to "UP",
            "timestamp" to System.currentTimeMillis(),
            // ForwardedHeaderFilter 确保获取到正确的客户端信息
            "clientInfo" to mapOf(
                "ip" to getClientIp(request),  
                "host" to request.serverName,
                "protocol" to request.scheme
            )
        )
    }
    
    private fun getClientIp(request: HttpServletRequest): String {
        // ForwardedHeaderFilter 已经处理了 X-Forwarded-For 头
        return request.remoteAddr  
    }
}

RESTful API 的完整配置

完整的过滤器配置示例
kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    
    // CORS 配置
    override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/api/**")
            .allowedOriginPatterns("https://*.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600)
    }
    
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    fun forwardedHeaderFilter(): FilterRegistrationBean<ForwardedHeaderFilter> {
        val registration = FilterRegistrationBean<ForwardedHeaderFilter>()
        val filter = ForwardedHeaderFilter()
        
        // 生产环境安全配置
        if (isProduction()) {
            filter.setRemoveOnly(true)
        }
        
        registration.filter = filter
        registration.addUrlPatterns("/*")
        registration.setDispatcherTypes(
            DispatcherType.REQUEST,
            DispatcherType.ASYNC,
            DispatcherType.ERROR
        )
        
        return registration
    }
    
    @Bean
    fun formContentFilter(): FilterRegistrationBean<FormContentFilter> {
        val registration = FilterRegistrationBean<FormContentFilter>()
        registration.filter = FormContentFilter()
        registration.addUrlPatterns("/api/*")
        return registration
    }
    
    @Bean
    fun shallowEtagHeaderFilter(): FilterRegistrationBean<ShallowEtagHeaderFilter> {
        val registration = FilterRegistrationBean<ShallowEtagHeaderFilter>()
        val filter = ShallowEtagHeaderFilter()
        filter.setWriteWeakETag(true)
        
        registration.filter = filter
        registration.addUrlPatterns("/api/articles/*", "/api/products/*")
        registration.setDispatcherTypes(
            DispatcherType.REQUEST,
            DispatcherType.ASYNC
        )
        
        return registration
    }
    
    @Bean
    fun urlHandlerFilter(): FilterRegistrationBean<UrlHandlerFilter> {
        val registration = FilterRegistrationBean<UrlHandlerFilter>()
        
        val filter = UrlHandlerFilter
            .trailingSlashHandler("/api/**")
            .redirect(HttpStatus.PERMANENT_REDIRECT)
            .trailingSlashHandler("/admin/**")
            .wrapRequest()
            .build()
            
        registration.filter = filter
        registration.addUrlPatterns("/*")
        
        return registration
    }
    
    private fun isProduction(): Boolean {
        return System.getProperty("spring.profiles.active")?.contains("prod") == true
    }
}

总结 📋

Spring Web MVC 的过滤器系统为我们提供了强大而灵活的请求处理能力:

  • FormContentFilter 解决了 RESTful API 中非标准 HTTP 方法的表单数据处理问题
  • ForwardedHeaderFilter 确保在代理环境中获取正确的客户端信息
  • ShallowEtagHeaderFilter 提供了简单有效的 HTTP 缓存机制
  • CorsFilter 解决了现代 Web 应用的跨域访问问题
  • UrlHandlerFilter 优雅地处理了 URL 路径的一致性问题

TIP

合理配置和使用这些过滤器,可以显著提升应用的性能、安全性和用户体验。记住,过滤器的执行顺序很重要,ForwardedHeaderFilter 应该最先执行,以确保其他过滤器能获取到正确的请求信息。

通过理解这些过滤器的工作原理和应用场景,你就能构建出更加健壮和高效的 Spring Web 应用! 🎉