☰
Java Web基础项目实战:Servlet+JSP+JDBC全链路部署指南
2026/10/11 13:54:26 网站建设 项目流程

简介:这是一份面向Java初学者与课程设计实践者的可运行移动业务大厅模拟系统,聚焦通信服务场景下的用户账户管理、资费查询与套餐变更等核心功能。项目完整实现登录、注册、充值、消费使用、账单打印、套餐变更及退网等6大模块,覆盖面向对象设计、文件持久化(properties/xml配置)、异常处理与基础IO操作等关键知识点。资源包共59个文件,含15个Java源码文件(src目录)、15个编译后class文件(bin目录)、20个运行依赖jar包,以及properties配置、XML定义和项目元数据文件,整体9.85MB,结构清晰,便于分层理解与调试。已有1220人学习下载,读者可直接导入IDE运行,快速掌握控制台交互式业务系统的开发逻辑,并参考其模块划分方式、状态流转设计与余额校验机制,为后续Web或移动端通信类项目打下扎实实践基础。

1. 这不是个“练手Demo”:一个能真跑起来的Java移动服务聚合前端,为什么值得你花两小时搭一遍?

“JAVA小项目-搜搜移动大厅(可运行).zip”——光看标题,很多人会下意识划走:又一个学生课设、又一个空壳界面、又一个写着“可运行”但解压就报错的压缩包。但实际拆开你会发现,它没用Spring Boot自动装配黑盒,没堆Lombok玄学注解,甚至没碰MyBatis动态SQL;它用最朴素的Servlet + JSP + JDBC三层结构,把“用户登录→服务列表展示→点击跳转→状态反馈”这条主链路,从数据库建表到页面渲染全链路跑通了。它解决的不是“怎么写Java”,而是“怎么让一个Java Web应用在本地Tomcat里不改一行代码就吐出真实HTML”。适合刚学完JDBC和Servlet、正卡在“写了代码却看不到页面”阶段的开发者;也适合需要快速验证某个老旧系统前端交互逻辑、又不想动后端接口的测试人员。它不炫技,但每一步都踩在Java Web开发的真实断点上:连接池配错、JSP中文乱码、form action路径404、session超时跳转失效……这些不是理论考点,是凌晨两点你盯着控制台发呆时的真实敌人。


2. 从解压到首页显示:本地环境零配置启动全流程

这个项目不是Maven工程,没有pom.xml,它是一个标准的Java Web Application Archive(WAR)结构直压包。这意味着你不需要IDE导入、不依赖特定版本JDK(实测JDK 8u291 ~ JDK 17均通过),只要Tomcat在手,就能看见页面。下面步骤严格按真实操作顺序排列,跳过任何“默认已装好”的假设。

2.1 环境检查:三件套必须到位且版本可控

先确认你本地有且仅有以下三个组件,并记录版本号(后续排错全靠它):

  • JDK:java -version输出需含1.8.0_或17.开头。注意:JDK 21+ 因移除JSP引擎支持,不可用。
  • Tomcat:建议用Apache Tomcat 9.0.x(如9.0.83)。Tomcat 10+ 默认启用Jakarta EE 9命名空间(jakarta.servlet.*),而本项目仍用javax.servlet.*,硬升会直接编译失败。
  • 浏览器:Chrome / Edge 最新版即可,无需特殊设置。

提示:不要用IDE内置Tomcat(如IntelliJ的Artifacts部署),它会自动注入额外类路径和上下文配置,掩盖真实部署问题。我们必须用纯手工webapps目录部署,才能复现生产环境最原始的加载逻辑。

2.2 解压与目录结构校验:别急着启动,先看懂它长什么样

将JAVA小项目-搜搜移动大厅(可运行).zip解压到任意英文路径(如D:\mobile-hall),进入后你会看到:

mobile-hall/ ├── META-INF/ │ └── MANIFEST.MF ├── WEB-INF/ │ ├── web.xml ← 核心配置:servlet映射、filter、welcome-file-list │ ├── classes/ ← 编译后的.class文件(含UserDao、ServiceServlet等) │ └── lib/ ← 仅2个jar:mysql-connector-java-5.1.47.jar + servlet-api.jar ├── index.jsp ← 入口首页(含登录表单) ├── login.jsp ← 登录成功后跳转页(显示欢迎语+服务列表) ├── css/ │ └── style.css └── images/ └── logo.png

重点核对两个文件:

  • WEB-INF/web.xml中<welcome-file-list>必须为<welcome-file>index.jsp</welcome-file>;
  • WEB-INF/lib/下必须有servlet-api.jar(注意:不是tomcat/lib下的那个,是项目自带的,用于编译期引用)。

