SpringMVC核心原理与实战:从请求流程到拦截器排错
SpringMVC这名字但凡搞过Java后端的同学都不会陌生。哪怕现在Spring Boot已经大行其道Controller、Service、Repository这套Web层的核心机制骨子里跑的还是SpringMVC那套东西——深入理解它的工作流程、请求处理链路和拦截器机制对排查线上问题、做二次封装、甚至哪天要手写框架都有实打实的好处。不少朋友问我SpringMVC到底怎么学才不算白学这篇博文我就把自己从入门到干活的经验一次性梳理出来围绕SpringMVC的工作流程、参数绑定、数据返回、拦截器实战和经典排错这五块展开只讲实战中用得上的东西适合刚学完Java基础、准备啃Web框架的同学也适合已经会用Spring Boot但想补一补底层原理的开发者。1. SpringMVC整体设计一次HTTP请求的完整旅程1.1 DispatcherServlet所有请求的“前台接待”SpringMVC的核心是一个叫DispatcherServlet的Servlet它本质上就是一个前端控制器。所有进来的HTTP请求第一步都会被它拦下来然后由它统一调度其他组件去干活。你可以把它理解成公司前台客人来了前台不会自己直接处理业务而是先确认你要找谁然后把你带到对应的部门去。当你在web.xml里配置了SpringMVC后容器启动时会初始化DispatcherServlet。只要请求路径命中它的url-pattern就会进入SpringMVC的请求处理管道。一个完整的请求生命周期大概是这么走的浏览器发出的请求被DispatcherServlet接收DispatcherServlet根据请求URL询问HandlerMapping找到对应的Controller方法命中后会生成一条HandlerExecutionChain其中可能包含多个拦截器在真正调用Controller方法之前拦截器的preHandle会先执行HandlerAdapter负责真正调用Controller方法框架会自动完成参数绑定Controller方法执行完返回ModelAndView或者被ResponseBody直接写回数据返回给DispatcherServlet后拦截器的postHandle执行ViewResolver把逻辑视图名解析成真正的视图渲染HTML响应回浏览器最后执行拦截器的afterCompletion回收资源或记录日志。我当年第一次看这个流程的时候最大的困惑是为什么搞得这么绕直接请求Controller不就行了吗后来才明白这么设计的核心目的是解耦。DispatcherServlet不关心业务逻辑HandlerMapping只负责“找”HandlerAdapter只负责“调”ViewResolver只负责“渲染”每一环都可以独立替换。这正好符合开闭原则——想加功能不需要把Servlet推倒重来。1.2 HandlerMapping与HandlerAdapter怎么配合HandlerMapping的作用是“根据请求找到处理者”。在注解驱动时代框架默认用的是RequestMappingHandlerMapping它会在Spring容器启动时扫描所有标了Controller和RequestMapping的方法把URL和Method的映射关系存到内存里。所以你在浏览器输入一个URL它能很快定位到对应的Controller方法。HandlerAdapter就更关键了。它负责把请求参数转换成方法参数、调用方法、再把返回值包装成ModelAndView或者直接写回响应。这里面最核心的一块是参数解析器HandlerMethodArgumentResolver框架内置了一大堆比如RequestParam、PathVariable、RequestBody都是靠它们解析的。这也是为什么你可以在Controller方法里写各种五花八门的参数框架都能帮你塞进来。实际开发中很多同学直接用了Spring Boot的自动配置基本不感知这两个组件。但排查问题时你会意识到很多“接口参数怎么突然收不到”的怪事本质上都出在参数解析这个环节。所以面试被问“SpringMVC九大组件”的时候至少要把HandlerMapping、HandlerAdapter、ViewResolver、HandlerExceptionResolver这四件套讲清楚才算真的懂。1.3 视图解析与渲染返回的不一定真的是页面Controller方法执行完返回的东西可以五花八门字符串逻辑视图名、ModelAndView、ModelMap或者直接通过ResponseBody输出JSON字符串。如果走的是视图渲染路线ViewResolver就会介入最常见的是InternalResourceViewResolver它会帮你做前缀后缀拼接比如你返回view配置了prefix/WEB-INF/views/、suffix.jsp它最终解析到/WEB-INF/views/view.jsp。注意服务端渲染这种模式的短板在于前后端耦合如果项目是前后端分离架构后端一般就只负责返回JSON数据渲染的事交给前端框架。所以现在很多项目里ResponseBody 消息转换器的组合比视图渲染更常用。但这不代表ViewResolver就不重要了你去看老项目的代码或者接手一些遗留系统还是会碰到JSP、Freemarker模板渲染。这块原理该懂还是得懂。关于九大组件我的建议是别硬背结合流程去理解请求来了DispatcherServlet要分发就必须有HandlerMapping、HandlerAdapter参数要转换就有HttpMessageConverter出异常了有HandlerExceptionResolver兜底要解析视图返回页面则需要ViewResolver。顺着这个思路九大组件自然就串起来了。2. 环境搭建与第一个Controller2.1 依赖与基础配置别一上来就Spring Boot很多新手一入门就学Spring Boot导致连SpringMVC是怎么被装进Web容器里的都不知道。为了把原理讲透我用传统的SSM工程来演示一遍是如何一步步把SpringMVC跑起来的。先看Maven依赖项目需要一个web工程引入spring-webmvc是必须的另外还需要Servlet API、JSP相关依赖以及Jackson用于JSON转换。dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.31/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies这里有个细节需要注意Servlet API和JSP API的作用域要设成provided因为Tomcat这类Servlet容器自带这些类库打包时如果带进去反而会和容器冲突。我见过不少同学把war包里塞了servlet-api结果部署到Tomcat上启动就报LinkageError这就是依赖作用域没搞对。2.2 DispatcherServlet与Spring配置文件让框架先跑起来传统Web工程需要配置web.xml把DispatcherServlet注册进去并告诉它SpringMVC的配置文件在哪。同时项目里如果还需要Service层、Dao层的Spring容器通常会再用ContextLoaderListener加载一个Spring根容器让父子容器各司其职。?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 加载Spring根容器管理Service、Dao等Bean -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 加载DispatcherServlet管理Controller、拦截器、视图解析器等Web层Bean -- servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:springmvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping /web-appurl-pattern这里有几个坑。最常被问到的就是到底配“/”还是“/”。“/”表示除了JSP以外的所有请求都交给DispatcherServlet处理这样前端页面还能走容器的JSP处理器“/”会把JSP请求也拦截住页面渲染就会出问题。所以标准做法是配“/”。springmvc.xml里配好组件扫描、注解驱动、视图解析器就能跑通第一个接口了?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:mvchttp://www.springframework.org/schema/mvc xsi:schemaLocation http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd context:component-scan base-packagecom.example.controller/ mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean /beans这里我建议把Controller和Service分开扫根容器扫Service、DaoSpringMVC容器扫Controller避免重复扫描导致Bean管理混乱。虽然Spring Boot时代这些琐碎配置都被自动化了但理解了这层关系你在Boot里遇到奇怪的Bean覆盖问题时能快速定位。2.3 第一个Controller页面跳转和JSON返回都要会Controller的写法从最早的实现Controller接口到后来的Controller注解再到现在的RestController风格一直在简化。传统写法是这样Controller RequestMapping(/hello) public class HelloController { GetMapping(/page) public String toPage(Model model) { model.addAttribute(message, Hello SpringMVC); return hello; } GetMapping(/json) ResponseBody public MapString, Object toJson() { MapString, Object result new HashMap(); result.put(code, 200); result.put(message, Success); return result; } }sheet说回hello方法最终会去/WEB-INF/views/hello.jspModel里的数据可以通过EL表达式直接取。而toJson方法因为加了ResponseBody就会通过Jackson把Map序列化成JSON字符串写回浏览器。这里必须注意如果springmvc.xml里没有配 mvc:annotation-driven/ ResponseBody很可能不生效原因是缺少消息转换器的自动注册。我遇到不少报错接口明明注解都写了却一直返回404或直接下载JSON文件十有八九就是漏了这一行。3. 参数接收与数据返回日常开发的核心技能3.1 请求参数绑定的4种常用姿势Controller方法里的参数绑定是日常编码使用频率最高的能力之一。我总结了四个最常用的姿势面试也总考第一种RequestParam。适合接收URL上的query参数或表单字段比如/user/query?age20方法参数里写RequestParam(age) Integer age就能拿到。这个注解的required属性默认是true如果前端没传这个参数SpringMVC直接报400。所以拿不准前端传不传的时候可以设置required false或者给一个defaultValue。第二种PathVariable。这是REST风格接口的关键注解适合把参数放在URL路径里比如/user/101Controller里写GetMapping(/user/{id})再用PathVariable(id) Long id接收。用这个方式时参数名称最好显式声明不然有些环境下会因为编译参数没开-parameters而解析不到。第三种POJO对象绑定。给方法加一个实体类参数比如public String save(User user)框架会自动把请求参数和User属性对应映射支持级联属性比如user.address.city这样的嵌套字段。这个写法在表单提交场景下非常省事但要注意请求参数的key如果没有一个匹配的属性SpringMVC会直接忽略不会报错这会让一些问题隐藏起来排查的时候要留个心眼。第四种RequestBody。这个是前后端分离项目里最常用的前端以JSON格式把数据放到请求体里后端用一个实体类接收。注意它和上面三种完全是两个路子走的是HttpMessageConverter消息转换器而且一个Controller方法里只能有一个RequestBody参数。如果前端提交的是application/x-www-form-urlencoded格式那RequestBody就拿不到数据只能走POJO绑定或RequestParam。3.2 从ModelAndView到ResponseBody数据到底怎么出去服务端渲染时代Controller返回ModelAndView是最正统的写法代码看起来就是“准备数据 指定视图名”RequestMapping(/detail) public ModelAndView detail(RequestParam Long id) { User user userService.getById(id); ModelAndView mv new ModelAndView(); mv.addObject(user, user); mv.setViewName(userDetail); return mv; }这种模式在前后端不分离的时代很自然但在现在的前后端分离架构里后端已经不太需要关心视图了。Controller只要把数据通过ResponseBody输出成JSON前端拿到数据自己渲染页面。这样切分以后后端接口可以一批人开发前端页面可以一批人开发只要约定好接口契约两边就能并行推进互不阻塞。ResponseBody底层依赖HttpMessageConverter其中最关键的是MappingJackson2HttpMessageConverter它专门负责把Java对象序列化为JSON。也就是说只要项目引入了Jackson依赖又开启了mvc:annotation-driven返回对象时框架会自动完成序列化你不需要手动处理。不过要注意序列化的一些小坑。比如日期类型的默认格式不太友好通常会得到一串时间戳如果想输出yyyy-MM-dd HH:mm:ss要么给字段加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)要么全局配置Jackson的ObjectMapper。另外遇到循环引用比如双向关联的实体类序列化会抛StackOverflowError解决办法是加JsonIgnoreProperties或者改用DTO对象别直接拿实体类往外丢。3.3 REST风格接口的实战建议SpringMVC 4.3以后推出了一组组合注解GetMapping、PostMapping、PutMapping、DeleteMapping分别对应HTTP的GET、POST、PUT、DELETE方法。相比一个全面通吃的RequestMapping组合注解更容易表达接口语义代码可读性也更高。比如获取资源用GET创建资源用POST更新用PUT删除用DELETEURL只描述资源本身动词交给HTTP方法。在实际项目里我一般建议团队内部定好规范查询类接口用GET写操作必须用POST或PUT删除操作我倾向于用POST加一个/delete/xxx路径因为不少网关和日志系统对DELETE的支持不太友好而且DELETE请求没法带请求体某些场景下批量删除会很别扭。这不是说DELETE不好而是要在团队可维护性和技术落地方案之间做取舍。另外RESTful接口在Controller方法返回时一般搭配ResponseEntity或者自定义统一响应体比如ResultT。我个人更推荐统一响应体里面放code、message、data三个字段。这样不管接口正常还是出错前端都能用同一套逻辑去处理排查问题时也不用到处找特殊格式。4. 拦截器深入实战登录校验、权限控制与性能日志4.1 HandlerInterceptor生命周期与执行顺序如果说过滤器Filter是Servlet规范层面的“门卫”那SpringMVC的HandlerInterceptor就是业务层面的“安检员”。它定义在SpringMVC框架里有权限拿到Spring容器的Bean比如Service、RedisTemplate这些所以非常适合做登录态校验、接口幂等判断、日志埋点这类业务逻辑。HandlerInterceptor接口里有三个方法preHandle在调用Controller方法之前执行。返回true则放行返回false则中断请求Controller不会执行postHandle在Controller方法执行完、视图渲染之前执行。此时ModelAndView还没被渲染可以修改数据afterCompletion整个请求处理完毕之后执行无论Controller是否抛异常都会执行适合做日志和资源回收。我见过一个很经典的坑有人把日志打印放在postHandle里结果接口抛异常后日志没打出来找半天原因。其实afterCompletion才是“无论如何都执行”的钩子想确保日志不丢就该放在这里。关于多个拦截器的执行顺序规则有点像洋葱先进来的preHandle顺序执行postHandle和afterCompletion则逆序执行。画个图就是拦截器A preHandle → 拦截器B preHandle → Controller → 拦截器B postHandle → 拦截器A postHandle → 视图渲染 → 拦截器B afterCompletion → 拦截器A afterCompletion同时要注意如果拦截器B的preHandle返回了false那么拦截器A的postHandle不会执行但afterCompletion会执行A的如果你配置了的话。这个行为比较绕但也恰好体现了afterCompletion的定位保证清理工作一定会做。4.2 拦截器注册与路径规则的两种写法在springmvc.xml里注册拦截器使用mvc:interceptors标签mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ bean classcom.example.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors如果你用的是Spring Boot或者JavaConfig方式则是实现WebMvcConfigurer接口Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /static/**, /error); } }路径匹配规则注意一下“/**”匹配所有路径包括多级子路径“/*”只匹配一层路径。比如/user/list用/*能匹配但/user/list/detail需要/**才行。这个规律记错的话你会发现拦截器要么拦不到深层次的接口要么连静态资源都被拦了。顺便提一下Spring Boot 2.0后静态资源默认在classpath:/static等位置如果拦截器配了/**又没有排除静态资源路径页面会直接被拦截成一片空白。这个问题在新手里出现频率非常高下面第5部分会单独展开。4.3 完整案例一个能用的登录态校验拦截器我直接写一个项目里比较常见的登录校验拦截器场景是访问业务接口时检查Session里有没有登录用户。没有就直接跳到登录页并且带一个业务提示。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求跨域时会先来一个OPTIONS请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } Object user request.getSession().getAttribute(LOGIN_USER); if (user null) { String ajaxFlag request.getHeader(X-Requested-With); if (XMLHttpRequest.equals(ajaxFlag)) { // AJAX请求返回401状态码前端统一处理跳转 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } else { response.sendRedirect(request.getContextPath() /login?needLogintrue); } return false; } return true; } }这个拦截器的几个业务细节值得说一下第一OPTIONS预检请求必须放行。前后端分离经常遇到跨域浏览器会先发一个OPTIONS请求探路如果拦截器把预检请求拦下来接口会出现“CORS error”看起来像是跨域配置错了实际上是拦截器把OPTIONS杀掉了。第二AJAX请求不能用重定向。普通页面请求被重定向到登录页浏览器会正常跳转。但AJAX请求接收302后浏览器默认会偷偷跟着重定向再拿回来的可能是登录页的HTML前端拿到的就是ParseError之类的东西根本没法处理。所以我在拦截器里判断了请求头X-Requested-With对AJAX直接返回401状态码由前端统一拦截401并弹出重新登录的提示这是一个很常见的实践方案。第三handler参数可以判断请求是不是找Controller方法。如果handler不是HandlerMethod类型说明请求的目标不是Controller可能是静态资源或别的处理器可以根据业务决定放行还是拦截。有些团队会用这个机制去区分“真正要校验的接口”避免静态资源也被权限卡住。4.4 踩坑记录多个拦截器不生效先查顺序拦截器不生效的问题我接手的项目里遇到过好几次最常见的三种情况第一种拦截器Bean没被加载。SpringMVC容器只管理springmvc.xml里扫描到的Bean如果你把拦截器写在另一个配置文件里而没有被加载配置自然无效。检查方式很简单在preHandle里打个日志启动时看有没有输出。第二种路径配置没问题但过滤器把请求拦了。有时候项目里同时有Filter和InterceptorFilter的doFilter没放行请求根本到不了DispatcherServlet拦截器自然就没机会执行。这种情况排查时先看请求有没有进入Controller如果进去了拦截器没触发那就是拦截器本身问题如果请求根本没进Controller就要考虑Filter。第三种登录拦截器生效了但登录后的用户信息取不到。这个多发生在同一Session在不同Servlet容器间切换、或者分布式架构下Session没有统一管理的情况。传统Session机制在多节点部署下很容易出现登录态不一致拦截器通过request.getSession()取数据不可靠。这个场景的解法一般是用Redis共享Session或者客户端Token方案。拦截器的逻辑不用变只是取登录用户的地方换成Redis查询。5. 常见问题与排错实录那些年我们踩过的坑5.1 高发问题速查表我把项目里和SpringMVC相关的常见问题整理成一张速查表方便你遇到对应现象时快速定位。问题现象可能原因解决办法请求404Controller没被扫描、映射路径写错、DispatcherServlet路径配置不对检查component-scan路径确认GetMapping和请求URL一致确认web.xml的url-patternResponseBody返回JSON失败缺Jackson依赖、没开mvc:annotation-driven添加jackson-databind依赖配置 mvc:annotation-driven/中文乱码请求编码不统一、响应头charset缺失配置CharacterEncodingFilter设置produces统一页面charset静态资源404DispatcherServlet拦截了静态资源配置 mvc:resources/ 或 mvc:default-servlet-handler/拦截器不生效Bean没被容器加载、路径不匹配、Filter先拦截给preHandle加日志检查拦截路径写法检查Filter链接口参数接收到null请求参数名不匹配、RequestBody格式不对、没加RequestParam用required和name属性定位用日志打印前端实际传的参数日期类型转换报400JSON中的日期格式和Java类型不兼容加JsonFormat或配置全局Jackson日期格式文件上传拿不到MultipartFile没配置MultipartResolver配置StandardServletMultipartResolverweb.xml里配multipart-config这张表不是全量但覆盖了我遇到过的高频问题。下面挑三个展开说说因为它们在面试和实际工作中反复出现。5.2 中文乱码问题从根源解决中文乱码是个老生常谈但就是反复出问题的话题。核心是编码不一致Tomcat的URI默认用UTF-8解码URL中的参数但如果前端页面是GBK编码的POST表单提交过来后端拿到的自然就是乱码反过来后端输出JSON时如果Content-Type里不带charsetUTF-8浏览器可能按本地默认编码解析也会出现“口口口”或者“锟斤拷”。解决请求乱码项目里一般配置一个CharacterEncodingFilterfilter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping注意filter-mapping的url-pattern是“/*”因为要让所有请求都经过编码过滤器。另外Tomcat 8以上版本URI编码默认已经是UTF-8所以GET请求的query参数问题不大重点是POST和响应的charset。如果用的是前后端分离更推荐Controller方法直接指定返回的媒体类型和编码GetMapping(value /list, produces application/json;charsetUTF-8) ResponseBody public ListUser list() { ... }或者用RequestMapping里的produces统一指定。但在企业级项目里这种硬编码不便于维护我会建议在配置层面统一设置消息转换器的默认编码这样所有接口都走同样的配置不用每个方法都加一遍。5.3 静态资源被DispatcherServlet拦截怎么处理静态资源404的问题是SpringMVC初学者必遇到的经典坑。当url-pattern配成“/”后所有非JSP请求都会进入DispatcherServlet但是SpringMVC默认没有处理静态资源的能力所以浏览器请求css、js、图片文件时HandlerMapping找不到对应的Controller直接返回404。解决办法有两种。第一种是用mvc:resources映射静态资源目录指定规则比如mvc:resources mapping/static/** location/static//第二种是用mvc:default-servlet-handler把没有处理器的请求交回给容器默认的Servletmvc:default-servlet-handler/两种方案的区别在于可控性。mvc:resources更精细还能配合缓存头配置default-servlet-handler偷懒省事但所有SpringMVC没处理到的请求都交给Servlet容器灵活性差一些。我建议项目里明确维护静态资源的目录规则统一用mvc:resources避免某些深层路径被容器处理导致行为不一致。使用mvc:resources时要注意一个细节如果你的拦截器path配了/**静态资源路径虽然能被Servlet处理但拦截器照样会执行preHandle。我遇到过做过权限校验的接口静态资源访问直接跳转登录页页面样式全丢就是因为拦截器没排除掉/static/**。正确做法是拦截器配置里加上excludePathPatterns(/static/**)或者拦截器代码里判断handler类型非HandlerMethod直接放行。5.4 请求404的“玄学”排查最后聊聊404问题。SpringMVC的404可能发生在三个层面Servlet容器层面、SpringMVC映射层面、视图解析层面。我一般是按下面这个思路排查的先看浏览器Network面板如果请求状态是404且响应体是Tomcat默认的404页面说明请求根本就没进SpringMVC优先检查web.xml的servlet-mapping和项目打包部署情况。如果404页面是Spring的错误页或者返回JSON格式的{timestamp, status, error}说明请求进了SpringMVC但Controller没匹配上这时看两个地方Controller类有没有加Controller并放在组件扫描范围内RequestMapping的路径和请求路径是不是完全一致大小写、参数占位符。如果这两个都正常就检查IDEA或Eclipse里是否把Spring配置正确加载有时配置文件没被识别成源代码目录导致classpath下没有springmvc.xmlDispatcherServlet启动失败接口自然全部404。这种排查习惯比对着报错乱猜高效得多。我经常跟组里的新人说任何404先分清“是容器层面的404还是SpringMVC层面的404”能省下东翻西找的大把时间。最后再多说几句个人体会做了一年多SpringMVC项目之后有个很深的体会框架看着简单但真要能游刃有余靠的是对请求生命周期和容器组件的真正理解。有人习惯把所有注解背一遍、把所有配置模板抄一遍遇到新版本或者定制需求就卡壳。其实SpringMVC的设计思路是很典型的“分工明确、层层传递”DispatcherServlet是这个流水线的总控HandlerMapping负责定位HandlerAdapter负责调用ViewResolver负责输出拦截器则像是流水线上的质检开关。理解这一整套你再看Spring Boot里的Web开发基本就是“原来那些复杂配置被自动配置接管了”这么一回事。最后分享一个小技巧写Controller时尽量遵循“POJO接收参数 返回统一响应体”的风格不要把HttpServletRequest直接塞进方法签名里到处传递。参数对象化之后测试好写接口文档也好生成后续要加字段也只需要改实体类。如果你还在用原生的request.getParameter去捞参数趁早改过来开发效率和代码可维护性会明显提升。SpringMVC这套底层功夫花时间研究透绝对值。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →