
在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提供的“全量配置基类”如果容器里有一个这样的BeanSpringBoot就会认为你想自己掌控全局于是把自动配置的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(ListHttpMessageConverter? 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改用实现WebMvcConfigurerJSON序列化格式异常误用configureMessageConverters覆盖默认转换器改用extendMessageConverters拦截器放行无效Spring Boot 2.6路径策略变化替换路径表达式或临时切回ant_path_matcherCORS配置不生效多个配置类冲突或与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(ListHttpMessageConverter? 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.jpgTomcat会把本机/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相关问题时少翻点源码多留点头发。