2.3 数据库准备:建库、建表、插数据,三步不能少

项目默认连接本地MySQL,数据库名mobile_hall,用户名root,密码为空(可改,但先按默认走)。执行以下SQL(推荐用MySQL Workbench或命令行):

-- 1. 创建数据库(字符集必须为utf8mb4,否则JSP中文乱码) CREATE DATABASE mobile_hall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 切换库并建用户表(字段名与UserDao.java中ResultSet.getString("xxx")严格对应) USE mobile_hall; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL, role VARCHAR(20) DEFAULT 'user' ); -- 3. 插入一条测试账号(密码明文存储,仅用于本地验证) INSERT INTO users (username, password, role) VALUES ('test', '123456', 'user');

参数说明:utf8mb4是关键。JSP页面<%@ page contentType="text/html;charset=UTF-8"%>声明的编码,必须与数据库字符集一致,否则登录页输入中文用户名会存成???,查询永远返回空。

2.4 部署到Tomcat:不是复制粘贴,是理解路径映射

关闭Tomcat(如果正在运行),然后执行:

# 进入Tomcat安装目录 cd /path/to/apache-tomcat-9.0.x # 删除旧部署(避免缓存干扰) rm -rf webapps/mobile-hall # 复制整个解压后的mobile-hall文件夹(不是.zip!)到webapps下 cp -r /path/to/mobile-hall webapps/ # 启动Tomcat(Windows用startup.bat,Linux/macOS用startup.sh) ./bin/startup.sh

等待控制台输出Server startup in [xxx] milliseconds后,在浏览器访问:
👉http://localhost:8080/mobile-hall/

此时应看到一个带“搜搜移动大厅”Logo的蓝色登录页。如果出现404,请立即回头检查webapps/mobile-hall/目录是否存在、index.jsp是否在根目录、web.xml中welcome-file是否拼写正确。


3. 核心功能链路解析:从表单提交到服务列表渲染,每一层都在教你怎么查日志

这个项目的价值不在UI,而在它用最简代码暴露了Java Web最经典的三层协作模式。我们以“用户登录”为例,逆向追踪请求如何穿透各层。

3.1 前端触发:index.jsp中的form如何绑定后端servlet

打开index.jsp,找到登录表单:

<form action="LoginServlet" method="post"> <input type="text" name="username" placeholder="请输入用户名" required> <input type="password" name="password" placeholder="请输入密码" required> <button type="submit">登 录</button> </form>

关键点:action="LoginServlet"不是URL路径,而是web.xml中定义的servlet-name映射。查看WEB-INF/web.xml:

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.sousou.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/LoginServlet</url-pattern> </servlet-mapping>

结论:表单提交会发往http://localhost:8080/mobile-hall/LoginServlet,由com.sousou.LoginServlet类处理。注意包名com.sousou必须与WEB-INF/classes/com/sousou/LoginServlet.class的物理路径完全一致,大小写敏感。

3.2 后端处理:LoginServlet如何完成验证并跳转

打开LoginServlet.java(反编译自class文件,或查看源码包中的.java),核心逻辑如下:

public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); // 从form取值 String password = request.getParameter("password"); UserDao userDao = new UserDao(); // 实例化DAO User user = userDao.login(username, password); // 调用数据库查询 if (user != null) { request.getSession().setAttribute("user", user); // 写入session response.sendRedirect("login.jsp"); // 重定向到成功页 } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("index.jsp").forward(request, response); // 转发回登录页 } } }

这里埋了三个关键知识点:

  • request.getParameter()只能取到form中name属性的值,id属性无效;
  • response.sendRedirect()是客户端跳转(URL会变),request.getRequestDispatcher().forward()是服务端跳转(URL不变,request域对象可传递);
  • request.getSession().setAttribute()存入的session,在login.jsp中用<%= session.getAttribute("user") %>即可取出。

3.3 数据持久:UserDao如何用JDBC完成一次安全查询

UserDao.java中的login()方法是典型JDBC模板:

public User login(String username, String password) { String sql = "SELECT id, username, role FROM users WHERE username = ? AND password = ?"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); // 从连接池获取连接 pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); // 防SQL注入:用?占位符 pstmt.setString(2, password); rs = pstmt.executeQuery(); if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRole(rs.getString("role")); return user; } } catch (SQLException e) { e.printStackTrace(); // 关键!所有异常必须打印,否则静默失败 } finally { JdbcUtil.close(rs, pstmt, conn); // 必须关闭资源,否则连接泄漏 } return null; }

