实测一个请求在 Spring MVC 里的完整路径:Filter → Interceptor.preHandle → 参数解析器 → Controller → ResponseBodyAdvice/消息转换器 → postHandle → afterCompletion → Filter;抛异常时没有 postHandle,由 @ExceptionHandler 处理;404 由静态资源处理器兜底再转 /error,405 不调用 preHandle;Boot 里有 6 个 HandlerMapping、4 个 HandlerAdapter、29 个参数解析器。
实测 Spring MVC 的请求匹配:字面量优先于变量、精确优先于 /**;带正则的变量与普通变量同时匹配会在请求时报 Ambiguous(500),启动时发现不了;末尾斜杠、.json 后缀、大小写默认都不匹配(404);%2F 被 Tomcat 拒绝(400);produces / consumes / params / headers 可以区分同一路径的方法,不匹配时 406 / 415;** 在模式中间启动失败。
实测 Spring MVC 的参数解析:必需参数缺失 400、类型转换失败 400、boolean 的 yes/on 当 true;不带注解的 int 参数缺失会抛 500 IllegalStateException;@RequestBody 的 JSON 错误、空 body 都是 400,多余字段被忽略;不带 setter 的 public 字段对象通过表单绑定一个字段都绑不上且不报错。
实测 Boot 4 的 Jackson 3 JSON 转换:日期类型写成 ISO 字符串,大整数写成数字(JS 会丢精度);反序列化时 LocalDateTime 带 Z 悄悄丢时区、Instant 的毫秒数被当成秒变成 +58768 年、Long 收到 1.5 悄悄变成 1,而 LocalDate 格式不对、enum 不认识会 400;Jackson 2 时代的 spring.jackson.serialization.write-dates-as-timestamps 在 Boot 4 里会启动失败。
实测 Spring MVC 异常处理:默认 /error JSON 只有 status、error、path;spring.mvc.problemdetails.enabled=true 只让标准 MVC 异常和 ResponseStatusException 输出 problem+json;@ControllerAdvice 里 @Order 小的先处理,控制器内的 @ExceptionHandler 优先于 Advice;Advice 里的 RuntimeException 兜底会吞掉带 @ResponseStatus 的异常(409 变成 500);Filter 里的异常不经过 Advice。
实测 Spring MVC 的拦截器与 Servlet Filter:Filter 包住整个拦截器链,preHandle 升序、postHandle 与 afterCompletion 倒序;preHandle 返回 false 后 Controller 与 postHandle 跳过,afterCompletion 只对返回过 true 的拦截器调用;404 请求 Filter 照常执行而拦截器只看路径匹配;Filter 读完请求体后 @RequestBody 得到 400。
实测 Spring MVC 的数据校验:@Valid 请求体失败抛 MethodArgumentNotValidException 且 Controller 不被调用,不写 @Valid 则脏数据直接进来;直接标在参数上的约束走内置的 HandlerMethodValidationException(400),控制器类上加 @Validated 反而变成 ConstraintViolationException(500);指定分组后默认组的约束不再检查;嵌套对象不写 @Valid 就不检查;Boot 默认响应和 problemdetails 都不带字段级违规。
实测 Spring MVC 内容协商:Accept 的 q 值和顺序决定 JSON 或 XML;返回 String 时只是按 Accept 贴 Content-Type 而不转换格式;produces 不匹配得到 406,ResponseEntity 显式指定 Content-Type 则不再协商;路径后缀(.json)在本版本下返回 404,?format= 要打开 favor-parameter 才生效,并且优先于 Accept;请求体方向 consumes 不满足得到 415。
实测 Spring MVC 文件上传:getOriginalFilename() 原样返回 ../../etc/passwd;默认单个文件 1MB(正好 1MB 能过,多 1 字节 413)、整个请求 10MB(超了客户端发送中被断开);413 响应体为空且不经过控制器的 @ExceptionHandler;部分个数默认上限 50(51 个文本字段就 413),由 server.tomcat.max-part-count 控制;关掉 multipart 后上传接口全部 400。
实测 Spring MVC 异步请求:返回 Callable 时 Tomcat 线程立刻释放,Callable 跑在 task 线程,完成后以 ASYNC 重新派发,拦截器 preHandle 被调用两次;DeferredResult 可以由任意线程完成;超时的实际等待时间对不上设定值(设 100 ms 的有时要等将近 1 秒),超时的 Callable 会被中断;Tomcat 只有 2 个线程时,10 个同步慢请求把别的请求挡住 1161 ms,Callable 只挡 1 ms。
实测 Spring MVC 的 SSE 与流式响应:SseEmitter 的线格式(id、event、data、retry、注释心跳)和逐事件到达;StreamingResponseBody 里的 flush() 被 nonFlushing 包装而无效,小块数据要到请求结束才一起到,块足够大才逐块到达;客户端断开后服务端要等下一次写失败才发现(多发 2 个事件),超时不精确;开启 gzip 压缩后事件仍逐块到达。
实测 Spring MVC 的 CORS:没有配置时服务端照常返回 200,拦截的是浏览器;@CrossOrigin 不在白名单的来源得到 403 Invalid CORS request;预检默认 Max-Age 1800,实际请求只带 Origin、Expose、Credentials 头;全局 addCorsMappings 与 allowedOriginPatterns 回显来源;异常的 500 在有匹配配置时仍带 CORS 头,没有匹配配置的路径(含 404)不带;CorsFilter 能覆盖 404;allowedOrigins("*") 加 allowCredentials(true) 启动不报错、请求时 500。