Appearance
WebMvcConfigurer
WebMvcConfigurer 是 Spring MVC 提供的一个配置接口,用于自定义 Spring MVC 的配置。它定义了回调方法来自定义通过 @EnableWebMvc
启用的基于 Java 的 Spring MVC 配置。
NOTE
WebMvcConfigurer 是一个接口,所有方法都提供了默认实现(空实现),因此你只需要重写需要自定义的方法即可。
概述
WebMvcConfigurer 接口允许开发者在 Spring Boot 应用中自定义 Web MVC 配置,而无需完全接管 Spring MVC 的自动配置。
1. 路径匹配配置 - configurePathMatch
配置 HandlerMapping 路径匹配选项,如是否使用解析的 PathPatterns 或字符串模式匹配。
kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer
.setUseTrailingSlashMatch(false) // 不匹配尾部斜杠
.setUseSuffixPatternMatch(false) // 不使用后缀模式匹配
.addPathPrefix("/api") { controller ->
controller.packageName.startsWith("com.example.api")
}
}
}
TIP
路径匹配配置主要用于统一 API 路径规范,比如为所有 API 添加统一前缀。
2. 拦截器配置 - addInterceptors
添加 Spring MVC 生命周期拦截器,用于控制器方法调用和资源处理器请求的前后处理。
kotlin
@Component
class AuthInterceptor : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val token = request.getHeader("Authorization")
if (token.isNullOrBlank()) {
response.status = HttpStatus.UNAUTHORIZED.value()
return false
}
return true
}
}
@Configuration
class WebConfig : WebMvcConfigurer {
@Autowired
private lateinit var authInterceptor: AuthInterceptor
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 拦截所有 /api/ 开头的请求
.excludePathPatterns("/api/login", "/api/register") // 排除登录和注册
}
}
IMPORTANT
拦截器的执行顺序很重要,先注册的拦截器先执行。可以通过 order()
方法设置执行顺序。
3. 静态资源处理 - addResourceHandlers
配置静态资源处理器,用于提供图片、JavaScript、CSS 等静态文件服务。
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
// 配置文件上传目录
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:./uploads/")
.setCachePeriod(3600) // 缓存1小时
// 配置静态资源
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(86400) // 缓存24小时
.resourceChain(true)
.addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
}
}
配置静态资源处理器
实际业务场景在电商网站中,商品图片通常存储在服务器的特定目录下,通过配置静态资源处理器可以直接通过 URL 访问这些图片,无需编写专门的文件下载接口。
4. CORS 跨域配置 - addCorsMappings
配置全局跨域请求处理,支持前后端分离架构。
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600) // 预检请求缓存时间
}
}
WARNING
在生产环境中,不要使用 allowedOrigins("*")
和 allowCredentials(true)
的组合,这会带来安全风险。
5. 视图控制器 - addViewControllers
配置简单的自动化控制器,无需编写控制器逻辑。
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addViewControllers(registry: ViewControllerRegistry) {
// 首页重定向
registry.addViewController("/").setViewName("redirect:/index")
// 直接返回视图
registry.addViewController("/about").setViewName("about")
// 返回状态码
registry.addViewController("/404").setStatusCode(HttpStatus.NOT_FOUND)
// 重定向
registry.addRedirectViewController("/old-page", "/new-page")
}
}
6. 消息转换器配置
configureMessageConverters - 配置消息转换器
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
// 添加自定义 JSON 转换器
val jackson2Converter = MappingJackson2HttpMessageConverter()
val objectMapper = ObjectMapper()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
jackson2Converter.objectMapper = objectMapper
converters.add(jackson2Converter)
}
}
extendMessageConverters - 扩展消息转换器
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
// 自定义 String 转换器,支持更多字符编码
val stringConverter = StringHttpMessageConverter(StandardCharsets.UTF_8)
stringConverter.setWriteAcceptCharset(false)
// 插入到列表开头,优先级更高
converters.add(0, stringConverter)
}
}
TIP
configureMessageConverters
会覆盖默认转换器,而 extendMessageConverters
是在默认转换器基础上进行扩展,推荐使用后者。
7. 异常解析器配置
configureHandlerExceptionResolvers - 配置异常解析器
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun configureHandlerExceptionResolvers(resolvers: MutableList<HandlerExceptionResolver>) {
resolvers.add(CustomExceptionResolver())
}
}
class CustomExceptionResolver : HandlerExceptionResolver {
override fun resolveException(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any?,
ex: Exception
): ModelAndView? {
when (ex) {
is IllegalArgumentException -> {
response.status = HttpStatus.BAD_REQUEST.value()
response.writer.write("""{"error": "参数错误", "message": "${ex.message}"}""")
response.contentType = "application/json;charset=UTF-8"
return ModelAndView()
}
else -> return null
}
}
}
extendHandlerExceptionResolvers - 扩展异常解析器
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun extendHandlerExceptionResolvers(resolvers: MutableList<HandlerExceptionResolver>) {
// 添加自定义异常解析器到默认解析器列表中
resolvers.add(0, GlobalExceptionResolver())
}
}
8. 参数解析器和返回值处理器
addArgumentResolvers - 添加参数解析器
kotlin
// 自定义参数解析器,用于解析当前用户信息
class CurrentUserArgumentResolver : HandlerMethodArgumentResolver {
override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.hasParameterAnnotation(CurrentUser::class.java)
}
override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?
): Any? {
val token = webRequest.getHeader("Authorization")
// 根据 token 解析用户信息
return getUserFromToken(token)
}
private fun getUserFromToken(token: String?): User? {
// 实际业务逻辑
return User(id = 1, name = "张三")
}
}
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
resolvers.add(CurrentUserArgumentResolver())
}
}
// 使用示例
@RestController
class UserController {
@GetMapping("/profile")
fun getProfile(@CurrentUser user: User): ResponseEntity<User> {
return ResponseEntity.ok(user)
}
}
addReturnValueHandlers - 添加返回值处理器
kotlin
class ApiResponseReturnValueHandler : HandlerMethodReturnValueHandler {
override fun supportsReturnType(returnType: MethodParameter): Boolean {
return returnType.hasMethodAnnotation(ApiResponse::class.java)
}
override fun handleReturnValue(
returnValue: Any?,
returnType: MethodParameter,
mavContainer: ModelAndViewContainer,
webRequest: NativeWebRequest
) {
mavContainer.isRequestHandled = true
val response = webRequest.getNativeResponse(HttpServletResponse::class.java)
val result = mapOf(
"code" to 200,
"message" to "success",
"data" to returnValue
)
response?.apply {
contentType = "application/json;charset=UTF-8"
writer.write(ObjectMapper().writeValueAsString(result))
}
}
}
9. 格式化器配置 - addFormatters
添加自定义的转换器和格式化器,用于类型转换。
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
// 日期格式化器
registry.addFormatter(object : Formatter<LocalDateTime> {
override fun print(obj: LocalDateTime, locale: Locale): String {
return obj.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}
override fun parse(text: String, locale: Locale): LocalDateTime {
return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}
})
// 字符串到枚举的转换器
registry.addConverter(object : Converter<String, UserStatus> {
override fun convert(source: String): UserStatus {
return UserStatus.valueOf(source.uppercase())
}
})
}
}
10. 异步支持配置 - configureAsyncSupport
配置异步请求处理选项。
kotlin
@Configuration
class WebConfig : WebMvcConfigurer {
override fun configureAsyncSupport(configurer: AsyncSupportConfigurer) {
configurer
.setDefaultTimeout(30000) // 30秒超时
.setTaskExecutor(asyncTaskExecutor()) // 自定义线程池
.registerCallableInterceptors(AsyncRequestInterceptor()) // 注册拦截器
}
@Bean
fun asyncTaskExecutor(): TaskExecutor {
val executor = ThreadPoolTaskExecutor()
executor.corePoolSize = 10
executor.maxPoolSize = 50
executor.queueCapacity = 100
executor.threadNamePrefix = "async-"
executor.initialize()
return executor
}
}
class AsyncRequestInterceptor : CallableProcessingInterceptor {
override fun beforeConcurrentHandling(
request: NativeWebRequest,
task: Callable<*>
) {
// 异步处理前的逻辑
}
override fun afterCompletion(
request: NativeWebRequest,
task: Callable<*>
) {
// 异步处理完成后的逻辑
}
}
完整配置示例
以下是一个完整的 WebMvcConfigurer 配置示例,展示了在实际项目中的综合应用:
kotlin
@Configuration
@EnableWebMvc
class WebMvcConfig : WebMvcConfigurer {
@Autowired
private lateinit var authInterceptor: AuthInterceptor
@Autowired
private lateinit var logInterceptor: LogInterceptor
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer
.setUseTrailingSlashMatch(false)
.addPathPrefix("/api/v1") { controller ->
controller.packageName.startsWith("com.example.api.v1")
}
}
override fun addInterceptors(registry: InterceptorRegistry) {
// 日志拦截器
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**")
.order(1)
// 认证拦截器
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**")
.order(2)
}
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
// 上传文件访问
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:./uploads/")
.setCachePeriod(3600)
// 静态资源
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(86400)
}
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600)
}
override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/").setViewName("redirect:/index.html")
registry.addViewController("/admin").setViewName("admin/index")
}
override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
// 自定义 JSON 转换器
val jackson2Converter = MappingJackson2HttpMessageConverter()
val objectMapper = ObjectMapper().apply {
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
registerModule(JavaTimeModule())
}
jackson2Converter.objectMapper = objectMapper
converters.add(0, jackson2Converter)
}
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
resolvers.add(CurrentUserArgumentResolver())
resolvers.add(PageableArgumentResolver())
}
override fun extendHandlerExceptionResolvers(resolvers: MutableList<HandlerExceptionResolver>) {
resolvers.add(0, GlobalExceptionResolver())
}
override fun configureAsyncSupport(configurer: AsyncSupportConfigurer) {
configurer
.setDefaultTimeout(30000)
.setTaskExecutor(asyncTaskExecutor())
}
@Bean
fun asyncTaskExecutor(): TaskExecutor {
val executor = ThreadPoolTaskExecutor()
executor.corePoolSize = 10
executor.maxPoolSize = 50
executor.queueCapacity = 100
executor.threadNamePrefix = "mvc-async-"
executor.initialize()
return executor
}
}
kotlin
@Component
class AuthInterceptor : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val token = request.getHeader("Authorization")
if (token.isNullOrBlank() || !token.startsWith("Bearer ")) {
response.status = HttpStatus.UNAUTHORIZED.value()
response.contentType = "application/json;charset=UTF-8"
response.writer.write("""{"error": "未授权访问"}""")
return false
}
// 验证 token 逻辑
val user = validateToken(token.substring(7))
if (user == null) {
response.status = HttpStatus.UNAUTHORIZED.value()
response.contentType = "application/json;charset=UTF-8"
response.writer.write("""{"error": "Token 无效"}""")
return false
}
// 将用户信息存储到请求中
request.setAttribute("currentUser", user)
return true
}
private fun validateToken(token: String): User? {
// 实际的 token 验证逻辑
return if (token == "valid-token") {
User(id = 1, name = "张三")
} else null
}
}
@Component
class LogInterceptor : HandlerInterceptor {
private val logger = LoggerFactory.getLogger(LogInterceptor::class.java)
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val startTime = System.currentTimeMillis()
request.setAttribute("startTime", startTime)
logger.info("请求开始: {} {}", request.method, request.requestURI)
return true
}
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
val startTime = request.getAttribute("startTime") as Long
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
logger.info(
"请求完成: {} {} - 状态码: {} - 耗时: {}ms",
request.method,
request.requestURI,
response.status,
duration
)
if (ex != null) {
logger.error("请求异常: ", ex)
}
}
}
最佳实践
1. 配置优先级
IMPORTANT
理解不同配置方法的优先级关系:
configure*
方法会替换默认配置extend*
方法会在默认配置基础上扩展add*
方法会添加到现有配置中
2. 性能考虑
kotlin
@Configuration
class PerformanceWebConfig : WebMvcConfigurer {
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(86400) // 设置缓存时间
.resourceChain(true) // 启用资源链
.addResolver(VersionResourceResolver().addContentVersionStrategy("/**")) // 版本控制
}
override fun configureAsyncSupport(configurer: AsyncSupportConfigurer) {
configurer.setDefaultTimeout(30000) // 合理设置超时时间
}
}
3. 安全配置
kotlin
@Configuration
class SecurityWebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("https://trusted-domain.com") // 明确指定域名
.allowedMethods("GET", "POST", "PUT", "DELETE") // 限制 HTTP 方法
.allowCredentials(true)
.maxAge(3600)
}
}
CAUTION
在生产环境中,务必:
- 明确指定 CORS 允许的域名,避免使用
*
- 限制允许的 HTTP 方法
- 设置合理的缓存时间和超时时间
- 对敏感接口进行适当的访问控制
总结
WebMvcConfigurer 是 Spring MVC 中非常重要的配置接口,它提供了灵活而强大的方式来自定义 Web 应用的行为。通过合理使用各种配置方法,可以构建出高性能、安全、易维护的 Web 应用。
TIP
记住这些要点
- 选择合适的方法:根据需求选择
configure*
、extend*
或add*
方法 - 注意执行顺序:拦截器、转换器的顺序很重要
- 性能优化:合理设置缓存、超时时间
- 安全考虑:谨慎配置 CORS、静态资源访问
- 代码复用:将通用配置抽取为独立的配置类
通过掌握 WebMvcConfigurer 的各种配置方法,你可以更好地控制 Spring MVC 的行为,构建出符合业务需求的高质量 Web 应用。