ARTICLE DETAIL

资讯详情

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

JSP+Servlet+Tomcat电子图书管理系统设计与部署实战指南

JSP+Servlet+Tomcat电子图书管理系统设计与部署实战指南 简介这份源码是一套基于JSP、Servlet和Tomcat的电子图书管理系统定位清晰面向Java Web初学者、高校课程设计或小组合作项目。系统以电子图书的资源管理为主线覆盖了图书列表展示、详情查看、用户注册登录、图书搜索和借阅管理等完整功能链条后端用Java源文件处理业务与请求分发前端借助JSP页面和Bootstrap完成动态内容输出与响应式界面呈现。资源包共含50个文件核心组成部分包括22个Java源代码、11个XML配置文件和8个JSP页面另外还有CSS、JavaScript、jar依赖、Git忽略规则、说明文档及项目配置等辅助文件压缩包大小2.77MB整体目录层级清楚适合按模块边看边练。目前已有123人学习下载。通过通读这套源码可以清楚把握Servlet如何拦截请求并调用逻辑、JSP如何生成动态页面、Tomcat如何部署并运行整个Web应用也能学习到Web项目常见的分层结构与资源配置方法无论是用作课程设计的进阶改造、毕业设计的起步框架还是日常练手的完整范例都有不错的参考意义。1. 别急着喊老古董这套“JSPServletTomcat”的电子图书管理系统在毕设与企业遗留系统里依然站得住脚如果你是冲着“基于JSPServletTomcat的电子图书管理系统设计源码”来的多半是三种情况之一要交毕设/课程设计接手了一台老板死活要留着的服务器或者想搞懂 JavaWeb 请求链路底层的运行逻辑。这个组合在 2024 年依然存在它不是一个能“一键跑通”的花架子也没有 Spring Boot 那种自动配置的魔法。但电子图书管理这种典型的“列表 详情 表单 借还状态”应用靠它完全能落地。它适合想用最小依赖把系统跑起来的人也适合想理解 Servlet 容器、Filter、Session 这些底层概念的人。往下看你会得到一个能直接照做的路径。2. 先拆底牌JSP、Servlet、Tomcat 三者怎么分工一次请求怎么走完全程2.1 三个组件各管一段谁也别越界很多从 Spring Boot 转过来的人第一反应是“JSP 不是被淘汰了吗”。淘汰不淘汰先放一边先把分工说清楚。JSP 是服务端模板引擎负责把数据渲染成 HTMLServlet 是控制器负责接收请求、解析参数、调用业务、控制跳转Tomcat 是 Servlet 容器负责监听端口、管理 Servlet 生命周期、把请求分发到对应的 Web 应用。三者的物理落点也很固定JSP 放在 webapp 目录下Servlet 是继承了javax.servlet.http.HttpServlet的 Java 类Tomcat 则通过conf/server.xml和webapps目录管理应用。对应关系如下表。组件职责典型落点JSP服务端渲染 HTML展示数据webapp/book/list.jspServlet接收 HTTP 请求处理参数调 DAOforward 到 JSPcom.xxx.servlet.BookListServletTomcatServlet 容器管理生命周期与线程池处理网络连接conf/server.xmlwebapps/elibrary/一次请求的完整链路是这样的浏览器发起GET /elibrary/book/list?page2Tomcat 的 Connector 在 8080 端口接到请求先匹配 Host再匹配 Context。Context 就是你的应用通常对应部署目录名elibrary。接着容器在 web.xml 或注解里查找能处理/book/list的 Servlet找到后进入 Filter 链再调用service()方法。Tomcat 会根据 HTTP 方法把请求分发给doGet()或doPost()。Servlet 里调 DAO 查数据库把结果放进 request 作用域最后通过RequestDispatcher.forward()把请求转发给 JSP。JSP 在容器里被翻译成 Servlet 源码再编译成 class最终输出 HTML 响应回浏览器。这里要说一下 Servlet 生命周期因为这是面试和排查问题的核心。Servlet 实例在第一次请求时被创建容器调用init()初始化一次之后每个请求由新线程执行service()但都是同一个 Servlet 实例容器关闭时调用destroy()。这意味着 Servlet 里的共享成员变量要极其小心不要在里面存可变的用户状态否则会串数据。这不是玄学是线程安全问题。2.2 电子图书管理系统是标准 CRUD服务端渲染天然合适这套系统的需求很典型图书的增删改查、借阅归还、用户登录、分类筛选、分页列表。它的前端交互强度很低没有复杂的图表联动没有实时推送核心就是表单提交和列表渲染。这种需求用服务端渲染是最省力的。拿“图书搜索”来说JSP Servlet 的做法是列表页提交查询条件到BookListServletServlet 读取 keyword 参数传给 DAO 拼接 SQL再把结果放到 request 里 forward 回原页面。整个过程两个 Java 文件加一个 JSP 就搞定了。如果换成 Vue Spring Boot 的前后端分离你得额外设计 REST API、处理跨域、配 Token、写 axios 封装一个人做毕设或小企业内部工具纯属给自己加戏。对比一下常见方案方便你决定要不要沿用这套。方案上手门槛单兵作战效率适合场景JSP Servlet Tomcat低链路透明高代码直白毕设、老系统维护、轻交互 SSR 应用Spring Boot Thymeleaf中自动配置多中排查依赖较累新项目需要 Spring 生态Spring Boot Vue 前后端分离高双端联调低一个人扛两座山多人协作、复杂交互、移动端复用我不否认前后端分离是主流但它不是唯一正确的形态。对电子图书管理系统这类以展示和表单为主的系统JSP Servlet 完全够用而且出了问题好查好改符合“源码拿到手能快速看懂”的需求。2.3 拿到这套源码优先看这三个落点读源码不要从头读到尾先看三个关键位置。第一是web.xml或注解里的 URL 映射这决定了“哪个地址对应哪个 Servlet”第二是WEB-INF/lib下放了哪些 jar重点看有没有 MySQL 驱动和 JSTL第三是webapp目录的 JSP 组织方式是直接在根目录还是按模块分子目录。这三处看明白整个项目的骨架就清楚了。很多老源码是把 Servlet 映射写在web.xml里的新一点的代码会用WebServlet注解。两者效果一样但注意不能对同一个 URL 同时用两种方式映射否则 Tomcat 启动会报Servlet mapping specifies an unknown servlet name。看源码时留意这一点改代码时也要保持一致。3. 把核心源码落地三张表、一个 Servlet、一个 JSP跑通最小闭环3.1 先建表书、人、借还记录三张表把业务闭环电子图书管理系统最核心的数据库表就三张图书表、用户表、借阅表。建表脚本直接照下面跑注意统一用 utf8mb4 字符集MySQL 5.7 和 8.0 都支持。CREATE TABLE book ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, isbn VARCHAR(32) NOT NULL COMMENT 国际标准书号, title VARCHAR(255) NOT NULL COMMENT 书名, author VARCHAR(100) DEFAULT COMMENT 作者, publisher VARCHAR(100) DEFAULT COMMENT 出版社, category VARCHAR(50) DEFAULT 未分类 COMMENT 分类, total INT NOT NULL DEFAULT 1 COMMENT 馆藏总量, available INT NOT NULL DEFAULT 1 COMMENT 当前可借数量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category (category), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码建议存加盐哈希, real_name VARCHAR(50) DEFAULT COMMENT 姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 0-读者 1-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE borrow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, book_id BIGINT NOT NULL, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP, return_date DATETIME DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-借出 1-已还, KEY idx_user (user_id), KEY idx_book (book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借阅记录表;表设计里有几个细节要注意。图书表里我故意拆了total和available两个字段而不是还书时去 borrow 表统计原因是这样查询“可借数量”不用做关联聚合列表页性能好逻辑也直观。借阅表不直接删记录而是用 status 标记已还方便后面统计借阅历史。这里不建议在表上建物理外键。老系统用逻辑外键就够了物理外键在插入借阅记录时会多一次约束检查而且后面如果要分库分表外键是第一个要拆掉的东西。删除和更新靠代码层保证一致性这就是上面说的“逻辑外键代替物理外键”。接下来是 JDBC 工具类这是老源码里必然有的一个类。注意 URL 参数的写法。public class DBUtil { static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { String url jdbc:mysql://localhost:3306/elibrary ?useUnicodetruecharacterEncodingUTF-8 useSSLfalseserverTimezoneAsia/Shanghai; return DriverManager.getConnection(url, root, 你的密码); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这段代码里的参数逐个说。useSSLfalse是关闭 MySQL 连接时的 SSL 握手避免本地开发时满屏警告serverTimezoneAsia/Shanghai解决 MySQL 8.0 时区报错characterEncodingUTF-8保证从数据库读写中文不乱码。close()方法里的空判断不能省否则前面某个资源创建失败后面 close 会抛空指针反而把真正的异常盖掉了。3.2 图书列表 Servlet这套写法的标准骨架写一个BookListServlet它是整个系统里最典型的控制器。先读分页参数再查总数算页数最后查当前页数据放进 request 转发给 JSP。WebServlet(/book/list) public class BookListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); // 分页参数默认第 1 页非法参数直接回退到 1 int page 1; String pageParam req.getParameter(page); if (pageParam ! null pageParam.matches(\\d)) { page Integer.parseInt(pageParam); } // 搜索关键字去掉首尾空格 String keyword req.getParameter(keyword); if (keyword ! null) { keyword keyword.trim(); } BookDao dao new BookDao(); int pageSize 10; int total dao.count(keyword); int totalPages (total pageSize - 1) / pageSize; // 向上取整 ListBook books dao.findPage(keyword, page, pageSize); req.setAttribute(books, books); req.setAttribute(currentPage, page); req.setAttribute(totalPages, totalPages); req.getRequestDispatcher(/book/list.jsp).forward(req, resp); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }这里有几个参数和写法必须讲清楚。pageParam.matches(\\d)这一步很多入门代码会省直接Integer.parseInt(pageParam)。一旦 URL 写成?pageabcTomcat 会抛NumberFormatException用户看到的就是 500 页面。正则校验虽然多写一行但能挡住绝大多数手误。req.setCharacterEncoding(UTF-8)只对 POST 请求的 body 生效对 GET 的 query string 无效。GET 的中文参数编码由 Tomcat 连接器的URIEncoding控制这部分在第 4 章讲。这里先知道这个边界就行。为什么用forward而不是sendRedirectforward 是服务端内部跳转同一个请求范围内setAttribute的数据在 JSP 里能取到sendRedirect 会让浏览器再发一次新请求原来的 request 对象就丢了数据拿不到而且多一次网络往返。列表页这种场景必须用 forward。3.3 JSP 页面用 EL 和 JSTL不要在页面里写 Java 脚本片段JSP 页面里最忌讳的是% %脚本片段。老源码里经常能看到一整页 Java 代码嵌在 HTML 里查询逻辑、循环、if 全在页面上。这种代码不是不能跑而是改一处要翻半天且脚本片段里声明的变量在多个 taglib 之间相互污染。更规范的做法是用 EL 表达式取数据用 JSTL 标签做循环和条件判断。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table border1 cellpadding8 tr th书名/thth作者/thth可借数量/thth操作/th /tr c:forEach items${books} varb tr td${b.title}/td td${b.author}/td td${b.available} / ${b.total}/td td a hrefc:url value/book/borrow c:param nameid value${b.id}/ /c:url借阅/a /td /tr /c:forEach /table div c:if test${currentPage 1} a href?page${currentPage - 1}上一页/a /c:if 第 ${currentPage} / ${totalPages} 页 c:if test${currentPage totalPages} a href?page${currentPage 1}下一页/a /c:if /div /body /html${b.title}这种 EL 表达式实际调用的是 Book 对象的getTitle()方法。所以 Book 这个 POJO 类的属性名要和数据库字段名对上并且提供 getter 方法否则页面上取不到值。借阅链接我特意用了c:url包了一层它会自动对 URL 参数做编码。这在后面的避坑章节会提到如果直接写href/book/borrow?id${b.id}看起来没区别但一旦 id 变成中文字段或带特殊字符Tomcat 的严格 URI 解析就会把请求打回 400。还要注意 JSTL 的uri是http://java.sun.com/jsp/jstl/core这个地址在 Tomcat 9 里不用改。但如果你用的是 Tomcat 10 及以上老 JSTL 1.2 的 jar 可能不兼容得换成jakarta.servlet.jsp.jstl的版本否则页面直接报 500。3.4 登录拦截和借阅事务两个必须做对的细节图书管理系统里登录拦截是通用的。如果你不想让未登录用户直接访问/book/list写一个 Filter 是最干净的方案。WebFilter(/book/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest hreq (HttpServletRequest) req; HttpServletResponse hresp (HttpServletResponse) resp; HttpSession session hreq.getSession(false); if (session null || session.getAttribute(userId) null) { hresp.sendRedirect(hreq.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }注意getSession(false)这个写法。getSession()不带参数时如果当前没有 session容器会创建一个新的空 session。而getSession(false)不会创建取不到就返回 null。在拦截器里如果用了getSession()未登录用户也会被强制创建 session等于每个匿名请求都往服务器内存里塞了一个无用对象浪费不说还可能让后面的逻辑误判。借书逻辑是整个系统里唯一涉及事务的地方。一次借书操作要同时做三件事检查可借数量、插入借阅记录、扣减 available。这三步必须在一个数据库事务里否则中间一步失败数据就不一致了。老系统里最常见的翻车方式是三个操作分别调 DAO 的三个方法每个方法各自开连接关连接事务完全断开。正确的传法是把业务方法写成一个整体手动控制事务边界public boolean borrow(int userId, int bookId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 锁定图书行防止并发借阅同一本 boolean ok bookDao.checkAndLock(conn, bookId); if (!ok) { conn.rollback(); return false; } // 2. 插入借阅记录 borrowDao.insert(conn, userId, bookId); // 3. 扣减可借数量 bookDao.decreaseAvailable(conn, bookId); conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} return false; } finally { DBUtil.close(conn, null, null); } }这里的关键点是三个 DAO 方法都接收同一个Connection参数不再自己开连接。checkAndLock里用SELECT ... FOR UPDATE锁定那一行避免两个用户同时借同一本书时都查到 available 大于 0然后都执行扣减最后变成负数。小系统里很多人会忽略这一步觉得并发量小无所谓。但图书管理这种场景同一本热门书被两个人同时点借阅是完全可能发生的。如果你不想在答辩时被问到“并发下怎么保证数据正确”而答不出来这段事务代码建议保留。4. 部署与配置 Tomcat版本选择、war 打包、连接器参数与 Linux 自启动4.1 版本匹配是第一道坎Tomcat 9 和 Tomcat 10 不是同一个世界拿到源码第一件事不是改代码而是确认 Tomcat 版本和 JDK 版本匹配。很多老项目是照着 JDK 8 Tomcat 8.5 或 9 写的代码里的 import 都是javax.servlet.*。如果你电脑上装的是 Tomcat 10跑这种老源码大概率直接挂因为从 Tomcat 10 开始Servlet 规范从 Java EE 迁移到了 Jakarta EE包名从javax.*改成了jakarta.*老代码根本编译不过。版本对照表如下。Tomcat 版本Servlet 规范包名前缀推荐 JDK针对老 JSP 项目的结论Tomcat 8.5Servlet 3.1javax.*JDK 7兼容性好资料多适合老源码Tomcat 9Servlet 4.0javax.*JDK 8兼顾新特性与兼容性最推荐Tomcat 10.1Servlet 6.0jakarta.*JDK 11老代码需改包名不建议我的建议是直接用 Tomcat 9 JDK 8。JDK 8 虽然老了但它依然是老源码兼容性最好的版本而且很多 Tomcat 优化参数在 JDK 8 下的资料最全。如果你本机装了 JDK 17也可以跑 Tomcat 9但要注意编译级别必须低于等于 17别用高版本语法写代码否则运行时UnsupportedClassVersionError会教做人。4.2 打包 war从 IDEA 到 Linux 服务器的标准姿势传统 JSP 项目有两种打包方式。如果源码是 Maven 结构直接在项目根目录执行mvn clean package -DskipTests打包完成后war 文件在target/目录下。如果源码是非 Maven 的普通 Web 项目用 IDEA 的 Artifacts 功能导出或者直接复制整个 Web 目录内容。不管是哪种方式最后拿到的 war 包内部结构必须长这样elibrary.war ├── book/ │ └── list.jsp ├── login.jsp ├── index.jsp └── WEB-INF/ ├── web.xml ├── classes/ │ └── com/xxx/... └── lib/ ├── mysql-connector-java-8.0.x.jar └── jstl-1.2.jarWEB-INF目录是 Tomcat 识别 Web 应用的关键classes 和 lib 必须放在它下面否则类加载不到。JSP 如果放到了WEB-INF里面浏览器不能直接访问必须通过 Servlet forward 或 redirect 出去这是一个刻意的安全边界。部署到 Tomcat 有两种常见方式。第一种是把 war 包直接丢进tomcat/webapps/目录启动时 Tomcat 会自动解压并部署适合交付和发布。第二种是在tomcat/conf/Catalina/localhost/下写一个 XML 文件指定应用路径适合把应用放在 webapps 之外的磁盘位置。!-- conf/Catalina/localhost/elibrary.xml -- Context docBase/data/projects/elibrary /这个 XML 的文件名就是你的应用上下文路径。比如文件名叫elibrary.xml访问地址就是http://ip:8080/elibrary/。第二种方式的好处是应用代码不在 webapps 目录下升级时直接替换指定目录的内容就完成部署不用反复拷贝 war 包。4.3 连接器和 JVM 参数三个必调项Tomcat 的conf/server.xml里有一个 Connector 节点它是所有 HTTP 请求的入口。对于中文系统这个节点必须做三处调整。Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 useBodyEncodingForURItrue relaxedQueryChars[]|{}^\ maxPostSize0 /逐项解释。URIEncodingUTF-8告诉 Tomcat 用 UTF-8 解码 GET 请求的 URL 参数这是解决中文搜索乱码的根本手段。useBodyEncodingForURItrue表示如果请求 body 有编码设置URI 参数也用同样的编码在设置了request.setCharacterEncoding(UTF-8)的 Filter 下GET 和 POST 就统一了。relaxedQueryChars是给那些无法控制的特殊字符兜底用的后面避坑章节会展开讲。maxPostSize0表示不限制 POST 表单大小如果你的图书系统要上传封面图这个参数必须改否则默认 2MB 的限制会让大图上传直接失败。JVM 内存参数在bin/catalina.shLinux/macOS或catalina.batWindows的CATALINA_OPTS里设置CATALINA_OPTS-Xms256m -Xmx1024m -XX:MaxMetaspaceSize256m -Dfile.encodingUTF-8这里重点说-Dfile.encodingUTF-8。如果不加在 Linux 服务器上 Tomcat 会继承系统的默认编码很多 CentOS 7 的默认编码是 POSIX 或 UTF-8 的变体一旦为 GBKJSP 页面读取 properties 文件或写入日志时中文就乱了。这个参数加进去等于给整个 JVM 定死了编码基调。4.4 Linux 部署systemd 配置自启动nginx 只做代理不解析 JSP热搜词里“linux 设置 tomcat 自启动”和“nginx 支持 jsp 吗”是高频问题。先说自启动在 Linux 上用 systemd 托管 Tomcat 是最干净的做法。# /etc/systemd/system/tomcat.service [Unit] DescriptionApache Tomcat Web Container Afternetwork.target [Service] Typeforking EnvironmentJAVA_HOME/usr/local/jdk1.8 EnvironmentCATALINA_HOME/usr/local/tomcat ExecStart/usr/local/tomcat/bin/startup.sh ExecStop/usr/local/tomcat/bin/shutdown.sh Restarton-failure [Install] WantedBymulti-user.target保存后执行systemctl daemon-reload然后systemctl enable tomcat设置开机自启systemctl start tomcat启动服务。这里Typeforking是因为startup.sh本身是一个启动后立即返回的脚本真正运行的 Tomcat 进程是它 fork 出去的子进程。如果类型写错了systemd 会认为服务没起来反复重启或报错。关于 nginx 和 JSP 的关系一句话说清nginx 本身不解析 JSP遇到.jsp请求它只能把请求转发给 Tomcat。所以 nginx 的正确姿势是做反向代理和静态资源缓存而不是指望它直接执行 JSP。upstream elibrary { server 127.0.0.1:8080; } server { listen 80; server_name book.example.com; location / { proxy_pass http://elibrary; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* \.(css|js|png|jpg|gif)$ { root /data/projects/elibrary-static; expires 7d; } }注意静态资源单独配了一个 location用正则匹配常见的静态文件后缀直接走磁盘路径不经过 Tomcat。这能显著降低 Tomcat 的负载因为 JSP 页面里的 CSS、JS、图片是最耗连接数的资源。但这里有个前提你要把 JSP 里引用静态资源的路径改成/static/css/xxx.css这种独立路径而不是和 JSP 混在一起否则 nginx 的 location 匹配不到。提示如果静态资源路径频繁改动或者你不想单独维护一个静态目录更省事的方案是全部走 Tomcatnginx 只做 proxy_pass。小系统 Tomcat 完全扛得住没必要为了优化而优化。5. Tomcat 常见问题避坑闪退、404、乱码、GET 链接失效和版本迁移5.1 Tomcat 启动秒退不是玄学是日志没看现象双击startup.sh或startup.bat窗口一闪而过Tomcat 没有起来IDEA 启动时直接报Process finished with exit code 1。原因最常见的有三类。一是JAVA_HOME环境变量没配置或者指向了不存在 JDK 目录二是 8080 端口被其他程序占用三是你用了 Tomcat 10 但本机是 JDK 8版本不匹配导致启动脚本在某些环境下异常退出。解决方式不要双击启动脚本去命令行前台运行 Tomcat。Windows 执行bin\catalina.bat runLinux 执行bin/catalina.sh run。日志直接打到控制台看清楚在哪一步报错。同时检查logs/catalina.out和logs/localhost.log大部分启动失败的真正原因都在这两个文件里。如果是端口被占用用系统命令查一下netstat -tunlp | grep 8080找到占用进程后要么杀掉它要么修改server.xml里的port8080为其他端口。5.2 IDEA 里部署后 404多半不是代码问题是发布结构问题现象IDEA 里配置好 Tomcat启动后访问http://localhost:8080/elibrary/login.jsp返回 404页面提示“源服务器未能找到目标资源的表示”这个报错在 IDEA 里很常见。原因排查思路先分清是 Tomcat 首页能访问还是完全打不开。如果访问http://localhost:8080/能看到 Tomcat 的默认首页说明 Tomcat 本身正常问题出在应用发布路径。最常见的两个操作错误是在 Run Configuration 的 Deployment 里没有设置 Application context 为/elibrary或者把 JSP 文件放在了src/main/java目录下面发布后不在 Web 应用的根目录里Tomcat 自然找不到。解决方式打开 Run Configuration看 Deployment 标签页确认 Server 下 Application context 是否和访问路径一致。再检查项目的输出目录IDEA 里的war exploded模式会输出到一个 target 目录进去看看WEB-INF是否存在JSP 是否在正确位置。提示先用浏览器访问http://localhost:8080/验证 Tomcat 首页。首页能出而应用 404问题在发布配置首页都打不开问题在 Tomcat 本身。这样能快速缩小排查范围。5.3 中文乱码按层治理一次调明白现象页面里中文变成一堆问号或管å控制台输出乱码数据库里写入的中文也变成???。原因乱码的本质是各层编码不一致。源码文件可能是 GBKMaven 编译时用了 platform 默认编码Tomcat 的 URI 解码是 ISO-8859-1MySQL 表是 utf8连接串又没指定编码多个环节只要有一个错最终表现出来都是乱码。所以治理必须逐层统一到 UTF-8。解决步骤按顺序来。第一步IDE 里把项目编码、源码文件编码全部设为 UTF-8。第二步在 Servlet 或 Filter 里加request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)。第三步检查server.xml连接器的URIEncodingUTF-8。第四步确认 JDBC URL 里有characterEncodingUTF-8。第五步如果控制台在 Windows 下还乱码修改conf/logging.properties里的编码或在 IDEA 的 VM options 加-Dfile.encodingUTF-8。这一步做完绝大多数乱码问题都能解决。如果还有个别页面乱码检查那个 JSP 文件本身的编码格式用 IDEA 右下角查看文件编码把它转成 UTF-8。5.4 GET 链接里带特殊字符被 Tomcat 打回relaxedQueryChars现象点击列表里的某个链接URL 里带着|、{}、^这类字符Tomcat 直接返回 400 Bad Request错误日志里写着Invalid character found in the request target。原因Tomcat 8.5 及以后版本对请求行按 RFC 3986 和 RFC 7230 做了严格校验。URL 里出现未编码的|、[、]、{、}字符会被判定为非法字符并拒绝请求。你从其他系统跳转过来或 JSP 页面上直接拼 URL 时这些符号没有被编码就会触发这个限制。解决方式分两层。第一层是代码层页面里拼参数时用c:url标签自动编码或者 Java 代码里用URLEncoder.encode()。这是根治方案。第二层是配置层兜底在server.xml的 Connector 上增加relaxedQueryChars[]|{}^\这个属性告诉 Tomcat 放宽对 URL 特殊字符的检查。注意它只影响 URI 解析不影响安全性但从经验看老系统里最省心的方式就是加上它然后在页面上逐步清理链接。5.5 Tomcat 10 的迁移坑javax 秒变 jakarta现象一套在 Tomcat 9 上运行良好的老项目源码部署到本地 Tomcat 10 后启动时报ClassNotFoundException: javax.servlet.http.HttpServlet或NoClassDefFoundError所有 Servlet 都起不来。原因Tomcat 10 开始把 Java EE 的命名空间迁移到 Jakarta EE原有的javax.servlet.*和javax.servlet.http.*包全部改名为jakarta.servlet.*和jakarta.servlet.http.*。老代码里的 import 语句在 Tomcat 10 的 servlet-api 里根本找不到对应的类。解决方式有两个。最稳妥的是换回 Tomcat 9老源码零改动直接跑。如果你必须用 Tomcat 10就要做一次代码迁移把src目录下所有 Java 文件里的import javax.servlet.*改成import jakarta.servlet.*同理javax.servlet.http.*改成jakarta.servlet.http.*。JSTL 依赖也要换把javax.servlet.jsp.jstl.core.*改成jakarta.servlet.jsp.jstl.core.*并且用org.glassfish.web:jakarta.servlet.jsp.jstl替换老的jstl-1.2.jar。这种迁移工作量不小而且系统里如果有第三方库依赖了老包名光是找出来就要花半天。所以结论很直接接手老源码默认装 Tomcat 9除非有硬性要求才考虑 10。6. 从“能跑”到“能用”一页自测清单和一个被忽略的分页优化系统跑起来不算完得验证“可用性”。下面这张表我每次交付前都会过一遍你也可以直接拿去用。功能模块验证步骤预期结果登录拦截不开浏览器登录态直接访问 /book/list跳转 login.jsp分页边界给 book 表插 30 条数据访问 ?page3显示 10 条且没有“下一页”链接借阅并发把某本书 available 置 1两个账号同时借只有一个成功另一个提示可借不足中文搜索输入“三体”搜索URL 不乱码结果正确返回空列表搜索一个不存在的词页面正常显示“暂无数据”不抛异常这里我想说一个很多源码都不会照顾到的点分页的totalPages在数据变动后会不会失效。借书或还书是高频操作每次刷新列表时重新count()是最省心的做法。几千条数据的量级下MySQL 算 count 只需要几十毫秒完全不值得为它做缓存。但如果你的 book 表里加了全文索引或者 LIKE 模糊查询命中了大字段count 会慢很多。一个常见的优化是给搜索字段加组合索引ALTER TABLE book ADD INDEX idx_title_author (title, author);注意这个索引对WHERE title LIKE 三%有效但对WHERE title LIKE %三体%这类前置通配符的查询无效。如果系统的搜索框确实需要模糊匹配至少保证精确匹配或前缀匹配走索引这是性价比最高的做法。最后说句我的习惯。以前我也觉得 JSP 老气毕设时硬上了 Vue Spring Boot结果一个人同时维护前端路由、跨域配置、登录态存储和服务器部署累得快放弃了。后来捡回 JSP Servlet 做老系统改造反而觉得这套东西只要接受“前后端不分离”的定位几乎所有问题都在代码里能看到、能 debug。技术没有贵贱只有适配场景的差别。希望帮到你。本文还有配套的精品资源点击获取
返回列表