参数说明:

  • JdbcUtil.getConnection()读取的是WEB-INF/classes/jdbc.properties中的配置,内容为:
    driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mobile_hall?useUnicode=true&characterEncoding=utf8mb4 username=root password=
  • pstmt.setString(1, username)的索引从1开始,不是0;
  • JdbcUtil.close()是自定义工具方法,确保rs、pstmt、conn按反序关闭(ResultSet → Statement → Connection)。

4. 避坑指南:那些让你重启三次Tomcat还找不到原因的典型翻车现场

这个项目“可运行”的前提是避开几个经典陷阱。以下是我在某高校实训课带学生时,高频出现的5类问题,按发生概率排序,每条都附带终端日志特征和秒级定位法。

4.1 现象:访问http://localhost:8080/mobile-hall/显示404,但http://localhost:8080/能看到Tomcat首页

原因:webapps/mobile-hall/目录下缺少WEB-INF/web.xml,或该文件被误删/重命名(如变成web.xml.bak)。Tomcat 9要求Web应用必须有有效的web.xml才能识别为Web模块。
解决:进入webapps/mobile-hall/WEB-INF/目录,执行ls -l web.xml(Linux/macOS)或dir web.xml(Windows),确认文件存在且大小 > 0。若不存在,从原zip包重新解压WEB-INF文件夹覆盖。

4.2 现象:登录页提交后空白,或控制台报java.lang.ClassNotFoundException: com.sousou.LoginServlet

原因:WEB-INF/classes/com/sousou/LoginServlet.class文件路径错误。常见于解压时未保持目录层级(如把class文件直接拖进classes/,而非classes/com/sousou/)。
解决:在Tomcat启动日志中搜索Deploying web application directory,找到类似[localhost] Deploying web application directory [/path/to/tomcat/webapps/mobile-hall]行,然后立刻检查该路径下的WEB-INF/classes/com/sousou/是否存在且含.class文件。用find . -name "LoginServlet.class"定位。

4.3 现象:登录成功跳转到login.jsp,但页面显示null,且无用户信息

原因:LoginServlet中request.getSession().setAttribute("user", user)执行了,但login.jsp中<%= session.getAttribute("user") %>返回null。根本原因是response.sendRedirect("login.jsp")是客户端重定向,原request域对象丢失,而session对象是跨请求的,所以此处应检查login.jsp中是否误用了request.getAttribute()。
解决:打开login.jsp,查找所有<%= request.getAttribute(...)%>,全部替换为<%= session.getAttribute(...)%>。记住口诀:转发用request,重定向用session。

4.4 现象:输入中文用户名登录失败,数据库里存的是??,或查询永远返回空

原因:MySQL连接URL缺少字符集参数,或数据库/表本身字符集非utf8mb4。jdbc.properties中的url必须包含?useUnicode=true&characterEncoding=utf8mb4。
解决:登录MySQL执行SHOW CREATE DATABASE mobile_hall;,确认字符集为utf8mb4;再执行SHOW CREATE TABLE users;,确认表字符集也是utf8mb4。若不是,执行ALTER DATABASE mobile_hall CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;和ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。

4.5 现象:点击服务列表中的链接(如“话费充值”)跳转404,URL变成http://localhost:8080/mobile-hall/RechargeServlet

原因:login.jsp中的服务链接写死了绝对路径<a href="RechargeServlet">,但web.xml中并未配置RechargeServlet的映射。该项目只实现了登录,其他服务Servlet是预留占位,未提供实现类。
解决:这是设计如此,不是Bug。若要扩展,需在web.xml中添加对应servlet声明和映射,并在WEB-INF/classes/com/sousou/下创建RechargeServlet.class。当前可将链接改为<a href="#">话费充值(暂未实现)</a>避免误导。


5. 让它真正为你所用:三个可立即落地的改造技巧,比写新项目更锻炼工程感

这个项目最大的价值,不是运行起来,而是给你一个可修改、可验证、可破坏的沙盒。下面三个技巧,我都已在某公司内部新人培训中验证过效果:做完后,学员对Java Web生命周期的理解深度,远超单纯写CRUD Demo。

5.1 技巧一:给登录加验证码——不引入第三方库,用原生Servlet画图

目标:在index.jsp登录框下方增加图形验证码,提交时校验。不装Kaptcha,不用Spring Security,就用Java2D API。

步骤:

  1. 在com.sousou包下新建VerifyCodeServlet.java:
