简介:这份资源面向JavaWeb初学者与课程实践者,提供一套可直接运行的登录与注册功能完整实现,帮助理解JSP页面展示、Servlet请求处理与MySQL数据存储的协作流程。压缩包共34个文件,约3.56MB,包含7个java源码与7个class编译文件、3个jsp页面、2个xml配置、2个properties属性文件,以及sql建表脚本、jar依赖、js脚本和项目工程配置等,覆盖从数据库建表到前端表单提交的完整链路。资源中已包含可导入的工程结构与数据库脚本,便于对照学习参数化查询防注入、session保存登录状态、密码加密存储及注册信息校验等关键细节,也能作为课程设计或毕业设计的起步模板。目前已有3049人学习下载,适合希望快速跑通JavaWeb登录注册案例、并在此基础上扩展错误提示与页面跳转逻辑的读者参考。
1. 从零搭一套 JSP + Java + MySQL 登录注册:这套源码能省掉你多少试错时间
如果你正在带新人、赶课程设计,或者需要给一个小型管理系统快速补上用户模块,那这套基于 JavaWeb 的登录注册实现大概率能直接塞进你的项目里。它不玩花活,就是最经典的 JSP + Servlet + MySQL 组合,把注册、登录、会话保持、表单校验这几件事串成了一条完整的链路。适合谁?刚学完 Servlet 但不知道怎么写进真实页面的人,以及需要一套能跑通的骨架去改造成自己业务逻辑的人。我见过太多人卡在“知道 JDBC 怎么连,但不知道 JSP 怎么把参数递给 Servlet”这一步,这套东西就是冲着这个断层来的。它解决的不是高并发登录,而是让你先把一条从浏览器到数据库再回到页面的完整路径跑通,后面加验证码、加拦截器、加密码加密才有地方下手。
2. 拆开这套登录注册的骨架:JSP 页面、Servlet 控制器与 MySQL 表结构怎么对上
2.1 三层结构在代码里到底长什么样
这套实现走的是最朴素的 MVC 变体:JSP 负责显示,Servlet 负责接收请求和跳转,DAO 负责跟 MySQL 打交道。别小看这个结构,很多新手写登录注册时会把 JDBC 代码直接写在 JSP 的<% %>里,结果页面一多就变成意大利面条。这套源码把数据库操作抽到了 DAO 层,Servlet 里只做参数校验和业务判断,JSP 里几乎看不到 Java 逻辑。
具体来说,你会看到这几个核心文件:login.jsp和register.jsp是表单页,LoginServlet和RegisterServlet是控制器,UserDao封装了findByUsername和insertUser两个方法,DBUtil负责拿连接。表结构也简单,一张user表,字段就是id、username、password、email、create_time。这种设计的好处是,你后面想把密码从明文改成 MD5 或者 BCrypt,只需要动UserDao里的插入和查询逻辑,页面和 Servlet 基本不用碰。
我一般会先看DBUtil是怎么写的,因为连接管理是最容易翻车的地方。这套代码用的是DriverManager直接拿连接,没有上连接池。对于课程设计或者日活几十个人的内部工具,这没问题;但如果你打算放到稍微有点并发的地方,建议换成 Druid 或者 HikariCP。换的时候注意,DBUtil里的getConnection()和close()方法要成对改,别只改一半。
2.2 数据库建表与连接配置的实操步骤
先把库和表建起来,别急着跑代码。我见过有人直接导入项目,结果忘了建库,控制台报Unknown database还找了半天。下面这段 SQL 可以直接在 MySQL 客户端里执行:
-- 创建数据库,字符集用 utf8mb4,避免中文用户名乱码 CREATE DATABASE IF NOT EXISTS javaweb_auth DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE javaweb_auth; -- 用户表,id 自增,username 加唯一索引防止重复注册 CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL, `password` VARCHAR(100) NOT NULL, `email` VARCHAR(100) DEFAULT NULL, `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;建完表之后,去改DBUtil里的连接参数。常见做法是把 URL、用户名、密码写在一个db.properties文件里,然后用Properties类加载。这样你换环境的时候不用重新编译 Java 文件。参数说明:useSSL=false是关掉 SSL 警告,serverTimezone=Asia/Shanghai是解决时区差 8 小时的问题,characterEncoding=utf8是让 JDBC 用 UTF-8 编码传数据。这三个参数少一个都可能出玄学问题。
// DBUtil.java 核心片段 private static final String URL = "jdbc:mysql://localhost:3306/javaweb_auth?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 8 驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException("驱动加载失败,检查 mysql-connector-java 是否放进 WEB-INF/lib", e); } return DriverManager.getConnection(URL, USER, PASSWORD); }注意驱动版本。MySQL 5.x 用com.mysql.jdbc.Driver,8.x 用com.mysql.cj.jdbc.Driver。如果你把 8.x 的驱动配了 5.x 的类名,启动就报ClassNotFoundException。另外,mysql-connector-java的 jar 包必须放在WEB-INF/lib下,别只加到 IDE 的 build path 里,Tomcat 跑的时候不认 IDE 的配置。
2.3 注册与登录的请求流转:从表单到 Servlet 再到跳转
注册页register.jsp的表单 action 指向RegisterServlet,method 用 post。这里有个细节:表单里的 input name 必须和 Servlet 里request.getParameter()的参数名完全一致,大小写敏感。我见过有人写userName和username对不上,排查了半小时。
<!-- register.jsp 表单核心部分 --> <form action="RegisterServlet" method="post"> 用户名:<input type="text" name="username" required><br> 密码:<input type="password" name="password" required><br> 邮箱:<input type="email" name="email"><br> <input type="submit" value="注册"> </form>Servlet 里先拿参数,再做非空判断,然后调 DAO 查重,最后插入。查重这一步不能省,虽然数据库有唯一索引,但直接插会抛SQLIntegrityConstraintViolationException,页面报 500 很难看。正确做法是先findByUsername,如果返回非空就跳回注册页并提示“用户名已存在”。
// RegisterServlet 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 必须放在 getParameter 之前 String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); UserDao dao = new UserDao(); if (dao.findByUsername(username) != null) { request.setAttribute("msg", "用户名已存在"); request.getRequestDispatcher("register.jsp").forward(request, response); return; } User user = new User(username, password, email); dao.insertUser(user); response.sendRedirect("login.jsp"); // 注册成功跳登录页 }登录的逻辑类似,但多了一步 session 写入。LoginServlet查到用户后,把 user 对象放进request.getSession().setAttribute("user", user),然后重定向到主页。后面你想做登录拦截,就写一个 Filter 检查 session 里有没有 user,没有就踢回登录页。这套源码里通常不带 Filter,但这是你拿到之后最值得加的第一个扩展。
3. 跑起来之前先把环境钉死:Tomcat 版本、JDK 与依赖 jar 的匹配清单
3.1 版本错配是启动失败的第一大来源
这套代码能不能跑,八成取决于你的 Tomcat 和 JDK 版本对不对。我整理了一个对照表,照着选基本不会翻车:
| 组件 | 推荐版本 | 不兼容表现 |
|---|---|---|
| JDK | 8 或 11 | JDK 17 跑老 Servlet 可能报NoClassDefFoundError |
| Tomcat | 8.5 或 9.0 | Tomcat 10 的包名从javax.servlet变成jakarta.servlet,直接编译不过 |
| MySQL | 5.7 或 8.0 | 8.0 需要cj驱动和时区参数 |
| mysql-connector-java | 8.0.28 左右 | 版本太低连 8.0 的库会报认证插件错误 |
Tomcat 10 那个坑特别典型。你从网上下的源码,import 的是javax.servlet.http.HttpServlet,放到 Tomcat 10 里启动就报ClassNotFoundException。解决办法要么换 Tomcat 9,要么把源码里所有javax.servlet批量替换成jakarta.servlet。我一般建议直接换 Tomcat 9,省事。
3.2 在 IDE 里配置运行环境的几个关键动作
以 Eclipse 为例,新建 Dynamic Web Project 之后,把源码里的src和WebContent分别拷进去。然后右键项目 → Properties → Project Facets,确认 Dynamic Web Module 版本是 3.1 或 4.0。接着去 Targeted Runtimes 里勾上你的 Tomcat。最后检查WebContent/WEB-INF/lib下有没有mysql-connector-java-8.0.28.jar,没有就拖进去。
如果你用 IDEA,步骤类似,但注意 Artifacts 配置。在 Project Structure → Artifacts 里,确保WEB-INF/lib下的 jar 都被包含进输出。我遇到过 IDEA 里跑得好好的,一打成 war 包部署到独立 Tomcat 就报ClassNotFoundException,就是因为 Artifacts 没把 lib 目录打进去。
启动之后,浏览器访问http://localhost:8080/你的项目名/register.jsp。先测注册,再测登录。如果注册成功但登录报“用户名或密码错误”,去数据库里看一眼password字段存的是什么。有时候是表单编码问题导致密码里多了空格,有时候是 DAO 查询条件写成了WHERE username = ? AND password = ?但传参顺序反了。
提示:每次改完 Java 代码,记得在 Tomcat 里重新 publish 再重启。只重启不 publish,跑的还是旧 class 文件,这种问题排查起来最耗时间。
4. 避坑与排查:登录注册跑不通时先看这五个地方
4.1 中文用户名注册后变成问号
现象:注册时输入中文用户名,数据库里存的是???,登录时怎么都匹配不上。原因:JDBC URL 里没加characterEncoding=utf8,或者数据库和表的字符集不是utf8mb4。解决:先确认建库建表语句带了DEFAULT CHARSET=utf8mb4,再检查 JDBC URL 里的编码参数。另外,request.setCharacterEncoding("UTF-8")必须写在所有getParameter之前,写在后面无效。
4.2 表单提交后浏览器报 404
现象:点注册按钮,页面跳转到http://localhost:8080/项目名/RegisterServlet显示 404。原因:web.xml里没配 Servlet 映射,或者注解@WebServlet("/RegisterServlet")的路径和表单 action 不一致。解决:如果用注解,确认@WebServlet的 value 是/RegisterServlet;如果用web.xml,检查<url-pattern>有没有写错。还有一种情况是项目名变了,表单 action 写的是相对路径,但页面本身在子目录里,相对路径就偏了。建议 action 统一用${pageContext.request.contextPath}/RegisterServlet。
4.3 数据库连接池耗尽或连接不释放
现象:连续点几次登录,后面就卡住不动,控制台报Too many connections。原因:DBUtil里拿了连接但没在 finally 里关闭,每次请求都泄漏一个连接。解决:所有 DAO 方法里,Connection、PreparedStatement、ResultSet都要在 finally 块里 close。如果嫌麻烦,至少把Connection的关闭写对。常见做法是用 try-with-resources,但老代码里手动关也行,关键是别漏。
// 正确的关闭顺序:ResultSet -> PreparedStatement -> Connection public User findByUsername(String username) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); ps = conn.prepareStatement("SELECT * FROM user WHERE username = ?"); ps.setString(1, username); rs = ps.executeQuery(); if (rs.next()) { return new User(rs.getInt("id"), rs.getString("username"), rs.getString("password")); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if (rs != null) rs.close(); } catch (SQLException e) {} try { if (ps != null) ps.close(); } catch (SQLException e) {} try { if (conn != null) conn.close(); } catch (SQLException e) {} } return null; }4.4 密码明文存储被扫出来
现象:数据库里password字段一眼就能看到123456。原因:源码默认没做加密。解决:至少上 MD5,虽然 MD5 也不安全,但比明文强。更好的做法是用 BCrypt,加个jbcrypt的 jar 包,注册时BCrypt.hashpw(password, BCrypt.gensalt()),登录时BCrypt.checkpw(input, stored)。注意 MD5 和 BCrypt 的哈希长度不一样,改的时候要把password字段长度从 100 改成 255,不然存不下。
4.5 登录成功后刷新页面又退回登录页
现象:登录成功跳到了主页,按 F5 刷新一下,又回到登录页。原因:session 超时时间太短,或者主页没有正确读取 session 里的 user 对象。解决:检查web.xml里的<session-config>,默认是 30 分钟,一般够用。如果主页是 JSP,确认有没有写session.getAttribute("user")的判断逻辑。还有一种可能是你用了response.sendRedirect跳转,但目标页面又做了拦截,拦截器里判断 session 为 null 就踢回去了。这时候要看拦截器的排除路径有没有把主页漏掉。
5. 拿到源码之后先做这三件事:改密码加密、加登录拦截、换连接池
5.1 把明文密码换成 BCrypt 的完整改法
这套源码默认存明文,你拿到之后第一件事就该把它换掉。去 Maven 仓库下jbcrypt-0.4.jar,丢进WEB-INF/lib。然后在RegisterServlet里把new User(username, password, email)改成new User(username, BCrypt.hashpw(password, BCrypt.gensalt()), email)。登录那边,UserDao.findByUsername返回 user 之后,不要直接比较密码,而是用BCrypt.checkpw(inputPassword, user.getPassword())。注意checkpw的第一个参数是用户输入的明文,第二个是数据库里存的哈希值,顺序别反。
改完之后,之前注册的明文用户就登不上了。解决办法要么清空 user 表重新注册,要么写个临时脚本把旧密码批量哈希一遍。我一般建议直接清表,反正是测试数据。
5.2 加一个 Filter 做登录拦截,防止直接访问主页
没有拦截的系统,用户直接在地址栏敲主页 URL 就能进去,登录形同虚设。写一个LoginFilter,实现javax.servlet.Filter,在doFilter里判断request.getSession().getAttribute("user")是否为空。为空就response.sendRedirect("login.jsp"),不为空就chain.doFilter(request, response)。然后在@WebFilter里配置拦截路径,比如/*,但要把login.jsp、register.jsp、LoginServlet、RegisterServlet和静态资源排除掉,不然登录页本身也被拦了。
@WebFilter("/*") public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String uri = request.getRequestURI(); // 放行登录注册相关路径和静态资源 if (uri.endsWith("login.jsp") || uri.endsWith("register.jsp") || uri.contains("LoginServlet") || uri.contains("RegisterServlet") || uri.contains(".css") || uri.contains(".js")) { chain.doFilter(req, res); return; } if (request.getSession().getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); } else { chain.doFilter(req, res); } } }5.3 连接池替换与压力测试的验证方法
DriverManager每次拿连接都新建一个 TCP 连接,开销大。换成 Druid 之后,连接复用,响应会快很多。步骤:下druid-1.2.x.jar丢进 lib,写一个druid.properties放连接参数,然后用DruidDataSourceFactory.createDataSource(properties)拿DataSource。DBUtil.getConnection()改成从dataSource.getConnection()返回。改完之后怎么验证?用 JMeter 或者简单的ab命令压一下登录接口,看响应时间有没有下降。更简单的办法是在DBUtil里加个静态计数器,每次拿连接加一,跑一百次登录看计数器是不是远小于一百,是的话说明连接池生效了。
从那以后我每次拿到一套登录注册源码,都强制先跑一遍“注册→退出→登录→刷新→直接访问主页”这五步,少一步都不放心。希望帮到你。
本文还有配套的精品资源,点击获取