JavaWeb原生架构实战:JSP+Servlet+JavaBean登录注册系统

发布时间:2026/10/24 0:27:23
JavaWeb原生架构实战:JSP+Servlet+JavaBean登录注册系统

简介:这是一份面向Java Web初学者的MVC架构实战项目资源,基于JSP+JavaBean+Servlet技术栈,完整实现用户登录注册、留言板交互及数据分页三大核心功能,适用于高校课程设计、Web开发入门实训与传统JavaEE技术栈巩固学习。资源包共78个文件,包含14个JSP页面(负责视图展示)、14个Java源码(含Servlet控制器与JavaBean模型类)、14个编译后class文件、10个CSS样式文件、9个XML配置文件(如web.xml、数据库连接配置)以及SQL建库脚本等,整体压缩包仅1.86MB,轻量易部署。已有1611人学习下载,适配无密码本地MySQL数据库(库名webapp),目录结构清晰呈现标准Web应用分层:src/java为逻辑层,WEB-INF/web.xml定义路由,db.sql提供初始化脚本,css与images保障基础界面呈现。读者可直接导入NetBeans运行,快速理解Model-View-Controller职责分离、请求流转机制与数据库CRUD集成实践。

1. 一个被教科书反复提起、却总在真实部署时翻车的 JavaWeb 老架构:它真能跑通登录注册+留言板?

你可能在某高校《Java Web 程序设计》实验课上抄过三遍:index.jsp提交表单 →LoginServlet拿参数 →UserBean封装 →UserService查库 →response.sendRedirect("success.jsp")。但当你把这套代码拷进 Tomcat 9,改完web.xml,配好 MySQL 8 驱动,启动后点“注册”却跳 404,F12 看 Network 显示login.do405 Method Not Allowed——那一刻你就知道:这不是语法问题,是整个请求生命周期被黑匣子吃掉了。这个项目不是怀旧玩具,它是理解 Servlet 容器如何调度 JSP/Servlet/Bean 三者协作的最小可运行闭环。适合两类人:刚学完 HTTP 协议想验证“请求怎么变成页面”的新手;以及需要快速搭一个无框架、纯原生 JavaWeb 后台做嵌入式管理页的老手。它不碰 Spring Boot 的自动装配,不依赖 Maven 依赖管理,所有 class 文件直塞WEB-INF/classes,连web.xml都得手写<servlet-mapping>——这种“原始感”,恰恰是排查线上 Filter 链路、理解request.setAttribute()和request.getSession().setAttribute()差别的后悔药。


2. 项目结构与技术栈:为什么用 JSP+JavaBean+Servlet 而不是直接上 Spring?

2.1 目录结构:从WEB-INF开始的生存法则

项目采用最经典的 JavaWeb 目录规范,没有pom.xml,没有src/main/java,所有源码和配置都在扁平化目录下:

jsp_login_register/ ├── index.jsp # 首页(登录入口) ├── register.jsp # 注册页 ├── messageBoard.jsp # 留言板主页面 ├── success.jsp # 登录成功页 ├── error.jsp # 统一错误页 ├── WEB-INF/ │ ├── web.xml # 核心配置:Servlet 映射、欢迎文件、过滤器 │ ├── classes/ # 编译后的 .class 文件存放处(手动编译!) │ │ ├── bean/ # UserBean.class, MessageBean.class │ │ ├── servlet/ # LoginServlet.class, RegisterServlet.class, ... │ │ └── dao/ # UserDao.class, MessageDao.class(含 JDBC 连接逻辑) │ └── lib/ # mysql-connector-java-8.0.33.jar(必须放这里!) └── css/ # login.css(仅基础样式,无 Bootstrap)