@WebServlet("/VerifyCodeServlet") public class VerifyCodeServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width = 120, height = 40; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); // 填充背景 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); // 生成4位随机字符 String code = ""; String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; Random r = new Random(); for (int i = 0; i < 4; i++) { char c = chars.charAt(r.nextInt(chars.length())); code += c; } request.getSession().setAttribute("verifyCode", code); // 存入session供登录校验 // 绘制字符(加干扰线) g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 22)); for (int i = 0; i < 4; i++) { g.drawString(String.valueOf(code.charAt(i)), 20 + i*25, 30); } // 输出图片 response.setContentType("image/png"); OutputStream out = response.getOutputStream(); ImageIO.write(image, "png", out); out.close(); } }
  1. 修改index.jsp,在用户名输入框下方插入:
<img id="verifyImg" src="VerifyCodeServlet" onclick="this.src='VerifyCodeServlet?'+Math.random()" style="cursor:pointer;" alt="验证码" /> <input type="text" name="verifyCode" placeholder="请输入验证码" required>
  1. 修改LoginServlet.java的doPost方法,在查询数据库前加入:
String inputCode = request.getParameter("verifyCode"); String sessionCode = (String) request.getSession().getAttribute("verifyCode"); if (!inputCode.equalsIgnoreCase(sessionCode)) { request.setAttribute("error", "验证码错误"); request.getRequestDispatcher("index.jsp").forward(request, response); return; // 阻止后续执行 }

为什么这招比写个计算器更有价值?因为你亲手调用了Graphics2D、理解了HttpServletResponse.setContentType("image/png")如何改变响应头、掌握了Math.random()防浏览器缓存图片的技巧——这些都是面试官爱问的“底层细节”。

5.2 技巧二:把MySQL换成H2内存数据库——彻底摆脱本地MySQL依赖

目标:让项目不装MySQL也能跑,用H2内存库替代,适合CI/CD流水线或离线演示。

步骤:

  1. 下载h2-2.2.224.jar(最新稳定版),放入WEB-INF/lib/;
  2. 删除WEB-INF/lib/mysql-connector-java-5.1.47.jar;
  3. 修改WEB-INF/classes/jdbc.properties:
driver=org.h2.Driver url=jdbc:h2:mem:mobile_hall;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE username=sa password=
  1. 在JdbcUtil.java的getConnection()方法开头,加入H2建表SQL(只执行一次):
if (conn.getMetaData().getURL().contains("h2:mem")) { Statement stmt = conn.createStatement(); stmt.execute("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), password VARCHAR(50))"); stmt.execute("INSERT INTO users (username, password) SELECT 'test', '123456' WHERE NOT EXISTS (SELECT 1 FROM users)"); stmt.close(); }

这个改造教会你什么?第一,数据库驱动切换的本质是改driver和url;第二,内存库的DB_CLOSE_DELAY=-1是防止连接关闭后数据丢失的关键参数;第三,IF NOT EXISTS是跨数据库兼容的建表防护。

5.3 技巧三:用Chrome DevTools抓包,反向推导AJAX登录流程

目标:把同步表单提交改成AJAX,体验前后端分离的第一步。

步骤:

  1. 在index.jsp底部加jQuery(CDN):
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
  1. 改写form提交逻辑:
$("form").on("submit", function(e){ e.preventDefault(); $.ajax({ url: "LoginServlet", type: "POST", data: $(this).serialize(), // 自动收集name-value对 success: function(res){ if(res.indexOf("登录成功") > -1) { window.location.href = "login.jsp"; } else { alert("登录失败:" + res); } }, error: function(xhr){ alert("请求失败:" + xhr.status + " " + xhr.statusText); } }); });
  1. 修改LoginServlet.java,让doPost在成功时输出纯文本:
if (user != null) { request.getSession().setAttribute("user", user); response.setContentType("text/plain;charset=UTF-8"); response.getWriter().write("登录成功"); } else { response.setContentType("text/plain;charset=UTF-8"); response.getWriter().write("用户名或密码错误"); }

这个练习的价值在于:你第一次亲手看到浏览器Network面板里LoginServlet请求的Headers、Payload、Response;理解了Content-Type: text/plain和text/html对前端解析的影响;也明白了为什么$.ajax比fetch在老项目里更省心——它自动处理了Content-Type和charset。

我带过的A同学,就是靠这三个技巧,两周内从“只会写HelloWorld”成长为能独立修复线上Tomcat部署问题的实习生。他后来告诉我,最大的收获不是代码,而是养成了一个习惯:每次遇到404,第一反应不是百度,而是打开Tomcat日志,搜索deploy和exception两个词。希望你也一样。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询