ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSM项目首页配置详解:从web.xml到Spring MVC路由的完整实现

SSM项目首页配置详解:从web.xml到Spring MVC路由的完整实现 1. 首页配置的真正难点在哪里1.1 拆解一次 index 请求的完整链路说一个玩 SSM 项目时几乎每次都躲不开的事把应用跑起来敲下http://localhost:8080/项目名/结果要么白屏要么 404要么直接给你跳到某个既不对也不像首页的页面。网上搜 “SSM 配置 index 页面的实现方式”多数答案会告诉你 “在 web.xml 里加个 welcome-file 就行”但等你自己跟着做问题根本没解决。根子在于大家没有把 index 页面放进 Spring MVC 的处理链路里去想。SSM 这个词看着是三个框架的缩写但首页路由这件事真正参与的只有 Spring MVC 和 Servlet 容器。一个请求从浏览器发出来到最终渲染出一个 index 页面至少要经过这么几步浏览器先请求http://localhost:8080/项目名/容器Tomcat、Jetty 这类收到请求后会先根据web.xml里的配置做判断。这里就有一个关键点容器找 index 页面的顺序和你直觉里想的不一样。它不会一上来就把请求交给 Spring MVC而是先看你的 webapp 根目录下有没有 index.html、index.jsp 这类欢迎文件。如果有容器直接用自带的 JSP 或静态资源处理器把这个文件返回出去如果没有才轮到 Spring MVC 的 DispatcherServlet 接管这个请求。这个先后顺序就是大多数 “SSM 首页配不明白” 案例的根源。你以为是 Spring MVC 没配好其实是容器和框架之间抢了一次请求归属你以为 Controller 写好了就能进首页结果根目录下一个多余的 index.jsp 就把你的 Controller 短路了。Spring MVC 接管之后会通过HandlerMapping找到处理/这个 URL 的 Controller 方法执行完之后返回一个逻辑视图名。这个名字不会被浏览器直接看到它会交给InternalResourceViewResolver由这个视图解析器在配置好的前缀目录和后缀名里拼出真正的物理路径最终把 JSP 渲染成 HTML 返回给浏览器。我的建议是把 “配置 index 页面” 这件事拆成两个独立的问题去看。第一个问题是 “根路径的请求由谁来接”第二个问题是 “接到之后渲染哪个物理视图”。只要把这两个问题分开配置就永远不会乱。1.2 三种首页路线的取舍工程里常见的 index 页面方案大致可以归成三条路线。第一条路线是纯静态首页。放一个 index.html 在 webapp 根目录不接任何后台逻辑。这种适合官网展示页、项目说明页、或者前后端分离之后把静态资源塞进同一个 war 包里的场景。优点是简单、启动即用缺点是拿不到登录用户信息、菜单权限这类动态数据一但需要动态内容就得改造。第二条路线是 Controller 动态渲染。在 Controller 里写一个处理根路径的方法返回一个视图名由视图解析器渲染 JSP。这是 SSM 项目里最主流的做法因为首页通常不只是个静态壳它可能需要加载用户菜单、展示公告列表、校验登录状态。Controller 一接管这些逻辑都能自然塞进去。第三条路线是 Spring MVC 的mvc:view-controller配置。如果你只需要从一个 URL 跳转到某个 JSP中间不需要任何业务方法可以用一行配置代替一个空 Controller 类。这种方案适合快速搭骨架、写原型的时候用。三条路线之间不是互斥的。实际项目里往往入口是一个 Controller 方法但某些如 “/login” 这种纯页面跳转又用 view-controller 处理静态资源则走专门的资源映射。我强烈建议在动手之前先问自己一个问题首页需不需要在渲染前去取数据需要就老老实实走 Controller不需要就别写空方法用 view-controller 减少代码量。2. web.xml 与 spring-mvc.xml 中容易踩雷的三个配置点2.1 DispatcherServlet 的 url-pattern/与/*SSM 项目里 DispatcherServlet 的映射路径初学者最容易在这两个符号上面翻车。配置写成/意思是 “除了 JSP 之外的请求都交给 Spring MVC”。注意/不会拦截对 JSP 的直接访问JSP 文件还是由容器自带的 JspServlet 处理。这也是为什么你的 Spring MVC 能转发到/WEB-INF/views/index.jsp而不会和 JspServlet 产生冲突。配置写成/*就不一样了。/*会把所有请求都拦截下来包括后缀是.jsp的请求。一旦你用了/*Spring MVC 去转发 JSP 时这个转发请求又会被 DispatcherServlet 再拦一次最终可能抛出 “Circular view path” 一类的异常或者页面直接 500。这是老生常谈但每次帮人看项目都能遇到。正确的做法是servlet-mapping servlet-namespringMVC/servlet-name url-pattern//url-pattern /servlet-mapping/还有一个隐含的作用容器里所有没有被其他 Servlet 明确匹配的请求都会走到 DispatcherServlet。所以就算 webapp 根目录下没有 welcome-file你访问http://localhost:8080/项目名/时容器也会把请求交给 DispatcherServlet再由它去匹配 Controller。2.2 欢迎文件列表的优先级web.xml里的welcome-file-list是个很微妙的东西。我见过不少项目web.xml 里配了 welcome-file根目录也放了 index.jspController 也写了RequestMapping(/)结果访问根路径时页面上永远看不到 Controller 里往 Model 放的值。原因就是前面说的优先级问题。容器的处理顺序很死板如果请求的路径是一个目录并且应用里存在与之匹配的欢迎文件容器会先把这个欢迎文件找出来用内部转发的方式交给它处理。这个内部转发发生在 Spring MVC 之前所以你的 DispatcherServlet 根本没机会接触请求。一个典型的反面配置是这样的welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list如果 webapp 根目录下恰好也存在 index.jsp那么浏览器输入根路径看到的永远是根目录这个 JSP而不是你 Controller 方法里返回的视图。根目录的 index.jsp 不经过 Spring MVC 的拦截器也拿不到 Model 里的数据这个坑很隐蔽。处理办法有两种。要么把根目录的 index.jsp 删掉让欢迎文件机制找不到文件请求自然落到 DispatcherServlet要么利用这个根目录的 index.jsp 做一次内部转发把它当作进入 MVC 路由的跳板这个思路我在第三章的第三种实现方式里会展开讲。2.3 视图解析器的前缀后缀与 JSP 位置SSM 项目的 JSP 通常被放在/WEB-INF/views/下这个目录是受容器保护的浏览器无法直接访问但服务端可以安全地转发到里面的 JSP。Controller 返回逻辑视图名之后靠InternalResourceViewResolver拼出真实路径。bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean当 Controller 返回index时视图解析器会把逻辑名拼成/WEB-INF/views/index.jsp然后由服务端内部转发到这个 JSP。这里有个很多人不理解的细节这个内部转发发生的过程中浏览器地址栏里的 URL 并不会变化。你访问的是/项目名/页面显示的是/WEB-INF/views/index.jsp的内容但地址栏一直是/项目名/。这是服务端渲染的正常表现不是 Bug。JSP 放在 WEB-INF 下的好处是安全用户没法猜一个.jsp路径去直接访问页面所有页面都必须经过 Spring MVC 的路由逻辑统一。坏处是容器自带的欢迎文件机制不一定能直接找到 WEB-INF 下的 JSP至少不建议这样用。你最好在根目录放一个入口文件或者干脆不走欢迎文件直接把根路径交给 Controller。3. 四种可落地的 index 页面实现3.1 方式一Controller 接管根路径返回动态首页这是 SSM 项目里最正统的做法也是我最推荐的主路线。它把首页变成一个完全受 Spring MVC 管理的动态页面要带数据、要做权限判断都方便。项目结构大致是这样src/main/webapp ├── WEB-INF │ ├── web.xml │ └── views │ └── index.jsp ├── index.html可选建议删掉 └── static └── css └── jsController 里写一个最简方法Controller public class IndexController { RequestMapping(value /, method RequestMethod.GET) public String index(Model model) { model.addAttribute(appName, SSM 示例项目); model.addAttribute(currentUser, 访客); return index; } }配合视图解析器配置index最终指向/WEB-INF/views/index.jsp。web.xml 那边不需要再画蛇添足加 welcome-file或者即便加了也确保根目录没有那个同名文件。如果用了 Maven 的 war 插件脚手架里经常会自动生成一个 webapp/index.jsp或者 index.html。这种自动生成的文件是麻烦之源打包前一定要检查不然你 Controller 写得再好根路径也可能被这个文件截胡。还要提一个细节如果你继承了某个 BaseController或者项目里配置了拦截器那么对根路径/的请求也会照常经过拦截器链。首页要做什么前置校验比如判断用户有没有登录、是不是要从重定向链接带参数都可以在这个方法里处理。3.2 方式二用 mvc:view-controller 去掉空 Controller 方法有时候你真的只是想把根路径转发到一个页面数据全在页面上写死或者数据由前端 JS 去拉接口。这时写一个空 Controller 方法有点小题大做Spring MVC 提供了一个配置层面的解决方案。在 spring-mvc.xml 里增加这样一段mvc:view-controller path/ view-nameindex/它的意思很直白访问/时不走 Controller直接把逻辑视图名设置成index交给视图解析器去拼 JSP 路径。加上这行之后根目录里的 home 页面就少了一个 Java 方法。使用 view-controller 时建议同时保留mvc:annotation-driven/因为我见过只配 view-controller 不配 annotation-driven 导致静态资源映射和注解 Controller 全部失效的案例。尤其是你的项目里既有注解 Controller又有 view-controller缺少 annotation-driven 时 HandlerMapping 可能没有被正确初始化最终表现为部分路径 404。mvc:annotation-driven/ mvc:view-controller path/ view-nameindex/view-controller 适合 “无脑转发” 的场景比如/index、/login、/register这类页面。一旦首页需要从数据库取菜单或者判断登录态就回到方式一别硬用 view-controller。3.3 方式三根目录 index.jsp 内部转发到 MVC 路由你可能会遇到一个不得已的情况项目是接手别人的根目录下已经躺着一个 index.jsp大家约定俗成把它当作欢迎页你要改造成 SSM 结构但不想大动。这时可以在根目录的 index.jsp 里写一个内部转发让请求进入 Spring MVC 的路由体系。% page contentTypetext/html;charsetUTF-8 languagejava % jsp:forward page/index/注意这里的/index必须在 Controller 里有对应映射否则会继续 404。转发之后由 Controller 返回真正的视图名视图解析器再渲染 WEB-INF 下的页面。也有人用重定向% response.sendRedirect(request.getContextPath() /index); %转发和重定向的区别在于转发是服务端内部的浏览器地址栏不变一次请求搞定重定向会返回 302让浏览器再发一次请求地址栏变成/index。如果首页里有需要放进 request 作用域的数据千万别用重定向因为重定向之后 request 就丢掉了。这个方案的坏处是多了一层跳板看起来不那么优雅。但它的好处也很实际保留根目录的 index.jsp就仍然可以利用欢迎文件的机制不需要去 web.xml 里删 welcome-file对老项目改动最小。我自己的经验是这个方案一般只用来做过渡等 Controller 路由稳定之后还是会回到方式一的结构。3.4 方式四纯静态首页与静态资源映射有时首页根本不需要服务端渲染你可能引入了一个纯静态的 HTML 原型或者把 Vue、React 打包出来的文件直接放进 webapp。这时候你要处理的不只是根路径还包括 CSS、JS、图片这些静态资源在 Spring MVC 下的访问问题。首先把静态首页命名为index.html放在 webapp 根目录它会被容器当作默认欢迎文件。然后配置 Spring MVC 对静态资源放行否则 DispatcherServlet 会去 Controller 里找/css/main.css、/js/app.js这种路径结果自然是 404。spring-mvc.xml 中常见两种配置。第一种是直接交给容器默认 Servletmvc:default-servlet-handler/第二种是显式指定静态资源目录mvc:resources mapping/static/** location/static//我建议在纯静态场景下把两种都用上。default-servlet-handler让未被 Controller 匹配的路径回退到容器原始方式适合零散分布在根目录的 favicon、robots.txt 之类resources映射则更清晰适合统一管理前端打包产物。值得注意的一点是纯静态index.html虽然能作为欢迎文件被容器直接返回但如果你在 web.xml 里配置了 Spring 的字符编码过滤器而且过滤器的url-pattern配的是/*那么对index.html的访问也会经过过滤器这在绝大多数情况下是好事保证页面内容不乱码。4. 实操中的问题排查和个人习惯4.1 访问根路径 404 的排查顺序首页 404 是最常见的问题我把排查顺序固定成下面这套能节省大量时间。先看访问根路径时Tomcat 控制台有没有输出 Spring MVC 的日志。如果完全没有日志说明请求根本没进 DispatcherServlet优先查 web.xml 的 servlet-mapping再看根目录下是不是有 index.jsp 或 index.html 截胡了。如果日志显示进入 Spring MVC 但还是 404再检查RequestMapping的 value 是不是写的/index而不是/或者 Controller 包名有没有被 component-scan 扫到。context:component-scan base-packagecom.example.demo.controller/这个包名如果写错一个字母Controller 就静默不生效Spring 不会报错页面就是 404。我帮人排查时发现很多所谓的 “首页无法配置” 其实是这段扫描路径写错了。4.2 静态资源被拦截与 500页面能打开了但 CSS、JS 全部 404这个排查起来快一些。DispatcherServlet 配了/之后所有静态文件请求都先经过 Spring MVC如果你没有配资源映射自然全部 404。在 spring-mvc.xml 里补上mvc:resources mapping/static/** location/static//还有个容易混淆的点JSP 页面渲染时报 “Circular view path”或者 500 错误里提示找不到视图。这种问题通常是把逻辑视图名写成了物理路径比如 Controller 里返回/WEB-INF/views/index.jsp而视图解析器又把前缀后缀拼了一遍结果变成/WEB-INF/views//WEB-INF/views/index.jsp.jsp。Controller 只需要返回逻辑名index物理拼装交给视图解析器。4.3 编码、路径、依赖三件套静态页面中文乱码JSP 页面中文也乱码或者提交表单之后中文成问号这些在 SSM 项目里基本是同一个问题。这里我展开说下编码过滤器。它虽然和 index 页面配置没有 100% 直接关系但它是所有页面能正确显示中文的前提。最常见的做法是在 web.xml 里配置一个 Spring 自带的编码过滤器filter filter-nameencodingFilter/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-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingforceEncoding设为true很关键它既强制了请求编码也强制了响应编码不然 JSP 页面头声明了UTF-8但响应头还是 ISO-8859-1 一样会乱。然后用UTF-8的前提是页面文件本身必须是 UTF-8 保存。Maven 项目里spring-webmvc 依赖缺失也会造成启动就报 ClassNotFoundorg.springframework.web.servlet.DispatcherServlet。不要只在本地 IDE 里能跑就以为没事打成 war 包放到独立 Tomcat 时才会暴露依赖问题。运行老项目时Tomcat 9 之后对 JSP 和 EL 的版本要求更严格如果 JSP 页面里用到 JSTL 标签库务必加上javax.servlet.jsp.jstl:jstl依赖否则首页渲染时经常抛 “Unable to find taglib” 的异常表现为整个页面 500。这类问题最坑因为问题不见得是首页路由配错而是页面本身依赖的标签库没带全。4.4 首页方案速查表项目接手多了之后我习惯把首页方案浓缩成一张对照表遇到新工程先在表里定位自己属于哪类再动手配置。场景推荐方案关键配置备注首页需要动态数据、登录态、权限菜单方式一Controller 接管/web.xml配/spring-mvc.xml 配视图解析器根目录不要放同名 index.jsp只是跳转到某个页面无业务逻辑方式二mvc:view-controllerview-controller path/ view-nameindex同时保留 annotation-driven老项目保留根索引页改造 SSM方式三根 index.jsp 内部转发根 JSP 写jsp:forward过渡期用不建议长期保留纯前端静态首页页面不渲染数据方式四静态首页 资源映射welcome-filemvc:resources记得配 default-servlet-handler最后分享一个我一直在用的习惯各种配置方式讲完还是要回到工程实践。我自己的习惯是新项目从一开始就明确 “首页是动态的”所以 web.xml 里不写 welcome-file-list根目录下也不让脚手架生成任何 index.jsp 或 index.html根路径/由 Controller 统一接管。这样请求链路是单线的不存在容器欢迎文件机制和 Spring MVC 抢请求的问题。页面需要静态资源时用mvc:resources单独把/static/**映射出来跟首页路由互不干扰。如果你只是想让项目先跑起来看效果建议直接用mvc:view-controller配一个首页跳转等后面需要动态数据了再把view-controller换成 Controller 方法改动量也不大。
返回列表