WebMvcConfigurer
本章聚焦 Spring MVC 的配置扩展层。前面章节讲解了如何编写 Controller、如何定义拦截器、如何进行消息转换,但这些组件都需要被"注册"到 Spring MVC 中才能生效。WebMvcConfigurer 是 Spring MVC 提供的核心配置接口,允许开发者以声明式方式定制框架行为,而无需继承或修改底层类。
本章与全局的关系:前面章节讲解了 HandlerInterceptor 的编写,本章讲解如何把拦截器注册到 Spring MVC。同时涵盖视图控制器、静态资源、跨域等常见配置场景。
定义与作用
WebMvcConfigurer 是 Spring MVC 提供的配置回调接口,定义了一系列 addXxx / configureXxx 方法,允许开发者在 Spring MVC 初始化时注入自定义配置。它采用"回调注册"模式——Spring MVC 在启动时会收集所有实现了该接口的 Bean,按顺序调用其配置方法。
核心职责包括:
- 注册拦截器(
addInterceptors)——指定拦截器生效的路径和排除的路径 - 配置视图控制器(
addViewControllers)——无需编写 Controller 即可映射 URL 到视图 - 配置静态资源(
addResourceHandlers)——指定静态资源的访问路径和存放位置 - 配置跨域(
addCorsMappings)——全局 CORS 策略 - 配置消息转换器(
configureMessageConverters/extendMessageConverters)——自定义 HttpMessageConverter - 配置参数解析器(
addArgumentResolvers)——自定义 Controller 方法参数解析 - 配置返回值处理器(
addReturnValueHandlers)——自定义返回值处理
生活类比:公司行政部的服务窗口
想象飞翔科技的行政部:
- 行政部(WebMvcConfigurer)不直接处理业务,但提供一系列服务窗口
- 你想申请门禁卡(注册拦截器)?去 3 号窗口
- 你想申请停车位(配置静态资源)?去 5 号窗口
- 你想申请出差报销(配置跨域)?去 7 号窗口
- 每个窗口都有标准化的表格和流程,你只需要填表提交,行政部统一处理
关键认知:WebMvcConfigurer 是"配置入口",不是"配置本身"。真正的配置逻辑(如拦截器实现、资源位置)由开发者提供,WebMvcConfigurer 负责把它们挂接到 Spring MVC 的运行时。
核心原理
配置类结构
工作流程:
- Spring 容器启动时,扫描所有
@Configuration类 - 发现实现了
WebMvcConfigurer的 Bean,收集其配置 DelegatingWebMvcConfiguration汇总所有配置,生成最终的 Spring MVC 运行时组件- DispatcherServlet 使用这些组件处理请求
拦截器注册流程
适用位置与常用属性
WebMvcConfigurer 是一个接口,开发者创建 @Configuration 类实现该接口即可生效:
| 方法 | 作用 | 典型场景 |
|---|---|---|
addInterceptors(InterceptorRegistry registry) | 注册拦截器 | 登录检查、权限校验、日志记录 |
addViewControllers(ViewControllerRegistry registry) | 直接映射 URL 到视图 | / → index.html,错误页面 |
addResourceHandlers(ResourceHandlerRegistry registry) | 配置静态资源映射 | /static/** → classpath:/static/ |
addCorsMappings(CorsRegistry registry) | 配置全局跨域策略 | 前后端分离项目的 CORS |
configureMessageConverters(List<HttpMessageConverter<?>> converters) | 配置消息转换器 | 添加自定义 YAML/Protobuf 转换器 |
extendMessageConverters(List<HttpMessageConverter<?>> converters) | 扩展消息转换器 | 在默认转换器基础上追加 |
addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) | 自定义参数解析器 | 解析自定义注解参数 |
addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) | 自定义返回值处理器 | 统一包装 REST 响应 |
configureViewResolvers(ViewResolverRegistry registry) | 配置视图解析器 | Thymeleaf、JSP 解析器 |
configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) | 启用默认 Servlet | 处理静态资源回退 |
InterceptorRegistration 链式 API
| 方法 | 作用 |
|---|---|
addInterceptor(HandlerInterceptor) | 添加拦截器实例 |
addPathPatterns(String... patterns) | 指定拦截的 URL 模式(如 /**, /api/**) |
excludePathPatterns(String... patterns) | 指定排除的 URL 模式(如 /login, /static/**) |
order(int order) | 指定拦截器顺序(数字越小越先执行) |
完整示例
场景
飞翔科技员工管理系统需要以下配置:
- 注册三个拦截器:登录检查(拦截所有路径,排除登录注册)、权限校验(仅拦截
/admin/**)、日志记录(拦截所有路径) - 配置首页直接跳转到
index.html - 配置静态资源映射
/uploads/**到文件系统目录 - 配置全局跨域,允许前端开发服务器
http://localhost:3000访问
项目结构
employee-web/
├── src/main/java/
│ └── com/feixiang/web/
│ ├── interceptor/
│ │ ├── LoginInterceptor.java
│ │ ├── AdminAuthInterceptor.java
│ │ └── LogInterceptor.java
│ └── config/
│ └── WebConfig.java
配置类实现
// WebConfig.java
package com.feixiang.web.config;
import com.feixiang.web.interceptor.AdminAuthInterceptor;
import com.feixiang.web.interceptor.LoginInterceptor;
import com.feixiang.web.interceptor.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
private final AdminAuthInterceptor adminAuthInterceptor;
private final LogInterceptor logInterceptor;
public WebConfig(LoginInterceptor loginInterceptor,
AdminAuthInterceptor adminAuthInterceptor,
LogInterceptor logInterceptor) {
this.loginInterceptor = loginInterceptor;
this.adminAuthInterceptor = adminAuthInterceptor;
this.logInterceptor = logInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录检查拦截器:拦截所有请求,排除登录、注册、静态资源
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register", "/static/**", "/error")
.order(1);
// 权限校验拦截器:仅拦截管理员路径
registry.addInterceptor(adminAuthInterceptor)
.addPathPatterns("/admin/**")
.order(2);
// 日志拦截器:拦截所有请求
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**")
.order(3);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 首页直接映射到 index.html
registry.addViewController("/").setViewName("index");
// 错误页面映射
registry.addViewController("/error/404").setViewName("404");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 文件上传目录映射为可访问的静态资源
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:/var/feixiang/uploads/");
// 额外的静态资源路径
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/assets/");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// 全局跨域配置
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
HTTP 请求示例 1:正常业务请求通过拦截器链
$ curl -X GET http://localhost:8080/api/employees \
-b cookies.txt \
-v
响应:
< HTTP/1.1 200
< Content-Type: application/json
<
[{"id":1,"name":"张三"}]
控制台输出:
[LoginInterceptor] preHandle: /api/employees → true
[LogInterceptor] preHandle: /api/employees → true
[LogInterceptor] afterCompletion: /api/employees, 耗时=15ms
[LoginInterceptor] afterCompletion: /api/employees
流程解析:
- 请求
/api/employees匹配/**,三个拦截器都参与 - 按 order 顺序:LoginInterceptor(1) → AdminAuthInterceptor 不匹配路径(跳过)→ LogInterceptor(3)
- LoginInterceptor 检查 session,已登录,返回 true
- LogInterceptor 记录开始时间,返回 true
- Controller 执行
- afterCompletion 逆序执行:LogInterceptor → LoginInterceptor
HTTP 请求示例 2:排除路径绕过登录检查
$ curl -X GET http://localhost:8080/login
响应:
< HTTP/1.1 200
< Content-Type: text/html
<
<!DOCTYPE html>...登录页面...
流程解析:
/login在excludePathPatterns中,LoginInterceptor 不拦截- LogInterceptor 仍然拦截(它的
addPathPatterns是/**,没有排除/login)
HTTP 请求示例 3:访问上传的文件
$ curl -X GET http://localhost:8080/uploads/avatars/user1.png
响应:
< HTTP/1.1 200
< Content-Type: image/png
<
[二进制图片数据]
流程解析:
/uploads/**匹配addResourceHandler,Spring 从file:/var/feixiang/uploads/目录查找avatars/user1.png- 不经过任何 Controller 或拦截器(静态资源由 ResourceHttpRequestHandler 处理)
易错场景与面试考点
误区一:@EnableWebMvc 和 WebMvcConfigurer 混用导致自动配置失效
错误代码:
@Configuration
@EnableWebMvc // 危险!
public class WebConfig implements WebMvcConfigurer {
// ...
}
错误现象:Spring Boot 的自动配置全部失效,包括静态资源处理、消息转换器、错误页面等。
纠正:在 Spring Boot 项目中,不要加 @EnableWebMvc。Spring Boot 的 WebMvcAutoConfiguration 已经自动配置了 Spring MVC,你只需要实现 WebMvcConfigurer 做增量配置。加 @EnableWebMvc 会关闭自动配置,导致大量默认行为丢失。
传统 Spring MVC 项目(无 Spring Boot)才需要 @EnableWebMvc 来启用注解驱动的 MVC 配置。
误区二:拦截器顺序理解错误
错误认知:"先注册的拦截器先执行 preHandle,也先执行 postHandle 和 afterCompletion。"
纠正:preHandle 是正序执行,postHandle 和 afterCompletion 是逆序执行。这是"责任链"模式的经典设计——先进去的后出来,确保资源清理的嵌套顺序正确。
误区三:excludePathPatterns 使用了错误的匹配规则
错误代码:
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/api/login"); // 想排除 /api/login
问题:如果请求是 /api/login?redirect=/home,excludePathPatterns 默认只匹配路径,不匹配查询参数,所以能正常排除。但如果写的是:
.excludePathPatterns("/api/*"); // 错误!
/api/login 不会被排除,因为 * 只匹配一级路径,** 才匹配多级路径。
正确写法:
.excludePathPatterns("/api/login", "/api/register", "/static/**");
面试高频:addPathPatterns 和 excludePathPatterns 的优先级
标准回答:excludePathPatterns 优先级高于 addPathPatterns。如果一个 URL 同时匹配两者,排除生效。Spring 内部先检查排除规则,再检查包含规则。
面试高频:Spring Boot 中 WebMvcConfigurer 与 application.properties 配置的关系
标准回答:两者是互补关系:
application.properties适合简单的键值配置(如server.port、spring.mvc.static-path-pattern)WebMvcConfigurer适合复杂的编程式配置(如拦截器注册、跨域策略、自定义参数解析器)- 如果两者冲突,
WebMvcConfigurer的编程式配置通常优先级更高
小结
WebMvcConfigurer 是 Spring MVC 的配置扩展接口,通过实现该接口的 addInterceptors、addResourceHandlers、addCorsMappings 等方法,开发者可以以声明式方式定制框架行为。在 Spring Boot 项目中,只需实现接口即可(无需 @EnableWebMvc),Spring Boot 会自动收集并应用这些配置。
本章与全局的关系:本章讲解了如何把拦截器注册到 Spring MVC 运行时。下一章"@CrossOrigin"将深入讲解跨域请求的底层机制和配置方式。