SpringMVC图书管理系统核心机制:请求流转、RESTful与拦截器实战
简介Spring MVC 是 Java Web 开发中应用广泛的 MVC 框架以 DispatcherServlet 为核心统一处理请求分发与视图解析。理解其工作原理是掌握 SSM 项目的基础也是面试中高频考查点。通过将资源抽象为 RESTful 风格的 URL并使用拦截器实现登录校验可以显著提高接口的语义化与安全性。在图书管理系统这类典型业务场景中借阅、归还、检索等操作天然适合资源化建模代码清晰且易于扩展。围绕 SpringMVC 图书管理系统的分层设计、核心配置与拦截器落地可有效串联框架的请求流转链路帮助开发者从增删改查的层面提升到框架设计思维。1. 基于 springMVC 的图书管理系统不是增删改查那么简单拿到这个题目多数人第一反应是建一张 book 表写几个 JSP 把数据查出来。真正算得上 springMVC 的设计重点看 DispatcherServlet 是否接管请求、Controller 是否暴露 RESTful 风格资源接口、springmvc 拦截器是否在进入业务前完成登录校验。这套系统刚好把 springMVC 核心机制压缩在一个可运行场景里图书是资源借阅、归还、检索对应不同 URL 与 HTTP 方法权限拦截在 Controller 之前做掉。适合正在做基于 Java 的图书管理系统毕业设计的人也适合准备 Spring 面试、想拿一条真实链路讲原理的人。下文从请求入口讲到代码落地每层都给出能改的参数和可复现的配置照着敲就能跑而不是停在看别人解决过的层次。2. springMVC 图书管理系统的请求流转与分层设计2.1 DispatcherServlet 接管请求的前置条件springMVC 的入口是 DispatcherServlet它把匹配到的请求收进来再交给 HandlerMapping、HandlerAdapter、ViewResolver 处理。很多基于 java 的图书管理系统毕业设计项目还在用*.do的旧映射控制器方法里大量出现/book/list.do这种半吊子 URLrestful 风格根本无从谈起。这里我一般会直接用/作为 servlet-mapping让所有非静态请求都走 DispatcherServlet。servlet servlet-namebookMvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namebookMvc/servlet-name url-pattern//url-pattern /servlet-mapping关键参数就三个。servlet-name决定默认配置文件路径如果省略init-param容器会去找/WEB-INF/bookMvc-servlet.xmlcontextConfigLocation显式把配置文件指到类路径便于和 root 上下文分开管理load-on-startup填 1保证 Tomcat 启动时就初始化 DispatcherServlet而不是第一个请求到达时才初始化。把url-pattern写成/之后/static/css/app.css这类请求也会进入 DispatcherServletController 里没有对应映射就返回 404。常见处理是在 spring-mvc.xml 里配mvc:resources把静态目录交给资源处理器这一点在 2.2 的配置里一起放出来。2.2 spring-mvc.xml 中的组件扫描、注解驱动与视图解析配置文件是 springMVC 运行的核心。下面这份最小可用配置同时完成三件事扫描 Controller、开启注解驱动、把逻辑视图名拼成/WEB-INF/views/下的 JSP。之所以把 JSP 放在/WEB-INF/views/是因为 WEB-INF 下的文件不能通过浏览器 URL 直接访问用户只能经过 Controller 转发进入页面这本身就是基于 web 的图书管理系统最基本的安全边界。context:component-scan base-packagecom.book.controller/ mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static//component-scan只扫com.book.controller是我在 springMVC 项目里偏好的做法。Service 和 DAO 交给 root 容器DispatcherServlet 只管 Controller事务、切面这些容器职责不混在一起。如果你打算只用一个上下文把base-package改成com.book也能跑但后续加Transactional时要确认事务注解所在包确实被扫描到否则 Service 上的事务会静默失效。mvc:annotation-driven负责注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter。少了它GetMapping、PostMapping、PathVariable、ResponseBody这些注解全部不生效。这种配置缺失的典型表现是页面访问 404 而不是 500因为 URL 映射根本没有建立。InternalResourceViewResolver的prefix、suffix是配套出现Controller 里return book/list解析器就拼成/WEB-INF/views/book/list.jsp。2.3 表现层、业务层、持久层的边界图书管理系统的数据流并不复杂页面提交表单后请求落到 ControllerService 校验库存、处理借阅纪录DAO 执行 SQL最终数据封装成 Book 对象回传。把三层边界画清楚不只是应付答辩更重要的是后续加功能时不至于把 SQL 写进 JSP。层核心类型包职责表现层BookControllercom.book.controller接收参数、绑定模型、跳转视图、轻量校验业务层BookService / BookServiceImplcom.book.service借还库存、ISBN 查重、事务边界持久层BookDao / BookDaoImplcom.book.daoJDBC 或 MyBatis 访问、结果集映射、分页三层之间通过接口依赖不要在 Controller 里 new Service。用Autowired注入接口对象Spring 启动时找到实现类完成装配。这样如果想把 DAO 从 JdbcTemplate 换成 MyBatis只替换第三层实现Controller 一行都不改。这个边界意识比多敲几百行代码更能说明设计能力。另一个实际边界是实体字段命名。Book 的字段我建议和表单字段保持统一bookId、isbn、title、author、stock、location前后端字段名对齐后springMVC 的参数绑定能自动完成Book book作为方法参数时按表单字段名逐一赋值。字段不一致是这个项目里最常见的隐性 bug比如页面上写book_id实体里叫bookId绑定后一查全是 null。统一命名比写一堆 set 方法省事得多。3. 用 RESTful 风格把图书管理系统的增删改查做成资源操作3.1 Book 实体与数据表的字段映射RESTful 风格的第一步是把图书看作一个book资源所有操作都围绕/books这个 URL 展开。实体类保持轻量只保留字段和 getter/setter。数据库表名我用book主键book_id自增业务字段包括isbn、title、author、stock、location。ISBN 要加唯一索引同一本书在系统里不允许重复登记。public class Book { private Integer bookId; private String isbn; private String title; private String author; private Integer stock; private String location; public Integer getBookId() { return bookId; } public void setBookId(Integer bookId) { this.bookId bookId; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author author; } public Integer getStock() { return stock; } public void setStock(Integer stock) { this.stock stock; } public String getLocation() { return location; } public void setLocation(String location) { this.location location; } }字段类型上有两个容易踩的坑。stock用Integer而不是int因为数据库记录可能为 null包装类型能区分「未初始化」和「0 本」isbn用String而不是数值型ISBN 可以包含X而且在 Java 里用Long存会丢前导零。这些细节在答辩时被问到往往比功能本身更能说明基本功。3.2 Controller 中基于 HTTP 方法的资源映射springMVC 从 4.3 开始提供GetMapping、PostMapping、PutMapping、DeleteMapping组合注解内部本质是RequestMapping加method限定。RESTful 风格的核心理念是 URL 只描述资源、HTTP 方法描述动作GET 查、POST 增、PUT 改、DELETE 删。下面这套映射是图书管理系统的常规做法。Controller RequestMapping(/books) public class BookController { Autowired private BookService bookService; GetMapping public String list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, Model model) { model.addAttribute(page, bookService.list(page, size)); return book/list; } GetMapping(/{bookId}) public String detail(PathVariable Integer bookId, Model model) { model.addAttribute(book, bookService.getById(bookId)); return book/detail; } PostMapping public String create(Book book) { bookService.add(book); return redirect:/books; } PutMapping(/{bookId}) public String update(PathVariable Integer bookId, Book book) { book.setBookId(bookId); bookService.update(book); return redirect:/books/ bookId; } DeleteMapping(/{bookId}) ResponseBody public MapString, Object delete(PathVariable Integer bookId) { bookService.delete(bookId); MapString, Object result new HashMap(); result.put(success, true); return result; } }RequestParam(defaultValue 1)处理分页参数用户不传page时默认走第一页。PathVariable从 URL 路径里取bookId/books/3这种地址天然具备可读性也符合搜索引擎和接口调试工具的预期。ResponseBody加在删除方法上是因为删除后要返回 JSON 给前端判断而不是跳转页面。返回的Map会被 Jackson 自动序列化前提是 spring-mvc.xml 里存在mvc:annotation-driven。HTTP 方法URL控制器方法作用GET/bookslist分页查询图书GET/books/{bookId}detail查看单本详情POST/bookscreate新增图书PUT/books/{bookId}update修改图书DELETE/books/{bookId}delete删除图书3.3 表单提交 PUT/DELETE 的隐藏方法改造浏览器表单只支持 GET 和 POST这是 HTML 规范的限制。要让表单发出 PUT、DELETE 请求springMVC 提供了HiddenHttpMethodFilter表单里带一个_method隐藏域过滤器在请求进入 DispatcherServlet 之前把 POST 请求包装成指定的 PUT 或 DELETE。filter filter-namehiddenHttpMethodFilter/filter-name filter-classorg.springframework.web.filter.HiddenHttpMethodFilter/filter-class /filter filter-mapping filter-namehiddenHttpMethodFilter/filter-name url-pattern/*/url-pattern /filter-mapping修改图书的表单写成这样form action${pageContext.request.contextPath}/books/${book.bookId} methodpost input typehidden name_method valueput/ input typehidden namebookId value${book.bookId}/ input typetext nametitle value${book.title}/ input typetext nameauthor value${book.author}/ input typenumber namestock value${book.stock}/ button typesubmit保存/button /form注意_method的 value 必须小写put过滤器只认小写枚举。bookId也必须放回表单否则setBookId(bookId)补偿赋值虽然做了但其他字段绑定后book对象里可能只剩部分字段。更稳妥的做法是在更新方法里先查一次原记录再把非空字段覆盖上去避免把库存误更新成 null。提示HiddenHttpMethodFilter要放在字符编码过滤器之后。如果编码过滤器先处理了参数再被隐藏方法过滤器包装POST 里的中文在 PUT 请求里可能有乱码风险。4. springmvc 拦截器做图书管理系统的登录校验4.1 为什么把登录校验放在拦截器里图书管理系统的借阅、归还、删除操作不能让未登录用户直接调用。最容易想到的做法是每个方法开头写一段 session 判断但十几个方法重复粘贴改一处漏一处。springmvc 拦截器HandlerInterceptor正好解决这类横切逻辑它在请求进入 Controller 之前执行统一的登录校验放这里业务代码保持干净。拦截器和 Filter 的位置不同。Filter 在 Servlet 容器层面工作Controller 还没被创建拦截器在 DispatcherServlet 分发请求时执行能拿到HandlerMethod知道这次请求要调用哪个方法。对图书管理系统来说拦截器更贴近业务场景因为可以在拦截器里针对特定 Controller 或特定方法做细粒度放行。4.2 实现 LoginInterceptor 并理解 preHandle 返回值HandlerInterceptor接口有三个方法preHandle在 Controller 方法执行前调用postHandle在方法执行后、视图渲染前调用afterCompletion在整个请求结束后调用。登录校验只需要preHandle返回 false 表示请求到此为止不再继续调用后续拦截器和 Controller。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser request.getSession().getAttribute(loginUser); if (loginUser ! null) { return true; } String requestUri request.getRequestURI(); String contextPath request.getContextPath(); if (requestUri.equals(contextPath /) || requestUri.equals(contextPath /login)) { return true; } response.sendRedirect(request.getContextPath() /login); return false; } }判断逻辑分三步。先查 session 里有没有loginUser有就放行没有再看请求是否访问登录页或首页这些地址要白名单最后其余请求全部重定向到登录页。实际项目里不要把密码明文放在 session 里存放 userId 和昵称即可。这里有个细节preHandle返回 false 之后postHandle和afterCompletion都不会执行因为请求链已经被打断。如果拦截器里做了资源初始化需要在返回 false 之前手动清理否则容易造成请求线程的上下文污染。4.3 spring-mvc.xml 中拦截路径与白名单配置拦截器写完之后要在 spring 容器里注册。springMVC 通过mvc:interceptors配置核心是mvc:mapping与mvc:exclude-mapping的路径匹配规则。mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/fonts/**/ bean classcom.book.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors配置项匹配路径作用mvc:mapping path/**所有请求路径默认全部拦截mvc:exclude-mapping path/login/login放行登录页和登录提交mvc:exclude-mapping path/static/**/static/ 下所有文件放行静态资源/**匹配所有层级/static/**匹配/static/下任意深度路径。这里容易踩的坑是mvc:exclude-mapping只对已经进入 DispatcherServlet 的请求生效如果静态资源没有配mvc:resources它们根本到不了拦截器这层就被 404 了。所以 2.2 的mvc:resources mapping/static/**和这里的 exclude 要同时存在。提示多个拦截器按声明顺序执行preHandle按顺序进入postHandle按逆序返回。如果后续加了权限拦截器要把登录拦截器配在前面否则用户还没登录就去核对权限逻辑上会多一层无意义的判断。5. 基于 web 的图书管理系统部署前的三个验证技巧5.1 用 curl 验证 RESTful 接口页面点几下只能证明页面能用证明不了接口语义正确。部署到 Tomcat 后用 curl 直接打接口是最快的验收方式。先登录拿 session再依次验证增删改查。curl -i -X POST http://localhost:8080/book/books \ -d isbn9787111213826titleSpring%E5%AE%9E%E6%88%98authorCraig%20Wallsstock5 curl -i -X PUT http://localhost:8080/book/books/1 \ -d _methodputbookId1isbn9787111213826titleSpring%E5%AE%9E%E6%88%98%EF%BC%88%E7%AC%AC5%E7%89%88%EF%BC%89authorCraig%20Wallsstock3 curl -i -X DELETE http://localhost:8080/book/books/1-i显示响应头能直接看到 302 还是 200。-X POST显式指定方法表单测试时 URL 里的中文要 URL 编码否则 Tomcat 可能返回 400。PUT 请求要带_methodput这是 HiddenHttpMethodFilter 的约定。如果 DELETE 返回 302 重定向到登录页说明拦截器生效但当前 session 没有登录态需要用-b cookies.txt -c cookies.txt维持 cookie 再测。5.2 静态资源 404 与中文乱码的定位顺序部署后最常见的两个问题CSS 全丢、中文数据乱码。CSS 全丢先看 spring-mvc.xml 里有没有mvc:resources mapping/static/**再看 JSP 页面引入地址是不是${pageContext.request.contextPath}/static/css/app.css。乱码按顺序排查JSP 页面头加pageEncodingUTF-8web.xml 里注册CharacterEncodingFilter且forceEncoding为 true数据库 JDBC URL 追加characterEncodingutf-8。三处只要断一处数据就会出现问号。5.3 把借阅动作也纳入资源设计图书管理系统做到最后借阅记录往往被处理成一张孤立的表跟book、user没有清晰的资源关系。进阶做法是把借阅也建模成资源/loans表示借阅记录集合借书是POST /loans还书是DELETE /loans/{loanId}查询某本书当前借给谁是GET /loans?bookId1。这样设计之后springMVC 的拦截器还能扩展到「只允许管理员删除图书」的权限控制而 Controller 本身的代码结构几乎不用变。RESTful 风格真正发挥作用的地方就是业务扩展时 URL 和职责都保持稳定。本文还有配套的精品资源点击获取