ARTICLE DETAIL

资讯详情

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

Java Web新闻发布系统:Servlet+JSP+MySQL全栈实战解析

Java Web新闻发布系统:Servlet+JSP+MySQL全栈实战解析 简介一份基于 Java Servlet JSP MySQL 的 Web 新闻发布系统源码与配套文档面向正在完成期末大作业、课程设计或需要入门 Java Web 开发的在校学生。系统涵盖新闻分类、内容发布、后台管理、数据持久化等典型模块源码经本地编译可正常运行评审得分 98难度适中所有内容均通过助教老师审定可作为学习参考或直接改造的工程基底。资源包共 1647 个文件大小约 24.45 MB其中 java 文件为业务逻辑与 Servlet 控制层jsp 负责动态页面展示xml 用于配置映射sql 为数据库初始化脚本另有大量 css、js、png、gif 等前端样式、交互素材与界面元素整体目录结构清晰便于按层查阅。目前已有 144 人学习下载适合需要快速上手新闻类 Web 项目的开发者。通过这份资料可获取一套完整的项目源代码、数据库文件及相关文档说明既能对照理解 Java Web 分层开发流程也能在此基础上扩展功能满足课程答辩或项目提交的实际需求。1. 从新闻发布系统反推Java Web传统技术栈的职责边界当一套新闻发布系统的源码摆在你面前技术栈是 Java Servlet JSP MySQL第一反应往往是这套组合还能不能改、部署起来麻不麻烦。其实在 Spring Boot 铺开之前这是绝大多数 Java Web 网页项目的默认答案搜索里高频出现的 mysql 安装配置、eclipse 创建基于 maven 的 servlet 项目、jsp 编译 class 文件保存在哪里说明很多人拿到这类源码之后第一步卡在环境与工程结构而不是业务本身。新闻系统最典型的链路是Servlet 接收列表请求调 DAO 查 MySQL把结果 setAttribute 到 request 里再 forward 给 JSP 渲染后台发布则依赖 Session 登录态和发布表单的校验。这套链路一旦跑通Servlet 生命周期、过滤器以及 Spring MVC 里 DispatcherServlet 和 WebApplicationContext 的分工就都说得通了。下面按“建表与连接池 → Servlet 与 JSP 请求流转 → 发布管理与会话控制 → Tomcat 部署和编译验证”展开。2. 新闻系统MySQL表结构设计JDBC连接池选什么先把数据库层定住。拿到这类源码第一件事不要急着打开 Java 代码而是先执行建表脚本。表结构如果先天缺字段后面加列、加索引都要改动 DAO 和页面返工成本是这几个环节里最高的。MySQL 的安装配置教程网上已经很多这里只说和新闻系统强相关的三个点字符集、索引、连接池。执行脚本用 MySQL Workbench 或者命令行都行重点是用 utf8mb4 字符集建库并且向下兼容旧的建表语句避免出现表情符号写入失败。2.1 三张核心表News、Category、User 中的字段怎么定按资讯类 Web 项目的习惯起步用三张表就够了用户表、分类表、新闻表。分类和新闻是一对多用户和新闻是一对多业务规模不大时不需要引入标签表。表引擎用 InnoDB字符集统一 utf8mb4因为新闻正文里出现中文引号和 emoji 很常见只用 utf8 会在部分字符上出现占位符错乱。CREATE DATABASE news_web DEFAULT CHARACTER SET utf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名称, sort INT NOT NULL DEFAULT 0 COMMENT 排序值越小越靠前 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT新闻分类表; CREATE TABLE news ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL COMMENT 所属分类, title VARCHAR(200) NOT NULL COMMENT 新闻标题, summary VARCHAR(500) DEFAULT COMMENT 摘要列表页展示用, content TEXT COMMENT 新闻正文, image_url VARCHAR(500) DEFAULT COMMENT 封面图路径, author VARCHAR(50) DEFAULT 管理员 COMMENT 作者, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, status TINYINT NOT NULL DEFAULT 1 COMMENT 0草稿 1发布, view_count INT NOT NULL DEFAULT 0 COMMENT 浏览量, KEY idx_category_create (category_id, create_time), KEY idx_status (status), CONSTRAINT fk_news_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT新闻表; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 加密后的密码, nickname VARCHAR(50) DEFAULT COMMENT 显示名称, role TINYINT NOT NULL DEFAULT 0 COMMENT 0编辑 1管理员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT后台用户表;字段设计上有几个容易忽略的点直接用表说明表字段设计要点newscategory_id 与 create_time建联合索引列表页最常见的查询是“某分类按时间倒序”联合索引让排序不再临时 filesortnewsstatus单独索引后台按草稿/发布状态过滤时避免全表扫描newscontent用 TEXT单篇几万字足够不要提早上 LONGTEXT逻辑备份和跨机迁移差别明显newsimage_url只存相对路径如 /upload/xxx.jpg不存带端口和域名的完整地址userpassword存加密后的密文用 MD5 加盐或者 BCrypt不存明文categorysort排序放数据库字段不要在 SQL 里把 name 当排序依据补充一点与存储过程相关的经验分页查询用 COUNT 加 LIMIT 两步完成即可没必要把分页写进 MySQL 存储过程存储过程一旦和 Servlet 事务混在一起排查问题时日志里能给你的线索反而变少。连接层的问题比业务 SQL 更早暴露接着看连接池。2.2 连接池配置DriverManager 和 Druid 怎么选新闻发布系统并发量不大直接用 DriverManager 获取连接也能跑通但每次请求都建立物理连接数据库连接成本高机器内存一紧张很快就会出现连接超时。常见做法是引入连接池Druid 和 HikariCP 都可以关键是初始化只做一次而不是在每次请求里重复读配置。如果你打算用 Eclipse 创建基于 Maven 的 Servlet 项目这个 DbUtil 可以直接放到 src/main/java 下db.properties 放在 src/main/resources 里Maven 打包时会自动带上。在 src/main/resources 下放一个 db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/news_web?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyour_password再用一个 DbUtil 统一管理数据源public class DbUtil { private static DruidDataSource dataSource; static { Properties props new Properties(); try (InputStream in DbUtil.class.getClassLoader().getResourceAsStream(db.properties)) { props.load(in); dataSource new DruidDataSource(); dataSource.setDriverClassName(props.getProperty(jdbc.driver)); dataSource.setUrl(props.getProperty(jdbc.url)); dataSource.setUsername(props.getProperty(jdbc.username)); dataSource.setPassword(props.getProperty(jdbc.password)); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMinIdle(5); } catch (IOException e) { throw new ExceptionInInitializerError(数据库配置加载失败); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }参数含义再明确一下。initialSize 是启动时预建的连接数设 5 适合新闻站的低并发起步maxActive 是最大活跃连接数20 对这个场景足够调高到几百并不会让数据库变快反而会让同一台 MySQL 服务器在突发流量下被压垮minIdle 保持 5 个空闲连接避免瞬间并发上来时全部重新建连。数据库本身的连接上限也要留意连接池 maxActive 和 MySQL 的 max_connections 要留出 buffer。有一个常见误用配了 Druid 又写Class.forName(com.mysql.cj.jdbc.Driver)。驱动注册两遍不会报错但代码一看就是从多个错误答案里拼凑出来的。另一个问题是驱动类名老项目里用com.mysql.jdbc.Driver新版驱动更名成com.mysql.cj.jdbc.Driver连接 URL 还必须带上 serverTimezone否则 JDBC 驱动在解析时间字段时可能抛异常。2.3 DAO 层封装把查询结果转成对象的关键写法DbUtil 只负责连接来源Servlet 里不能直接写 ResultSet 循环。按单表建一个 DAO 类NewsDao 里放 findPage、findById、insert、updateStatus。分页实现用两步查询先 count 拿总数再按 limit 拿当前页数据。public PageResultNews findPage(Integer categoryId, int page, int pageSize) { PageResultNews result new PageResult(); String where ; ListObject params new ArrayList(); if (categoryId ! null) { where WHERE category_id ?; params.add(categoryId); } String countSql SELECT COUNT(*) FROM news where; String pageSql SELECT id, category_id, title, summary, create_time, view_count, status FROM news where ORDER BY create_time DESC LIMIT ?, ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps1 conn.prepareStatement(countSql); PreparedStatement ps2 conn.prepareStatement(pageSql)) { for (int i 0; i params.size(); i) { ps1.setObject(i 1, params.get(i)); ps2.setObject(i 1, params.get(i)); } ps2.setObject(params.size() 1, (page - 1) * pageSize); ps2.setObject(params.size() 2, pageSize); try (ResultSet rs1 ps1.executeQuery()) { if (rs1.next()) { result.setTotal(rs1.getInt(1)); } } try (ResultSet rs2 ps2.executeQuery()) { ListNews list new ArrayList(); while (rs2.next()) { News n new News(); n.setId(rs2.getInt(id)); n.setCategoryId(rs2.getInt(category_id)); n.setTitle(rs2.getString(title)); n.setSummary(rs2.getString(summary)); n.setCreateTime(rs2.getTimestamp(create_time)); n.setViewCount(rs2.getInt(view_count)); n.setStatus(rs2.getInt(status)); list.add(n); } result.setList(list); } return result; } catch (SQLException e) { throw new RuntimeException(分页查询失败, e); } }这里故意不用 select *。列表页不需要 content 大字段查出来只会增加网络传输和对象内存详情页再通过 findById 单独取 content。PreparedStatement 先占位后传参categoryId 不会拼进 SQL 字符串这属于防注入的基础写法。分页的 LIMIT 偏移量是(page - 1) * pageSize第一页从第 0 条开始要跟 SQL 的 LIMIT 语法对应否则页码整体错位。3. ServletJSP的新闻列表与详情页请求流转怎么串列表页和详情页是新闻系统里最常用的两个动作。列表页要处理分页和分类筛选详情页要处理无效参数和浏览量这两条链路走通后台管理的 Servlet 就是同样的模式加一点状态判断。3.1 用 WebServlet 写 ListNewsServlet分页参数校验不做懒处理WebServlet 注解方式省掉了 web.xml 里的 servlet 映射Tomcat 7 以上都支持。同一个 web 工程里注解和 web.xml 不冲突但同一个 Servlet 路径不要同时配置两份否则部分容器会重复初始化。WebServlet(/news/list) public class ListNewsServlet extends HttpServlet { private NewsDao newsDao new NewsDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); int page parsePositiveInt(req.getParameter(page), 1); int pageSize parsePositiveInt(req.getParameter(pageSize), 10); Integer categoryId parsePositiveIntOrNull(req.getParameter(categoryId)); PageResultNews result newsDao.findPage(categoryId, page, pageSize); req.setAttribute(result, result); req.setAttribute(categoryId, categoryId); req.getRequestDispatcher(/WEB-INF/jsp/news/list.jsp).forward(req, resp); } private int parsePositiveInt(String value, int defaultValue) { if (value null || !value.matches(\\d)) { return defaultValue; } return Integer.parseInt(value); } private Integer parsePositiveIntOrNull(String value) { if (value null || !value.matches(\\d)) { return null; } return Integer.valueOf(value); } }这里有两个小细节容易被忽略。第一个是字符编码req.setCharacterEncoding(UTF-8)只对 POST 请求体里的参数有效GET 请求参数的编码取决于 Tomcat 连接器的 URIEncoding 配置在 url 里带中文分类名或标题时要么配置 Tomcat 的URIEncodingUTF-8要么让前端的链接只传 id。第二个是请求分页参数page-1或pageabc这类脏值必须兜底直接Integer.parseInt会抛出 NumberFormatException页面显示 500。页面跳转用的是 forward 而不是 sendRedirect。sendRedirect 是让浏览器重新发起一次请求地址栏会变成新地址并且原 request 里的属性全部丢失forward 是 Servlet 容器内部转发浏览器地址不变JSP 可以直接通过${result}取到 Servlet 设好的数据。3.2 JSP 页面只用 EL 和 JSTL源码里不写 Java 脚本列表页 JSP 中常见的错误写法是% for (...) { %块结合 JDBC 代码。换成 JSTL 和 EL 之后再维护页面里只剩标签和变量名。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle新闻列表/title/head body c:forEach items${result.list} varitem div classnews-item h3a href${pageContext.request.contextPath}/news/detail?id${item.id}${item.title}/a/h3 p${item.summary}/p spanfmt:formatDate value${item.createTime} patternyyyy-MM-dd HH:mm//span span浏览量${item.viewCount}/span /div /c:forEach div classpagination c:if test${result.currentPage 1} a href${pageContext.request.contextPath}/news/list?page${result.currentPage - 1}categoryId${categoryId}上一页/a /c:if span第 ${result.currentPage} / ${result.totalPages} 页/span c:if test${result.currentPage result.totalPages} a href${pageContext.request.contextPath}/news/list?page${result.currentPage 1}categoryId${categoryId}下一页/a /c:if /div /body /html这段代码要注意三点。第一${pageContext.request.contextPath}是动态获取工程上下文路径工程名改了就自动变不要在链接里写死 /news_web/。第二fmt:formatDate 处理 java.util.Date如果 createTime 在实体类里用了 LocalDateTime需要先转换或者直接用c:out输出字符串大部分老源码直接用 TimestampJSTL 可以识别。第三分页链接要带上 categoryId否则翻到第二页时分类条件丢失看似“分页失效”实际是请求参数没有完整透传。JSTL 标签库需要在 web 工程里引入 jstl 相关 jar 包Servlet 容器本身不包含 JSTL 实现。很多人在 Eclipse 里新建 Servlet 项目时能编过 JSP一运行报错提示找不到标签库就是这个依赖漏了。用 Maven 管理依赖时把 jstl 依赖加到 pom.xml构建后会自动打进 war。转发关系整理成一张表方便对照工程目录找文件页面动作Servlet 映射forward 目标新闻列表/news/list/WEB-INF/jsp/news/list.jsp新闻详情/news/detail/WEB-INF/jsp/news/detail.jsp后台登录入口/admin/login.jsp直接访问不经 Servlet3.3 详情页与原生 Servlet 的映射关系和 DispatcherServlet 对照着看详情页的 Servlet 逻辑比列表页更简单但它承担着参数校验和浏览量自增两件事。WebServlet(/news/detail) public class NewsDetailServlet extends HttpServlet { private NewsDao newsDao new NewsDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String idParam req.getParameter(id); if (idParam null || !idParam.matches(\\d)) { resp.sendError(400); return; } News news newsDao.findById(Integer.parseInt(idParam)); if (news null || news.getStatus() ! 1) { resp.sendError(404); return; } newsDao.increaseViewCount(news.getId()); req.setAttribute(news, news); req.getRequestDispatcher(/WEB-INF/jsp/news/detail.jsp).forward(req, resp); } }id 参数先匹配\\d再转换能挡掉负数和非数字字符串。news 不存在时返回 404避免被搜索引擎收录后出现一堆无效详情页。浏览量自增放在查询之后SQL 可以写成UPDATE news SET view_count view_count 1 WHERE id ?不要在 Java 里先查出来再加因为那两步中间如果有并发计数会互相覆盖。原生 Servlet 的/news/detail映射到一个 HttpServlet 类上Spring MVC 则用一个 DispatcherServlet 作为前端控制器DispatcherServlet 内部通过 HandlerMapping 把 URL 映射到 Controller 方法。DispatcherServlet 的 WebApplicationContext 负责装配 Controller 等 Web 层 beanrootWebApplicationContext 负责数据源和业务层 bean两层之间有父子容器关系。理解这个语境之后再去看 Servlet WebApplicationContext 和 RootWebApplicationContext 的差异就不会停留在背面试八股文的程度。4. 新闻发布系统里的登录控制、发布表单与文件上传新闻系统的后台通常包含登录、新闻发布、图片上传三个模块。这三个模块不需要复杂的权限框架用 Filter 加 Session 就能做到清晰可控重点是别把所有校验都压在 JSP 页面里。4.1 用 Filter 统一拦截后台 Session 没有的请求最常见的裸奔做法是每个后台 Servlet 开头都从 Session 里查登录用户代码一多就有人漏写。推荐用一个 Filter 统一拦截 /manage/* 下的所有路径WebFilter(/manage/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); Object loginUserId session null ? null : session.getAttribute(loginUserId); if (loginUserId null) { response.sendRedirect(request.getContextPath() /admin/login.jsp); return; } chain.doFilter(req, resp); } }关键点是getSession(false)。如果传 true容器会为每个未登录请求都新造一个 Session等于把会话 ID 无意义地发给所有访问者Cookie 里存了一堆垃圾。这里显式传 falsesession 不存在就直接跳登录页。登录页本身要放在 /admin/ 下而不是 /manage/ 下避免进后台页面时先被 Filter 拦回登录页形成死循环。登录成功后不要只把用户名放 Session把 userId 和角色 role 一起放进去后面判断“能否删除新闻”时需要角色只存用户名就成了能进不能管的“假登录”。使用 WebFilter 时过滤器顺序不固定如果项目里还有编码过滤器最好在 web.xml 里把 encodingFilter 放在 LoginFilter 前面否则过滤器链先执行登录判断再纠正编码POST 参数的中文可能还没生效就被带进业务逻辑了。4.2 发布新闻的保存 Servlet参数校验、转义与重复提交发布新闻是后台最重要的写操作。NewsSaveServlet 建议单独建在 /manage/news/save 路径表单 post 到这儿。这里重点关注四个边界未登录直接 POST 的请求、空标题和空正文、正文里的 HTML 脚本、重复点击提交按钮。WebServlet(/manage/news/save) public class NewsSaveServlet extends HttpServlet { private NewsDao newsDao new NewsDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUserId) null) { resp.sendError(403); return; } String title req.getParameter(title); String content req.getParameter(content); if (title null || title.trim().length() 2 || title.trim().length() 200) { req.setAttribute(error, 标题长度需要在 2 到 200 字之间); req.getRequestDispatcher(/manage/news/edit.jsp).forward(req, resp); return; } if (content null || content.trim().isEmpty()) { req.setAttribute(error, 正文不能为空); req.getRequestDispatcher(/manage/news/edit.jsp).forward(req, resp); return; } News news new News(); news.setTitle(escapeHtml(title.trim())); news.setSummary(escapeHtml(req.getParameter(summary))); news.setContent(filterUnsafeHtml(content)); news.setAuthor((String) session.getAttribute(loginNickname)); // categoryId、status 同理需要强校验后 set newsDao.insert(news); resp.sendRedirect(req.getContextPath() /manage/news/list); } }这段代码把转义和过滤放在一个明显的位置。title 是纯文本用 escapeHtml 把script变成lt;scriptgt;页面再怎么渲染也出不了脚本。content 如果允许后台人员发简单 HTML要用白名单方式过滤只保留 p、br、strong 这类安全标签即便富文本编辑器在浏览器端做了过滤服务端也不能裸信任。表单提交后的跳转用了 sendRedirect避免刷新时浏览器再次 POST。重复点击的问题常见做法是提交后前端禁用按钮后端也可以再配合一个 token 校验但对后台发布系统来说先做好参数校验和登录过滤更重要。4.3 图片上传用 MultipartConfig 和 Part路径别和业务字段混在一起新闻列表页的封面图一般只存图片 URL不把图片二进制放进 MySQL。上传接口用 Servlet 3.0 的 Part 接口实现不需要额外引 commons-fileupload。在 Servlet 类上增加注解WebServlet(/manage/news/uploadImage) MultipartConfig( maxFileSize 3 * 1024 * 1024, maxRequestSize 10 * 1024 * 1024, fileSizeThreshold 1024 * 1024 ) public class UploadImageServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part part req.getPart(file); if (part null || part.getSize() 0) { resp.getWriter().write({\code\:400}); return; } String submitted part.getSubmittedFileName(); int dotIndex submitted.lastIndexOf(.); String ext dotIndex 0 ? submitted.substring(dotIndex 1).toLowerCase() : ; if (!jpg.equals(ext) !jpeg.equals(ext) !png.equals(ext) !gif.equals(ext)) { resp.getWriter().write({\code\:400}); return; } String savedName UUID.randomUUID().toString().replace(-, ) . ext; String uploadDir getServletContext().getRealPath(/upload); File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } long size part.write(uploadDir File.separator savedName); resp.getWriter().write({\code\:200,\url\:\/upload/ savedName \}); } }配置项的含义列一下方便对照着调参数示例值作用maxFileSize3 * 1024 * 1024单张图片上限 3MB大于这个尺寸直接拒绝maxRequestSize10 * 1024 * 1024一次请求体总大小上限防止批量传图撑爆堆内存fileSizeThreshold1024 * 1024超过 1MB 的 Part 先落磁盘临时文件而不是一直留在内存上传文件的文件名必须重命名。part.getSubmittedFileName()是浏览器传过来的原始名可能带中文、空格甚至路径信息直接拼到目录里会引发路径穿越风险用 UUID 生成随机名原始扩展名仅作为白名单判断依据。图片 URL 中保存/upload/xxx.jpg这种相对路径不存http://localhost:8080/xxx原因在第 2 章已经说过换域名和端口时存量数据不用改。5. Tomcat部署后怎么验证JSP编译的class文件位置部署一个传统 Web 工程网上教你“把 war 拷贝到 webapps”的文章一搜一大把但真正上手时常见的问题往往不是启动失败而是页面行为跟代码不一致。这时候直接去看 Tomcat 把 JSP 编译出来的 Java 源码比反复刷新页面更快定位。5.1 用 work 目录确认 JSP 是否真的编译成功先用 Maven 打包或者直接用 Eclipse 的 Run on Server 把这个 web 项目部署到 Tomcat。启动后第一次访问首页新闻列表Tomcat 才会把对应的 list.jsp 翻译成 Java 源码并编译成 class 文件。文件位置在 Tomcat 安装目录的 work 子目录里与工程名和上下文路径对应ls -l $CATALINA_HOME/work/Catalina/localhost/news_web/org/apache/jsp/预期能看到类似下面的输出-rw-r--r-- 1 dev dev 42170 mon dd hh:mm list_jsp.java -rw-r--r-- 1 dev dev 28241 mon dd hh:mm list_jsp.classlist_jsp.java 是 JSP 翻译后的 Servlet 源码里面能看到一个_jspService方法request、response、session、application 这些对象会被容器预置成方法内局部变量。如果页面里引用的 JSTL 标签或者 EL 表达式运行时报错在生成的 Java 文件里搜具体的异常关键字比对着 JSP 猜要快得多。修改 JSP 后Tomcat 会自动重新编译并覆盖旧的 .java 和 .class 文件不需要手动删除 work 目录如果你发现改完页面没生效再检查是不是浏览器缓存了旧资源换个无痕窗口访问一次即可。第一次用 Eclipse 打开老项目时JSP 编辑器会提示页面内嵌 JavaScript 和标签库无法解析还会弹出离开页面提示这些静态检查信息不影响 Tomcat 编译很多人却因此误判成工程有问题。直接把 work 目录下对应生成的 .java 文件打开看到内容里已经有你新加的 HTML 片段就说明部署的确实是当前最新版本提示可以忽略。5.2 文档说明按数据库脚本、接口清单、部署步骤三块整理这类源码包里通常会带文档说明但很多文档写成了流水账。常见做法是控制住三个部分即可环境要求与部署步骤、数据库初始化脚本说明、后台接口与目录结构清单。环境要求部分写清楚 JDK 版本、Tomcat 版本和 MySQL 版本这三个版本不匹配是最常见的启动异常。数据库部分把建表脚本的导入命令写出来比如mysql -u root -p news_web.sql并注释默认后台账号密码。接口清单部分把 /news/list、/news/detail、/manage/news/save 这些路径的请求方式、参数和返回页面列成表格。目录结构说明标注 java 源码和 JSP 所在位置后来接手的同事不需要从 web.xml 一个个去翻。最后给一个可复现的验证顺序先执行数据库脚本再用mvn package打 war拷贝到 Tomcat webapps 后启动访问/news/list看到数据点进详情页看到浏览量 1再登录后台发布一条测试新闻并上传一张封面图最后检查 work 目录里对应的 JSP class 文件时间戳。复制生成的 list_jsp.java 到工程外的临时目录用 diff 命令和上一版部署的编译产物比对行号变化的范围能精确指出 JSP 页面上哪一段模板代码被更改过这个技巧在排查线上页面和本地不一致时比一遍遍重启 Tomcat 更省事。本文还有配套的精品资源点击获取
返回列表