在SpringBoot项目里待久了,你会发现一个很有意思的现象:SpringMVC明明是Spring家族非常成熟的Web框架,但很多人一年到头都在写Controller、Service、Mapper,却从来没有主动配置过SpringMVC。SpringBoot把SpringMVC的大部分配置都自动处理掉了,以至于“SpringBoot扩展SpringMVC”这件事反而成了新手进阶时最容易卡住的地方。这篇文章我用自己的实际踩坑经历,把SpringBoot里扩展SpringMVC的几种方式、高频配置点、版本差异一次讲清楚。
如果你刚接触SpringBoot,或者在公司项目里看到别人写了一个继承WebMvcConfigurer的配置类却不知道是干嘛的,又或者你在网上搜索“SpringMVC配置”发现一堆过时答案,那这篇内容应该能帮你省下不少时间。我会从底层自动装配原理讲到具体配置代码,再配合几个真实项目中遇到过的问题,尽量让你看完就能在自己项目里落地。
1. SpringBoot与SpringMVC的关系:扩展前先搞清楚底层的“约定”
1.1 自动装配到底帮我们做了什么
很多新手会把SpringBoot和SpringMVC当成两个并列的东西,实际上SpringBoot是一个“集成框架”,它不做Web开发本身的事,而是通过自动装配把SpringMVC、Jackson、Tomcat这些组件拉进来,并且给它们提供一套开箱即用的默认配置。你在pom.xml里引入spring-boot-starter-web,其实就是两个动作:引入SpringMVC相关依赖,启动SpringBoot的Web自动装配机制。
这里有一个非常关键的核心类:WebMvcAutoConfiguration。这个类位于spring-boot-autoconfigure包下,上面有一堆条件注解,比如@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })。只有满足条件,它才会生效。它干的事非常多,包括注册DispatcherServlet、配置默认视图解析器、注册静态资源处理、配置消息转换器、注册RequestMappingHandlerMapping等。换句话说,你在一个空的SpringBoot项目里启动Web环境,什么都不写,SpringMVC就能跑起来,靠的就是这个自动配置类。
理解这一点后,你就能明白“扩展SpringMVC”的真正含义了:我们不是要去重写SpringMVC,而是在SpringBoot已经自动配置好的基础上,往里面追加或覆盖一部分行为。那SpringBoot也早就想到了这点,它留了一个非常关键的“后门”:WebMvcConfigurer接口。只要容器里有一个WebMvcConfigurer的实现类,WebMvcAutoConfiguration就会在自动配置完成之后,把WebMvcConfigurer里的各种回调方法逐个执行一遍。
1.2 SpringMVC在SpringBoot中的启动链路
我们可以把SpringBoot启动SpringMVC的过程简单分成三步来理解。第一步,SpringBoot在启动时扫描到spring-boot-starter-web,自动配置类开始生效。第二步,WebMvcAutoConfiguration创建一个DispatcherServlet,并把它注册到内嵌的Tomcat容器中,同时初始化RequestMappingHandlerMapping、RequestMappingHandlerAdapter等核心组件。第三步,SpringBoot会从容器中取出所有WebMvcConfigurer类型的Bean,调用它们的回调方法,把这些回调方法的结果合并进MVC配置里。
这里有个容易忽略的点:这些WebMvcConfigurer回调方法的执行顺序是没有严格保证的。如果项目里有多个配置类都实现了WebMvcConfigurer,可能出现配置覆盖的情况。我建议你可以在配置类上使用@Order注解来指定顺序,但更好的做法是收敛配置职责,不要到处写小配置类,免得排查问题的时候找不到是谁改的配置。
1.3 扩展到底在扩展什么:是否等于覆盖默认配置
很多人在第一次接触“自定义SpringMVC配置”时,容易走进一个误区:以为只要写了配置类,SpringBoot的默认配置就会被自己的配置完全替代。其实不是这样。绝大多数情况下,我们只是“追加”配置。比如你实现了addResourceHandlers方法,静态资源处理器不是被替换,而是在默认的基础上新增了一个handler映射;你实现了addInterceptors方法,项目原有的拦截器一个都不会少,只是多了一个新的拦截器注册进去。
真正要小心的是另一种情况:如果你在配置类上直接继承WebMvcConfigurationSupport,那WebMvcAutoConfiguration会因为@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)这个条件而直接失效,整个SpringMVC的自动配置都会被“顶掉”。这个后果很严重,不是简单地增加一个配置,而是把默认的静态资源、消息转换器、视图解析器全部“归零”了。后面我会专门讲这个问题。
2. 扩展SpringMVC的三种姿势:我推荐第一种,别轻易碰第三种
2.1 方式一:实现WebMvcConfigurer接口,最主流也最安全
这是目前最推荐、也是Spring官方推荐的扩展方式。你只需要新建一个配置类,实现WebMvcConfigurer接口,按需重写其中的方法,再加上@Configuration注解让SpringBoot扫描到即可。我们来看一个最基础的代码骨架:
@Configuration public class MyWebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 注册自定义拦截器 registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login", "/static/**"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 映射外部目录到访问路径 registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/data/upload/"); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST"); } }这种方式之所以安全,是因为它不会阻止WebMvcAutoConfiguration生效。SpringBoot在自动装配阶段,会检测到你的MyWebMvcConfig,并且把它作为WebMvcConfigurer集合的一部分,与自动配置自带的内部配置类一并应用。你可以把SpringBoot的自动配置理解成一个“基础装修”,你的WebMvcConfigurer实现是在这个基础上做“软装”,不会拆承重墙。
2.2 方式二:继承WebMvcConfigurationSupport,能不用就不用
我见过很多网上老教程,让你继承WebMvcConfigurationSupport,然后重写addResourceHandlers等方法。这些教程很可能是基于Spring Boot 1.x或很早期的2.x,当时这个类确实是扩展MVC的方式之一。但在SpringBoot 2.x之后,官方明确不推荐这种用法,原因就是我前面提到的自动配置失效问题。
简单说,WebMvcConfigurationSupport是SpringMVC提供的“全量配置基类”,如果容器里有一个这样的Bean,SpringBoot就会认为你想自己掌控全局,于是把自动配置的SpringMVC部分全部关闭。你可能会看到静态资源访问不了了,返回JSON时某些默认转换器也不见了,页面上出现404,排查半天才发现是因为继承了这个类。
举一个我实际遇到的例子:有一个老项目,开发同学从网上复制了一个配置类,继承WebMvcConfigurationSupport,只是想在Swagger静态资源上做一下放行。结果上线后接口全部返回406,因为WebMvcAutoConfiguration里的MappingJackson2HttpMessageConverter没有自动注册,导致Controller方法返回值无法正常转JSON。后来我们删掉继承,改成实现WebMvcConfigurer,问题立刻消失。
2.3 方式三:直接在启动类实现WebMvcConfigurer,可行但不够好
还有一部分同学图省事,直接在@SpringBootApplication标注的主启动类上实现WebMvcConfigurer接口,比如:
@SpringBootApplication public class DemoApplication implements WebMvcConfigurer { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void addInterceptors(InterceptorRegistry registry) { // ... } }这样写确实能生效,因为启动类本身也是一个@Configuration类,会被Spring容器扫描到。但我不推荐在项目中使用。首先它会让主类变得臃肿,一个本应只负责启动的类慢慢塞满了各种配置代码;其次它把配置职责集中在顶层,多人协作时容易产生冲突。如果你只是临时做个demo,可以这样玩;若是公司项目,建议还是单独建一个config包,放专门的配置类。
2.4 三种方式的对比与选型建议
| 方式 | 是否影响自动配置 | 推荐程度 | 典型使用场景 |
|---|---|---|---|
| 实现WebMvcConfigurer | 不影响 | 强烈推荐 | 绝大多数SpringBoot项目的常规扩展 |
| 继承WebMvcConfigurationSupport | 会让WebMvcAutoConfiguration失效 | 不推荐 | 极少场景需要手动控制全部MVC配置 |
| 启动类实现WebMvcConfigurer | 不影响 | 不推荐 | 快速demo,或代码规模很小的项目 |
我个人在实际项目中的选型几乎只有一个:实现WebMvcConfigurer。遇到个别需要完全接管MVC的场景,我会先认真评估是否真的有必要,因为一旦接管,后续所有SpringBoot版本的MVC自动更新都与你无关了,维护成本很高。
3. 从实际需求出发:五个高频扩展点详解
3.1 静态资源映射:前端资源分离时的必经之路
在前后端分离项目中,前端打包好的静态资源通常放在Nginx上,但有些中小项目会把前端构建产物放到后端服务里。SpringBoot默认已经支持classpath:/static/、classpath:/public/等目录,你可以直接把静态文件丢到resources/static下面,通过http://localhost:8080/index.html访问。但如果你想把某个外部磁盘目录映射成URL访问路径,比如上传的文件存在服务器/data/upload/,那就要用addResourceHandlers来配置。
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:/data/upload/") .setCachePeriod(3600); }这段配置的含义是:凡是/files/**路径的请求,都到本地磁盘/data/upload/目录下找对应文件。setCachePeriod可以设置浏览器缓存时间,单位是秒,对图片、PDF等不经常变的文件很有效。
这里有一个容易被坑的点:如果你配置的addResourceHandler是/**,那么所有请求都会优先交给这个处理器去找静态文件,可能把你原本的Controller接口也“吞”了。所以日常使用中,我建议尽量使用有明确前缀的路径,比如/files/**、/static/**,不要把根路径/**随便映射到磁盘目录。
3.2 拦截器注册:登录校验、接口鉴权、埋点通用做法
拦截器是SpringMVC扩展里的高频功能,很多项目需要做登录状态校验、接口访问日志、权限控制。SpringBoot中注册拦截器非常简单,第一步写一个实现HandlerInterceptor接口的类,第二步在addInterceptors方法里注册。下面是一个简单的登录拦截器:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 简单示例:从Header中获取token String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 实际项目里在这里解析token,并存入ThreadLocal或request attribute request.setAttribute("userId", "123"); return true; } }注册时要注意放行路径的粒度。比如登录接口、注册接口、静态资源、Swagger文档一般都需要放行,否则用户没登录连登录页的样式都加载不出来:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/login", "/api/register", "/doc.html", "/webjars/**"); }这里我踩过一个坑:Spring Boot 2.0时代,excludePathPatterns("/webjars/**")这种写法没什么问题,但到了Spring Boot 2.6以后,默认路径匹配策略从AntPathMatcher换成了PathPatternParser,一些旧的路径表达式行为会发生变化。最典型的例子是/**和/static/之类通配符的匹配优先级。遇到拦截器放行失效时,不要先怀疑代码逻辑,优先确认SpringBoot版本和路径策略。
3.3 CORS跨域配置:前后端分离踩坑记录
前后端分离已经是标配,跨域问题几乎每个项目都会碰到。有两种解法:一种是在每个Controller方法上加@CrossOrigin注解,另一种是全局配置addCorsMappings。实际项目中我更喜欢后者,因为不用侵入Controller代码,配置一次全局生效:
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .maxAge(3600); }这里有个细节:早期版本里常用.allowedOrigins("*"),但在Spring Boot 2.4以上版本,如果同时允许携带凭证(.allowCredentials(true)),就不能用*通配具体origin,否则会报错。推荐改用allowedOriginPatterns("*"),它在携带凭证时也能正常工作。如果你遇到“跨域配置明明写了但不生效”的情况,先检查两点:第一,是否有多个配置类都对同一路径配置了CORS,产生了覆盖;第二,项目里是否同时使用了@CrossOrigin注解和全局addCorsMappings,导致预检验证时产生重复响应头。
3.4 HttpMessageConverter消息转换器:JSON序列化细节
SpringMVC处理请求和响应时,靠的是HttpMessageConverter把Java对象转换成JSON、XML等格式,或者把请求体解析成Java对象。SpringBoot默认注册了Jackson的消息转换器,大多数场景不用我们自己配置。但有些项目需要定制序列化,比如日期格式统一为yyyy-MM-dd HH:mm:ss,或者空字符串转null,这时可以在extendMessageConverters里追加自定义ObjectMapper。
@Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { for (HttpMessageConverter<?> converter : converters) { if (converter instanceof MappingJackson2HttpMessageConverter) { MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter; ObjectMapper objectMapper = jacksonConverter.getObjectMapper(); objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } } }这个方法名extendMessageConverters和configureMessageConverters不同。前者是在SpringBoot默认转换器列表基础上追加或修改,后者则是完全接管。之前我在代码里误用了configureMessageConverters,结果SpringBoot默认的转换器被清空,导致返回LocalDateTime时直接报序列化错误。后来改成extendMessageConverters,问题就解决了。所以如果你只是想定制Jackson,千万别动configureMessageConverters。
3.5 路径匹配与视图控制:过时API与新版适配
在SpringMVC中,configurePathMatch可以用来设置路径前缀、是否启用后缀匹配等。比如给所有接口统一加/api前缀,可以通过PathMatchConfigurer实现:
@Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotatedMethod(RequestMapping.class)); }这个功能在微服务网关环境下比较有用。但要注意,Spring Boot 2.6及以上版本默认使用PathPatternParser,与原来的AntPathMatcher有一些差异,尤其是在使用PathMatchConfigurer时如果看到了关于setUseSuffixPatternMatch、setUseTrailingSlashMatch一类的API过时提示,建议直接忽略或改用新API,不要为了兼容旧写法强行改配置,否则可能引发更多问题。
addViewControllers则是“没有Controller的请求映射”,适合直接跳转页面。比如访问/跳转到/index.html,或者配置一个纯静态的错误页:
@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addRedirectViewController("/", "/index.html"); registry.addViewController("/login").setViewName("login"); }以前我用它来配置欢迎页跳转,后来换了前后端分离部署方案后就不太用了。但在纯后端渲染模板引擎的项目中,它依然很实用。
4. 常见问题与排查技巧实录
4.1 扩展配置“不生效”怎么办
这是被问得最多的问题。配置类写了,方法也重写了,但接口的拦截器没生效,跨域还是报错,静态资源也找不到。遇到这种情况,我一般按下面三步排查。
第一步,确认配置类被Spring容器扫描到。配置类上有没有@Configuration注解?类存放的包是否在启动类所在包的子包下面?如果你的启动类在com.example.demo,但配置类放在com.example.config,那没问题;如果放在com.other.config这种并行包下,默认扫描不到。
第二步,确认方法名是否写对。WebMvcConfigurer接口里方法很多,方法名拼写一错,IDE可能不会报错,但重写的方法是无效的。比如addInterceptors写成了addInterceptor,编译不报错,但方法不会被回调。这种低级错误我见过不止一次,所以我习惯在每个扩展方法上打一个短暂的日志,确认方法被调用。
第三步,看看是不是有多个配置类互相覆盖。前面提到,所有WebMvcConfigurer实现类都会被合并生效,但如果两个配置类对同一个路径做了不同处理,后执行的可能会覆盖先执行的。排查时可以给不同配置类加上@Order注解,或者临时注释掉另一个配置类做二分定位。
4.2 SpringBoot版本升级后路径匹配失效
在Spring Boot 2.6.0版本里,官方做了一次不兼容升级:默认路径匹配策略由AntPathMatcher切换为PathPatternParser。这次升级影响面很大,最典型的问题就是拦截器路径匹配、静态资源路径匹配行为变化。比如excludePathPatterns里写的/api/**,在旧版能排除,在新版可能排除不了,因为PathPatternParser对/**和通配符的语义不完全一样。
解决方式有几种。第一种,调整你的路径表达式,尽量符合新规则;第二种,如果你暂时不想动代码,可以在配置文件里加一行强制切回旧策略:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher但我不建议长期依赖这个开关,因为官方在后续版本可能会移除旧策略。更优雅的做法是升级代码,适配新规则。Spring Boot 3.x已经默认使用PathPatternParser,有些API已经从旧实现移除,升级时要重点检查拦截器、资源映射、CORS这几块。
4.3 拦截器放行路径失效:静态资源被拦截
之前有个项目,前端登录页引用了/static/css/login.css,但页面打开后样式全丢。查了Nginx配置,查了静态资源路径,最后发现是登录拦截器把所有路径都拦截了,虽然写了excludePathPatterns("/static/**"),但静态资源访问还是被拦住了。
原因在于Spring Boot默认静态资源路径不是/static,而是/static/**本身是URL访问路径,但资源实际位置在classpath:/static/。拦截器匹配的是URL,所以excludePathPatterns("/static/**")理论上是有效的。问题出在我们的配置里把静态资源映射到了自定义目录,比如:
registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/resources/static/");如果addResourceHandlers和拦截器注册的路径前缀没有对齐,就会出现“资源明明存在但被拦截器拦住”的现象。建议你在写拦截器放行路径时,先确认项目中所有静态资源访问的URL前缀,并把它们都加入excludePathPatterns,包括Swagger的/swagger-ui/**、/v3/api-docs/**等。
4.4 配置类被代理两次:@EnableWebMvc背后的坑
在SpringBoot项目里,如果你不需要完全接管SpringMVC,千万不要在配置类上加@EnableWebMvc。这个注解的主要作用是导入DelegatingWebMvcConfiguration,并让WebMvcConfigurationSupport生效,但一旦使用,同样会导致WebMvcAutoConfiguration失效。网上有部分教程会在自定义配置类上同时写@Configuration和@EnableWebMvc,这在SpringBoot中是一个非常隐蔽的坑。
我遇到过一种现象:加了这个注解之后,项目能启动,接口也能调通,但是返回的JSON中时间字段格式乱了,静态资源全部404。排查了很久,最后定位到配置类上多了一个@EnableWebMvc。去掉之后所有现象消失。所以建议大家在SpringBoot项目中尽量不要使用@EnableWebMvc,除非你非常清楚自己在做什么。
4.5 常见问题速查表
| 现象 | 大概率原因 | 解决建议 |
|---|---|---|
| 扩展方法没有执行 | 配置类未被扫描、方法名拼写错误 | 检查包路径和注解,打日志确认 |
| 静态资源404 | 继承WebMvcConfigurationSupport或加了@EnableWebMvc | 改用实现WebMvcConfigurer |
| JSON序列化格式异常 | 误用configureMessageConverters覆盖默认转换器 | 改用extendMessageConverters |
| 拦截器放行无效 | Spring Boot 2.6+路径策略变化 | 替换路径表达式,或临时切回ant_path_matcher |
| CORS配置不生效 | 多个配置类冲突,或与@CrossOrigin混用 | 统一使用一个全局配置类 |
5. 从零到一个完整的扩展实践:一个配置类解决多个需求
5.1 综合场景描述
假设我们有一个后台管理系统,需要实现以下需求:接口路径统一以/admin开头;登录校验拦截器除登录接口外全部拦截;上传的文件放在服务器外部目录/data/files,需要能通过/files/**访问;前端调用接口可能涉及跨域;希望返回的JSON里时间格式统一、空值不输出。
针对这些需求,我们可以写一个完整的WebMvcConfigurer配置类。由于configurePathMatch是给所有标注了RequestMapping的方法增加前缀,这会同时影响Controller接口,而拦截器注册的addPathPatterns则要基于最终访问的URL来写,这是最容易混乱的地方。
5.2 完整配置代码与解释
@Configuration public class AdminWebMvcConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 给所有含 @Controller 且带 @RequestMapping 的类前缀加上 /admin configurer.addPathPrefix("/admin", HandlerTypePredicate.forAnnotatedMethod(RequestMapping.class)); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) // 注意:加上前缀后,实际URL是 /admin/** .addPathPatterns("/admin/**") // 登录接口实际路径为 /admin/login .excludePathPatterns("/admin/login", "/files/**"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:/data/files/") .setCachePeriod(3600); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/admin/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .maxAge(3600); } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { for (HttpMessageConverter<?> converter : converters) { if (converter instanceof MappingJackson2HttpMessageConverter) { MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter; ObjectMapper objectMapper = jacksonConverter.getObjectMapper(); objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } } } }这段代码里,最需要注意的是configurePathMatch和addInterceptors的配合。因为给接口加了/admin前缀,所以登录接口的实际URL可能是/admin/login,如果你在拦截器里放行的是/login,那么登录请求照样会被拦截,然后返回401,这又是一个非常隐蔽的坑。
5.3 实际运行效果与注意事项
我在本地用Spring Boot 2.7版本跑过这个完整配置。启动项目后,访问/admin/test这样的接口,拦截器会截到请求,校验Header中的token;访问/admin/login则直接放行;访问/files/avatar.jpg,Tomcat会把本机/data/files/avatar.jpg的内容返回给浏览器;如果前端页面放在另一个端口,调用/admin/**接口时能正常响应CORS头。整体跑下来,核心功能都符合预期。
但这个配置也暴露出一个问题:如果项目里还有其他不以/admin开头的接口,比如/public/**,它们是不会被configurePathMatch加前缀的,但拦截器里也没有拦截它们。实际项目中,这往往不是bug,而是需求设计如此。如果你希望全站接口都要登录,拦截器路径要相应地修改为/**,并放行静态资源和无需认证的路径。
5.4 为什么这个方案比散装配置更稳妥
把多个扩展点收敛到一个配置类里,最大的好处是配置一目了然。你打开这个类,就能看到路径前缀规则、拦截器范围、静态资源映射、跨域规则、JSON序列化规则。排查问题时不需要在十几个类之间来回跳。
当然,不是说所有项目都要全部塞进一个类。如果某个模块的配置特别多,比如拦截器就写了十几个,那可以拆成多个WebMvcConfigurer实现类,用包名或命名区分。但要注意给它们分配好优先级,并且避免同一个路径被不同配置类重复定义。
6. 最后再分享几个实战中的小技巧
6.1 利用WebServerFactoryCustomizer调整内嵌Tomcat参数
虽然这不算SpringMVC配置,但在SpringBoot项目里做Web开发时经常会一起碰到。比如要限制上传文件大小,很多人直接在application.yml里写:
spring: servlet: multipart: max-file-size: 100MB max-request-size: 200MB这里的max-file-size控制单个文件大小,max-request-size控制整个请求体大小。如果是Spring Boot 2.x,这个配置对大部分场景是够用了。但如果需要修改Tomcat本身的连接数、keepalive超时等参数,就需要自定义WebServerFactoryCustomizer。这些虽然不属于WebMvcConfigurer,但又经常和MVC扩展一起讨论,分享一下可以帮新人少走弯路。
6.2 排查顺序建议:先看配置类是否注册,再看日志里有没有异常
有些时候配置写了没生效,不是代码问题,而是项目启动时出现了异常,导致配置类没有被正常加载。所以我建议一旦怀疑配置不生效,第一件事是看后端启动日志里有没有关于MyWebMvcConfig的报错,比如依赖注入失败、循环依赖等;第二步再在配置类里临时加一个构造方法或@PostConstruct方法输出日志,确认确实被Spring管理了;第三步才是去分析拦截器、跨域等细节。
6.3 千万不要在扩展MVC时写一堆静态工具类自欺欺人
我记得有个同事为了给多个接口加前缀,不选择configurePathMatch,而是自己在Controller类上一个个加@RequestMapping。结果维护了半年,接口前缀改一次要动十几个Controller。后来我帮他改成配置里的统一前缀,代码瞬间少了很多。SpringBoot已经提供了这么方便的扩展点,该用就要用,不要什么都靠手改。
6.4 说点关于“面试被问到”的个人看法
“SpringBoot如何扩展SpringMVC”这道题在面试中出现的频率很高,但面试官真正想考察的往往不是背诵配置方法,而是你有没有意识到WebMvcAutoConfiguration和WebMvcConfigurer之间的关系,以及继承WebMvcConfigurationSupport或使用@EnableWebMvc会带来什么后果。如果你能把这个原理讲清楚,再结合实际项目说一个静态资源或拦截器的坑,会比单纯报出一串方法名得分高很多。
我在实际项目中使用SpringBoot这些年,踩得最多的坑几乎都集中在“配置被自动装配覆盖”、“版本升级后旧API失效”、“多个配置类相互干扰”这三类。个人体会是,SpringBoot的自动配置确实省心,但省心不等于可以不懂原理。只有把自动配置的关键开关和扩展机制搞清楚,才敢说“你真正掌握SpringBoot”。希望这篇文章能让你在遇到MVC相关问题时,少翻点源码,多留点头发。