提示:WEB-INF是 Tomcat 的安全边界——用户无法通过 URL 直接访问该目录下的任何资源(如http://localhost:8080/WEB-INF/web.xml会 404)。所有 Servlet 必须在此目录内声明,JSP 页面才能通过request.getRequestDispatcher()或response.sendRedirect()跳转到受保护资源。

2.2 技术选型逻辑:不是拒绝新框架,而是守住底层契约

  • JSP:承担 View 层,用<%= %>输出变量、<%-- --%>写注释、<jsp:useBean>声明 JavaBean 实例。不写 Java 代码块(<% %>)处理业务逻辑,只做展示控制(如if(user != null)判断登录态)。
  • JavaBean:严格遵循 POJO 规范——私有属性 + public getter/setter + 无参构造。例如UserBean中private String username;对应getUsername()/setUsername(),不加 Lombok,不加@Data,因为 Tomcat 6+ 的 JSP 引擎靠反射调用标准方法。
  • Servlet:继承HttpServlet,重写doPost()处理表单提交(doGet()仅用于重定向或获取验证码等轻量操作)。所有请求参数必须用request.getParameter("xxx")获取,不能依赖@RequestParam。
  • 数据库连接:使用DriverManager.getConnection()手动建连(非 Druid/HikariCP),连接字符串硬编码在Dao类中(教学场景下可接受,生产环境必须抽到properties文件)。

这种组合的价值在于:你能清晰看到 HTTP 请求头如何被HttpServletRequest解析,request.setAttribute()如何让数据跨 Servlet→JSP 传递,HttpSession如何通过 Cookie 维持用户状态——而这些,在 Spring MVC 的@Controller里全被ModelAndView封装成了黑盒。

2.3 关键依赖版本:Tomcat 与 MySQL 的兼容性玄学

  • Tomcat 版本:实测兼容Tomcat 8.5.x / 9.0.x(Tomcat 10+ 因 Jakarta EE 命名空间变更,javax.servlet.*全改为jakarta.servlet.*,会导致编译失败,必须降级)。
  • MySQL 驱动:必须用mysql-connector-java-8.0.33.jar(低版本如 5.1.47 不支持 MySQL 8 的caching_sha2_password认证插件,会报Access denied for user)。
  • JDK 版本:建议JDK 8u202+(JDK 11+ 需额外配置--add-opens参数解决模块化限制,新手易踩坑)。

注意:web.xml的web-app版本声明必须与 Tomcat 匹配。Tomcat 8.5 对应web-app_3_1.xsd,开头需写:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

3. 核心模块拆解:从LoginServlet到UserBean的完整链路

3.1LoginServlet.java:一个请求的生死七步

这是整个流程的中枢,我们逐行看它如何把username=admin&password=123变成跳转到success.jsp:

// src/servlet/LoginServlet.java public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Step 1:设置请求体编码,否则中文用户名乱码 request.setCharacterEncoding("UTF-8"); // Step 2:获取表单参数(注意:name 属性值必须与 JSP 中一致) String username = request.getParameter("username"); String password = request.getParameter("password"); // Step 3:实例化 JavaBean 并注入数据(MVC 中的 Model) UserBean user = new UserBean(); user.setUsername(username); user.setPassword(password); // Step 4:调用 DAO 层查询数据库(此处简化为 UserService 封装) UserService service = new UserService(); UserBean validUser = service.login(user); // 返回 null 表示失败 // Step 5:根据业务结果决定跳转路径 if (validUser != null) { // Step 6:将用户信息存入 session(维持登录态) request.getSession().setAttribute("currentUser", validUser); // Step 7:重定向到成功页(避免 F5 刷新重复提交) response.sendRedirect(request.getContextPath() + "/success.jsp"); } else { // 失败则转发到错误页,并携带错误信息 request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("/error.jsp").forward(request, response); } } }

关键参数说明:

  • request.setCharacterEncoding("UTF-8"):必须在getParameter()之前调用,否则 POST 提交的中文参数是??。GET 请求需在 Tomcat 的server.xml中给Connector加URIEncoding="UTF-8"。
  • request.getSession().setAttribute("currentUser", validUser):session是服务器端存储,"currentUser"是 key,后续所有 JSP 页面可通过${sessionScope.currentUser.username}获取。
  • response.sendRedirect()vsrequest.getRequestDispatcher().forward():前者是客户端重定向(URL 变,两次请求),后者是服务器端转发(URL 不变,一次请求)。登录成功用 redirect 防止刷新重复提交;登录失败用 forward 保留request.setAttribute()的错误信息。

3.2UserBean.java:JavaBean 的黄金三原则

这个类看似简单,却是整个数据流转的基石:

// src/bean/UserBean.java public class UserBean { private String username; private String password; private String email; // 注册时用 // 必须有无参构造(JSP 的 <jsp:useBean> 会反射调用) public UserBean() {} // getter/setter 必须严格匹配命名规范(IDEA 自动生成即可) public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }

为什么不用@Data?
因为 JSP 引擎(如 Jasper)在编译.jsp为.java时,会生成类似userBean.getUsername()的调用代码。如果getUsername()方法不存在(Lombok 未生效或编译顺序错乱),JSP 页面会直接抛javax.servlet.ServletException: java.lang.NoSuchMethodException。手写 getter/setter 是最可控的方式。

3.3UserService.java:DAO 层的极简实现

不引入 MyBatis 或 Hibernate,用原生 JDBC 演示数据访问本质:

// src/dao/UserService.java public class UserService { private static final String URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASS = "123456"; public UserBean login(UserBean user) { String sql = "SELECT id, username, email FROM users WHERE username = ? AND password = ?"; try (Connection conn = DriverManager.getConnection(URL, USER, PASS); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); // 注意:生产环境必须加密存储! ResultSet rs = ps.executeQuery(); if (rs.next()) { UserBean validUser = new UserBean(); validUser.setUsername(rs.getString("username")); validUser.setEmail(rs.getString("email")); return validUser; } } catch (SQLException e) { e.printStackTrace(); // 教学场景可接受,生产需日志框架 } return null; // 登录失败 } }

血泪经验:MySQL 8 默认密码认证插件是caching_sha2_password,而老版 JDBC 驱动不支持。解决方案只有两个:

  1. 在 MySQL 中执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
  2. 或升级驱动到 8.0.33+(推荐)。

4. 运行全流程:从零部署到页面可交互的 7 个硬核步骤

4.1 环境准备:三件套的版本锁死

  1. 下载 Tomcat 9.0.83(官网 archive 版,避免最新版的未知兼容问题)
  2. 下载 mysql-connector-java-8.0.33.jar(Maven Repository 搜索,不要用 5.x)
  3. 安装 JDK 8u202(Oracle 官网历史版本,或 Adoptium OpenJDK 8)

验证命令:

java -version # 输出应为 java version "1.8.0_202" $TOMCAT_HOME/bin/version.sh # Linux/macOS,Windows 用 version.bat

4.2 数据库初始化:一条 SQL 创建测试表

在 MySQL 中执行(注意字符集必须为utf8mb4):

CREATE DATABASE IF NOT EXISTS testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE testdb; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 生产环境存 BCrypt 加密串 email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入测试数据(密码明文,仅教学用) INSERT INTO users (username, password, email) VALUES ('admin', '123456', 'admin@example.com');

4.3 项目编译:手动 javac 的仪式感

进入项目根目录,按包路径创建classes目录结构:

# 创建编译输出目录(Linux/macOS) mkdir -p WEB-INF/classes/bean WEB-INF/classes/servlet WEB-INF/classes/dao # 编译 JavaBean(注意 -d 指定输出目录) javac -d WEB-INF/classes/ src/bean/UserBean.java src/bean/MessageBean.java # 编译 Servlet(需指定 classpath 包含 servlet-api.jar) javac -d WEB-INF/classes/ -cp "$TOMCAT_HOME/lib/servlet-api.jar" \ src/servlet/LoginServlet.java \ src/servlet/RegisterServlet.java \ src/servlet/MessageServlet.java # 编译 DAO(需同时包含 servlet-api.jar 和 mysql-connector.jar) javac -d WEB-INF/classes/ -cp "$TOMCAT_HOME/lib/servlet-api.jar:lib/mysql-connector-java-8.0.33.jar" \ src/dao/UserService.java \ src/dao/MessageDao.java

Windows 用户注意:-cp中的分隔符是;而非:,且路径需用双引号包裹空格路径。

4.4 Tomcat 部署:WAR 包不是必须的

将整个项目文件夹(jsp_login_register/)直接复制到$TOMCAT_HOME/webapps/下,无需打包成 WAR。Tomcat 启动时会自动将其识别为 Context Path/jsp_login_register。

4.5 启动与验证:浏览器里的第一声心跳

  1. 启动 Tomcat:$TOMCAT_HOME/bin/startup.sh(Linux/macOS)或startup.bat(Windows)
  2. 访问http://localhost:8080/jsp_login_register/index.jsp
  3. 输入admin/123456,点击登录 → 应跳转到success.jsp,页面显示欢迎 admin!
  4. 查看 Tomcat 控制台日志,确认无ClassNotFoundException或SQLException

现象排查:若页面空白或 500 错误,立即查看$TOMCAT_HOME/logs/catalina.out,搜索Caused by:—— 90% 的问题是mysql-connector.jar没放对位置(必须在WEB-INF/lib/,不能放Tomcat/lib/)。


5. 避坑指南:那些让你调试到凌晨三点的典型问题

5.1 现象:HTTP Status 404 – Not Found,但index.jsp明明存在

原因:web.xml中<welcome-file-list>未配置,或 Tomcat 未扫描到该文件。
解决:检查WEB-INF/web.xml是否包含:

<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>

并确认index.jsp位于项目根目录(与WEB-INF同级),而非WEB-INF内部。

5.2 现象:登录后success.jsp中${sessionScope.currentUser.username}显示为空

原因:LoginServlet中用了request.setAttribute()而非session.setAttribute(),或 JSP 页面未启用 EL 表达式。
解决:

  • 确认 Servlet 中是request.getSession().setAttribute("currentUser", user);
  • 在success.jsp顶部添加<%@ page isELIgnored="false" %>(Tomcat 8+ 默认开启,但某些 IDE 生成的模板会设为 true)。

5.3 现象:MySQL 报错java.sql.SQLException: The server time zone value 'XXX' is unrecognized

原因:JDBC 连接字符串未指定serverTimezone,且 MySQL 服务端时区与系统不一致。
解决:修改UserService.java中的 URL 为:

private static final String URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=GMT%2B8";

(GMT%2B8是 URL 编码后的GMT+8)

5.4 现象:注册时中文用户名存入数据库变成???

原因:三个环节任一缺失 UTF-8 设置:

  • MySQL 表字符集不是utf8mb4;
  • JDBC 连接字符串未加characterEncoding=utf8;
  • JSP 页面未声明<%@ page contentType="text/html;charset=UTF-8" %>。
    解决:
  1. MySQL 执行ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. JDBC URL 改为:...?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8
  3. 所有 JSP 顶部加<%@ page contentType="text/html;charset=UTF-8" %>

5.5 现象:LoginServlet编译报错cannot find symbol: class HttpServlet

原因:javac未引入servlet-api.jar作为编译依赖。
解决:编译命令必须带-cp参数:

javac -cp "$TOMCAT_HOME/lib/servlet-api.jar" -d WEB-INF/classes/ src/servlet/LoginServlet.java

切记:servlet-api.jar仅用于编译,不能打包进WEB-INF/lib/,否则 Tomcat 会因类加载冲突报错。


6. 二次开发实战:从留言板到可扩展后台的 3 个改造技巧

6.1 技巧一:用Filter统一处理登录校验,告别每个 Servlet 重复写 session 判断

当前messageBoard.jsp直接访问,任何人都能看留言。要实现“未登录用户访问留言板自动跳转登录页”,需添加LoginFilter:

// src/filter/LoginFilter.java 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; // 白名单:登录页、注册页、静态资源放行 String uri = request.getRequestURI(); if (uri.endsWith("/index.jsp") || uri.endsWith("/register.jsp") || uri.contains("/css/") || uri.contains("/images/")) { chain.doFilter(req, resp); return; } // 校验 session 中是否存在 currentUser HttpSession session = request.getSession(false); if (session == null || session.getAttribute("currentUser") == null) { response.sendRedirect(request.getContextPath() + "/index.jsp"); return; } chain.doFilter(req, resp); } }

配置web.xml:

<filter> <filter-name>LoginFilter</filter-name> <filter-class>filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/*</url-pattern> <!-- 拦截所有请求 --> </filter-mapping>

为什么比在每个 Servlet 里判断更优?

  • 解耦:业务逻辑(MessageServlet)不再关心权限,专注数据处理;
  • 可维护:修改登录规则只需改 Filter,不用动十几个 Servlet;
  • 符合 Servlet 规范:Filter 是官方定义的横切关注点(AOP)实现方式。

6.2 技巧二:将数据库配置外置为db.properties,避免硬编码泄露

新建WEB-INF/classes/db.properties:

jdbc.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

修改UserService.java,用ResourceBundle读取:

private static ResourceBundle bundle = ResourceBundle.getBundle("db"); private static final String URL = bundle.getString("jdbc.url"); private static final String USER = bundle.getString("jdbc.username"); private static final String PASS = bundle.getString("jdbc.password");

优势:上线时只需替换db.properties,无需重新编译 Java 类;DBA 可独立管理数据库凭证。

6.3 技巧三:为留言板增加分页功能,用LIMIT offset, size实现真·物理分页

当前MessageServlet一次性查出所有留言,数据量大时页面卡死。改造如下:

// 在 MessageServlet 中 int pageSize = 10; int currentPage = Integer.parseInt(request.getParameter("page")); // 从 URL ?page=2 获取 int offset = (currentPage - 1) * pageSize; String sql = "SELECT id, content, create_time FROM messages ORDER BY create_time DESC LIMIT ?, ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, offset); ps.setInt(2, pageSize);

前端 JSP 分页导航(messageBoard.jsp中):

<!-- 计算总页数 --> <% int totalRecords = 123; // 从 DAO 查询 SELECT COUNT(*) 得到 int totalPages = (int) Math.ceil((double) totalRecords / 10); %> <!-- 生成页码链接 --> <div class="pagination"> <c:forEach begin="1" end="${totalPages}" var="i"> <a href="messageBoard.jsp?page=${i}">${i}</a> </c:forEach> </div>

关键点:LIMIT是 MySQL 物理分页,比 Java 内存分页(查全量再subList())性能高 10 倍以上,尤其当留言表超 10 万行时。

从那以后我每次接手老系统维护,第一件事就是检查web.xml里有没有 Filter 链,第二件事是翻WEB-INF/classes/下有没有*.properties配置文件——这两处藏着系统最真实的运行契约。而这个 JSP+Servlet+JavaBean 项目,就是我用来验证自己是否还看得懂底层脉络的试金石。希望帮到你。

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