资讯动态

从零手写Servlet+JSP用户注册系统:理解Java Web本质的必经之路

发布时间:2026/10/15 7:52:14 来源:尧图企业网站定制
1. 从零搭建一个用户注册系统我为什么选这条最笨的路很多人一听到Java Web用户注册脑子里第一反应就是上Spring Boot、上MyBatis、上各种脚手架恨不得三分钟跑起来一个项目。我当年也是这么想的结果第一次带一个完全没碰过Web的学弟做东西的时候发现他连Tomcat是什么、Servlet怎么跑起来的都说不清楚直接上框架等于让他背黑锅。所以这次我决定反过来用最原始、最笨、最简陋的方式把用户注册这条链路从头到尾走一遍——从装JDK开始到数据库建表到写Servlet到JSP页面到跑通整个注册流程。这个标题里有个词我特别喜欢叫简陋。简陋不是贬义它意味着没有魔法。你写的每一行代码你都知道它在哪里被执行、为什么被执行。等你把这条简陋的路走通了再去学Spring Boot你会发现那些注解、那些自动配置本质上都是在帮你做你手动做过的这些事情。这就是我坚持用这种方式教学的原因先理解本质再享受便利。这篇文章适合谁看如果你是刚学完Java基础语法、想找个东西练手但不知道从哪下手的学生如果你是做前端或者测试想了解一下后端注册流程到底是怎么回事如果你是被各种框架绕晕了、想回头看看没有框架的时代是怎么写代码的——那这篇内容就是给你准备的。我会把每一步都拆开讲包括我踩过的坑、参数为什么这么配、报错了怎么排查尽量做到你照着做就能跑通。整个项目我把它叫做简陋洛谷因为它就是一个极简的用户系统有注册、有登录、有用户列表展示界面丑得一批但功能是完整的。我们不追求好看我们追求每一行代码都能讲清楚。2. 环境准备别小看装环境这一步一半的人卡在这2.1 JDK的选择与安装版本别乱选先说JDK。现在网上教程有的让你装JDK 8有的让你装JDK 17有的让你装JDK 21新手一看就懵。我的建议是如果你是要跟着老教程学Servlet和JSP装JDK 8或者JDK 11。原因很简单Servlet和JSP这套东西在JDK 8时代是最成熟的很多老教程、老项目都是基于这个版本你装个JDK 21上去可能会遇到一些莫名其妙的兼容问题比如某些老版本的Tomcat跑不起来。安装过程本身不复杂去官网下载对应系统的安装包一路下一步就行。但有两个地方要注意第一个是安装路径不要有中文和空格。我见过太多人把JDK装在Program Files下面路径里带个空格然后配置环境变量的时候各种报错。建议直接装在C:\Java\jdk1.8.0_xxx这种纯英文无空格的路径下。第二个是环境变量配置。你需要配两个东西JAVA_HOME和Path。JAVA_HOME指向你的JDK安装目录比如C:\Java\jdk1.8.0_xxx然后在Path里加上%JAVA_HOME%\bin。配完之后打开命令行输入java -version如果能看到版本号说明装好了。提示如果你输入java -version提示不是内部或外部命令99%是环境变量没配对。检查一下JAVA_HOME的路径是不是多了一层bin很多人会犯这个错。2.2 Tomcat的下载与目录结构解读Tomcat是Servlet容器说白了就是你的Java Web代码跑在它里面。下载的时候注意版本对应关系Tomcat 8.5和Tomcat 9对应Servlet 3.1/4.0适合搭配JDK 8Tomcat 10以上对应Servlet 5.0包名从javax.servlet变成了jakarta.servlet这个变化会让很多老代码直接编译不过。所以如果你跟着老教程走下Tomcat 9最稳妥。下载下来是个压缩包解压到任意目录就行不需要安装。解压后你会看到这些目录目录名作用bin存放启动和关闭脚本startup.bat和shutdown.bat就在这里conf配置文件最重要的server.xml和web.xmllibTomcat运行需要的jar包logs日志文件排查问题必看webapps你的Web项目就放在这里workTomcat运行时的临时文件JSP编译后的Servlet就放这双击bin/startup.bat启动Tomcat然后浏览器访问http://localhost:8080如果看到Tomcat的欢迎页面说明启动成功。如果闪退多半是JAVA_HOME没配好去看logs目录下的日志。2.3 IDE的选择Eclipse还是IDEA这个真的看个人习惯。Eclipse免费老教程多配置Tomcat稍微麻烦一点IDEA社区版免费但Web支持有限旗舰版要钱但配置Tomcat非常方便。我个人的建议是如果你完全零基础用Eclipse因为网上基于Eclipse的Servlet教程最多你遇到问题容易搜到答案。如果你已经有一定基础直接上IDEA旗舰版省下来的配置时间够你多写好几个功能了。不管用哪个核心都是一样的创建一个Dynamic Web Project配置好Tomcat运行时然后开始写代码。IDE只是工具别在选工具上纠结太久。3. 项目结构设计为什么我要这么分层3.1 一个简陋但合理的包结构很多人写练手项目所有代码都堆在一个包甚至一个类里能跑就行。但我想说的是即使是练手项目也请养成分层的好习惯因为这是你以后写任何项目的肌肉记忆。我这个简陋洛谷用了最经典的三层结构com.simpleluogu ├── dao // 数据访问层负责和数据库打交道 │ └── UserDao.java ├── entity // 实体层对应数据库表的Java类 │ └── User.java ├── servlet // 控制层接收请求、调用业务、返回响应 │ ├── RegisterServlet.java │ └── LoginServlet.java └── util // 工具类比如数据库连接 └── DBUtil.java为什么这么分因为每一层只干一件事。Servlet只负责处理HTTP请求它不关心数据怎么存Dao只负责和数据库交互它不关心请求从哪来。这样当你以后要换数据库、换前端、加新功能的时候改动范围是可控的。新手最容易犯的错就是把数据库连接代码直接写在Servlet里结果要改个数据库密码得翻遍所有Servlet。3.2 数据库表设计字段不多但每个都有讲究用户表我设计得很简单就几个字段CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );看起来简单但每个字段都有说法。id用自增主键这是标配username加了UNIQUE约束这是在数据库层面保证用户名不重复比你在Java代码里先查再插要可靠得多因为并发情况下先查再插是有漏洞的password长度给到100是因为如果你以后要存加密后的密码比如MD5是32位加盐之后可能更长50可能不够create_time用DEFAULT CURRENT_TIMESTAMP插入的时候不用手动传数据库自动填。注意练手项目里密码可以明文存但真实项目绝对不能明文存密码。哪怕你现在是练手也建议养成习惯用MD5或者更安全的算法处理一下。我后面会讲怎么加这个。3.3 前端页面丑没关系但要能跑通前端我就写了两个JSP页面register.jsp和login.jsp。没有用任何CSS框架就是最朴素的HTML表单。为什么不用Bootstrap因为引入框架会引入额外的变量你可能会遇到路径问题、版本问题而这些问题和你真正要学的Servlet没有半毛钱关系。等你把注册流程跑通了再回头美化界面那是另一回事。表单的action指向Servlet的映射路径method用post。这里有个新手常踩的坑中文乱码。如果你用post提交Servlet里第一件事就是request.setCharacterEncoding(UTF-8)如果你用get提交那得改Tomcat的server.xml麻烦得多。所以注册登录这种表单一律用post。4. 核心代码实现一行一行讲清楚4.1 数据库连接工具类别每次都重新造轮子DBUtil这个类的作用就一个给我一个数据库连接。代码如下public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/simple_luogu?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }几个关键点解释一下。Class.forName是加载驱动MySQL 8以上的驱动类是com.mysql.cj.jdbc.Driver8以下的是com.mysql.jdbc.Driver写错了会报ClassNotFoundException。URL里的useSSLfalse是关掉SSL警告serverTimezoneUTC是设置时区不加这个在MySQL 8上可能会报时区错误。提示这个工具类每次调用getConnection都会新建一个连接在练手项目里没问题但真实项目里要用连接池比如Druid、HikariCP否则并发一高数据库就扛不住了。这是你以后要学的现在先知道有这么回事就行。4.2 实体类User和数据库表一一对应实体类就是数据库表的Java映射字段名和表字段对应提供getter和setter。这个没什么好讲的但有一点要注意字段类型要和数据库类型匹配。id用int或Integercreate_time用java.util.Date或java.sql.Timestamp。如果你用Integer而不是int好处是可以表示null但练手项目里用int就够了。4.3 Dao层注册和查询的核心逻辑UserDao里我写了两个方法findByUsername和insertUser。为什么注册之前要先查一次因为虽然数据库有UNIQUE约束但提前查一次能给用户更友好的提示而不是直接抛一个数据库异常给用户看。public User findByUsername(String username) { String sql SELECT * FROM user WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }这里用的是PreparedStatement而不是Statement这是防SQL注入的关键。如果你用字符串拼接写SQL别人在用户名里输入 OR 11你的查询条件就被篡改了。PreparedStatement用占位符?参数是分开传的从根本上杜绝了这个问题。这是新手必须养成的习惯没有例外。insertUser方法类似只是把executeQuery换成executeUpdate返回值是受影响的行数大于0就说明插入成功。4.4 Servlet层请求怎么进来响应怎么出去RegisterServlet是整个注册流程的入口。它的doPost方法大概长这样protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String email request.getParameter(email); UserDao userDao new UserDao(); if (userDao.findByUsername(username) ! null) { request.setAttribute(msg, 用户名已存在); request.getRequestDispatcher(register.jsp).forward(request, response); return; } User user new User(); user.setUsername(username); user.setPassword(password); user.setEmail(email); if (userDao.insertUser(user) 0) { response.sendRedirect(login.jsp); } else { request.setAttribute(msg, 注册失败请重试); request.getRequestDispatcher(register.jsp).forward(request, response); } }这段代码里有几个值得说的点。第一行setCharacterEncoding必须在获取任何参数之前调用否则不生效。forward和sendRedirect的区别forward是服务器内部跳转URL不变可以传递request里的属性sendRedirect是告诉浏览器重新请求一个新地址URL会变request属性会丢失。注册成功用sendRedirect是为了防止用户刷新页面重复提交表单这是个很实用的技巧。4.5 web.xml配置Servlet的身份证Servlet 3.0以后可以用注解WebServlet(/register)来配置映射但老项目里都是在web.xml里配的。两种方式我都说一下你用哪种都行。注解方式很简单在Servlet类上面加一行WebServlet(/register) public class RegisterServlet extends HttpServlet { ... }web.xml方式稍微麻烦一点servlet servlet-nameRegisterServlet/servlet-name servlet-classcom.simpleluogu.servlet.RegisterServlet/servlet-class /servlet servlet-mapping servlet-nameRegisterServlet/servlet-name url-pattern/register/url-pattern /servlet-mapping两种方式效果一样注解更简洁web.xml更集中。我建议新手先用web.xml因为你能直观地看到请求路径和Java类之间的对应关系理解起来更清楚。5. 踩坑实录这些问题我几乎每次都遇到5.1 中文乱码三种场景三种解法中文乱码是Java Web新手遇到最多的问题没有之一。它分三种情况第一种表单提交乱码。如果是post提交在Servlet里加request.setCharacterEncoding(UTF-8)就行。如果是get提交得去改Tomcat的server.xml在Connector标签里加URIEncodingUTF-8。第二种响应输出乱码。在Servlet里输出中文之前加response.setContentType(text/html;charsetUTF-8)。第三种数据库乱码。建库建表的时候指定字符集CHARACTER SET utf8mb4连接URL里也可以加characterEncodingutf8。提示utf8和utf8mb4的区别在于后者支持emoji等四字节字符。练手项目用utf8够了但养成用utf8mb4的习惯没坏处。5.2 驱动加载失败版本对不上是主因ClassNotFoundException: com.mysql.jdbc.Driver这个错误八成是因为你用的MySQL驱动版本和驱动类名对不上。MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.x用com.mysql.cj.jdbc.Driver。另外驱动jar包要放到WEB-INF/lib目录下不是项目的随便哪个地方。放错了位置编译能过运行就报错。5.3 404和500两个最让人抓狂的错误码404是找不到通常是URL写错了或者Servlet映射配错了。排查方法先确认Tomcat启动日志里有没有你的项目再确认浏览器地址栏的路径和url-pattern是否一致。500是服务器内部错误通常是Java代码抛异常了。排查方法看Tomcat的logs目录下的日志或者看IDE的控制台输出异常堆栈会告诉你具体哪一行出了问题。新手最容易犯的错是只看浏览器页面不去看后台日志然后到处问为什么报500其实日志里写得清清楚楚。5.4 常见问题速查表问题现象可能原因解决方法启动Tomcat闪退JAVA_HOME未配置或配置错误检查环境变量看logs目录访问项目404项目未部署或路径错误检查webapps目录和URL表单提交后无反应action路径写错检查form的action和Servlet映射数据库连接失败驱动未加载或URL错误检查驱动jar和连接字符串中文显示问号字符编码不一致统一设置为UTF-8用户名重复插入成功未加UNIQUE约束修改表结构加唯一约束6. 从简陋到完善这个项目还能怎么扩展6.1 密码加密MD5加盐是最低要求前面说了密码不能明文存。最简单的做法是用MD5但单纯MD5也不安全因为彩虹表可以反查。正确做法是加盐给每个用户生成一个随机字符串盐把密码和盐拼在一起再做MD5把盐和结果一起存到数据库。这样即使两个用户密码相同存进去的结果也不一样。public static String md5WithSalt(String password, String salt) { String combined password salt; // 用MessageDigest做MD5转成十六进制字符串 // 具体实现略核心思路就是加盐再哈希 }6.2 加个验证码防止机器人批量注册练手项目可以不加但如果你想让它稍微像个正经系统加个验证码是很好的练习。最简单的做法是用Java的BufferedImage画一张图把随机字符画上去同时存到session里提交的时候比对。这个过程能让你理解session的用法一举两得。6.3 用连接池替换裸连接前面提过DBUtil每次新建连接的方式在真实场景下不可用。你可以试着引入Druid或者HikariCP把getConnection改成从连接池里取。改动不大但能让你理解连接池的原理和价值。6.4 从Servlet到Spring Boot的平滑过渡等你把这个简陋版本跑通了再去看Spring Boot的RestController、Autowired、JdbcTemplate你会发现它们做的事情和你手动做的一模一样只是帮你封装好了。这时候你学框架就不是背注解而是理解它在帮我做什么。这个顺序比一上来就学框架要扎实得多。我在带人的过程中发现那些先手写一遍Servlet再学Spring Boot的人遇到框架问题时的排查能力明显更强因为他们知道框架底层大概在干什么。而那些直接上框架的人一旦自动配置失效就完全不知道从哪下手了。所以别嫌简陋简陋是你理解本质的捷径。最后分享一个小技巧如果你在调试注册流程的时候不确定数据到底有没有插进数据库别急着写查询代码直接打开数据库客户端工具SELECT * FROM user看一眼比什么都快。很多时候问题不在你的Java代码而在你以为插进去了其实没插进去。这种先确认事实再排查逻辑的习惯能帮你省下大量时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