Java Servlet 从入门到实战——用登录注册系统带你彻底搞懂 Servlet
Servlet 是运行在服务器端的 Java 程序,用于处理客户端(浏览器)发来的 HTTP 请求,并返回响应。浏览器访问一个网址 → 请求到达 Tomcat → Tomcat 找到对应的 Servlet → Servlet 处理逻辑 → 返回 HTML 页面给浏览器Servlet(接口)└── GenericServlet(抽象类)└── HttpServlet(抽象类) ← 我们写 Servl
本文通过一个完整的"选课管理系统"登录注册项目,手把手带你学习 Java Servlet 的核心知识。适合没有任何 Servlet 基础、想快速上手的同学。
目录
- 课前准备:你需要装什么
- Servlet 是什么?一张图看懂
- 5 分钟写出你的第一个 Servlet
- Servlet 的一生:生命周期
- 用 @WebServlet 给 Servlet 分配网址
- web.xml:另一种配置方式
- 怎么拿到用户提交的数据?
- 登录成功了,怎么跳转页面?
- 直接往浏览器输出内容
- 让 Servlet 连上数据库
- 密码不能明文存——MD5 加密
- JSP:写 HTML 的页面模板
- 前端 JS 验证 + 后端 Servlet 验证,一个都不能少
- 把整个流程串起来
- 项目文件该放哪?标准目录结构
- 代码中的坑,我帮你踩过了
- 总结
1. 课前准备:你需要装什么
在开始写代码之前,先把这些工具准备好:
| 工具 | 干什么用的 | 本项目用的版本 |
|---|---|---|
| JDK | Java 运行环境 | 1.8 |
| Tomcat | Servlet 容器,运行 Servlet 的地方 | 8.5 |
| MySQL | 数据库 | 5.x / 8.x 都行 |
| IntelliJ IDEA | 写代码的 IDE | 任意版本 |
| mysql-connector-java | JDBC 驱动,Java 连 MySQL 用的 | 5.1.47 |
重要概念: Servlet 不是那种 main 方法启动就能跑的程序。它必须放到一个叫 Servlet 容器 的东西里才能运行。Tomcat 就是最常用的 Servlet 容器。你把写好的 Servlet 部署到 Tomcat,Tomcat 负责接收浏览器请求,调你的 Servlet 来处理,然后把结果返回给浏览器。
2. Servlet 是什么?一张图看懂
一句话:Servlet 就是跑在服务器上、专门处理浏览器请求的 Java 类。
整个流程是这样的:
浏览器输入网址 → 请求到达 Tomcat → Tomcat 找到对应的 Servlet → Servlet 干活 → 返回页面给浏览器
Java 里 Servlet 的继承关系:
Servlet(接口,定规矩)
└── GenericServlet(抽象类,实现通用功能)
└── HttpServlet(抽象类,专门处理 HTTP 请求)← 你写 Servlet 就继承它
实际写代码的时候,你只需要做两件事:
public class MyServlet extends HttpServlet {
// 处理 GET 请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) { ... }
// 处理 POST 请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) { ... }
}
req 是请求对象——客户端发过来的所有信息都在里面。resp 是响应对象——你要返回给客户端的内容通过它来设置。
3. 5 分钟写出你的第一个 Servlet
下面就是这个项目的登录 Servlet 的完整代码。别被长度吓到,我们拆开一句一句讲:
package com.sleepcat;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.*;
@WebServlet(value = "/HtmlLoginServlet")
public class HtmlLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
String pass = req.getParameter("pass");
try {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection(
"jdbc:mysql:///homework", "root", "root");
Statement stmt = con.createStatement();
pass = MD5Encryption.encryptMD5(pass);
String sql = "SELECT * FROM `login` where username='"
+ name + "' and password='" + pass + "'";
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
resp.sendRedirect("index.jsp");
} else {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().print("<script>\n" +
" alert('用户名或密码错误!请重新登录');\n" +
" window.location.href = 'login.jsp';\n" +
"</script>");
}
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException(e);
}
}
}
逐行拆解:
@WebServlet(value = "/HtmlLoginServlet")— 告诉 Tomcat:“有人访问/HtmlLoginServlet这个地址的时候,用这个类来处理”。extends HttpServlet— 继承HttpServlet,你的类就变成了一个能处理 HTTP 请求的 Servlet。doPost(...)— 这个方法专门处理 POST 请求。前端表单用method="post"提交,就会进这个方法。req.getParameter("name")— 从请求里拿参数。参数名"name"对应前端<input name="name">,名字必须完全一致。resp.sendRedirect("index.jsp")— 让浏览器跳转到index.jsp(重定向)。resp.setContentType("text/html;charset=UTF-8")— 告诉浏览器"我返回的是 HTML,编码是 UTF-8"。这行必须在getWriter()之前写!resp.getWriter().print(...)— 直接往浏览器输出内容。这里输出了一段 JavaScript,浏览器收到后会弹个 alert 框然后跳转。
4. Servlet 的一生:生命周期
Servlet 的生命由 Tomcat 全程管理,分三个阶段:
init() → 诞生:第一次被访问时调用一次,做初始化工作
service() → 工作:每次请求都调用,内部帮你分派到 doGet/doPost
destroy() → 销毁:容器关闭或被卸载时调用一次,清理资源
三个你必须知道的事实:
- 一个 Servlet 类在内存中只有一个实例(单例),所有请求共享它。所以不要在 Servlet 里写会变的成员变量。
init()一生只调一次,适合放加载驱动这种一次性操作。service()你一般不直接重写它,而是重写doGet()和doPost(),service()会根据请求类型自动分派。
GET 和 POST 的使用场景:
| 方法 | 什么时候用 | 特点 |
|---|---|---|
doGet() |
点链接、直接访问 URL、表单 method=“get” | 参数在 URL 里可见 |
doPost() |
提交登录、注册等表单 | 参数在请求体里,URL 里看不到 |
5. 用 @WebServlet 给 Servlet 分配网址
Servlet 3.0 开始支持注解配置,一行代码搞定 URL 映射:
@WebServlet(value = "/HtmlLoginServlet")
当浏览器访问 http://localhost:8080/HtmlLoginServlet 时,Tomcat 就调用这个 Servlet。
@WebServlet 有哪些属性可以配:
| 属性 | 说明 | 例子 |
|---|---|---|
value 或 urlPatterns |
URL 路径,可以配多个 | "/login" 或 {"/a", "/b"} |
name |
给 Servlet 起个名字 | "LoginServlet" |
loadOnStartup |
设为 1 表示 Tomcat 一启动就加载 | 1 |
initParams |
初始化参数 | @WebInitParam(name="db", value="mysql") |
本项目两个 Servlet 的映射:
@WebServlet(value = "/HtmlLoginServlet")→ 处理登录@WebServlet(value = "/HtmlRegisterServlet")→ 处理注册
前端表单 <form action="/HtmlLoginServlet" method="post"> 中的 action 必须和这个一致。
6. web.xml:另一种配置方式
在 Servlet 3.0 之前,只能通过 web.xml 来配置 Servlet。这个文件位于 WEB-INF/web.xml,是整个应用的配置中心。
本项目的 web.xml 长这样:
<?xml version="1.0" encoding="UTF-8"?>
<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_4_0.xsd"
version="4.0">
</web-app>
因为本项目用 @WebServlet 注解配置了路由,所以 web.xml 是空的。
如果用 xml 方式配置,要这样写:
<!-- 声明一个 Servlet -->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.sleepcat.HtmlLoginServlet</servlet-class>
</servlet>
<!-- 把 Servlet 映射到一个 URL -->
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/HtmlLoginServlet</url-pattern>
</servlet-mapping>
注解 vs web.xml 选哪个?
| 方式 | 优点 | 缺点 |
|---|---|---|
@WebServlet |
代码即配置,简洁直观 | URL 路径写死在代码里 |
web.xml |
集中管理,改 URL 不用重新编译 | 配置繁琐,xml 一长就不好维护 |
初学阶段用 @WebServlet 就够了,等你学了 Spring MVC,路由配置会更简单。
7. 怎么拿到用户提交的数据?
前端表单提交后,Servlet 里用 HttpServletRequest 来拿数据:
// 拿单个值(最常用)
String name = req.getParameter("name");
// 拿多选值(如 checkbox)
String[] hobbies = req.getParameterValues("hobby");
// 遍历所有参数名
Enumeration<String> names = req.getParameterNames();
// 拿到所有参数的 Map
Map<String, String[]> map = req.getParameterMap();
铁律: req.getParameter("xxx") 里填的字符串,必须和前端 <input name="xxx"> 里 name 属性的值一模一样。
来看本项目中前后端是怎么对上的:
前端(login.jsp):
用户名:<input name="name" type="text"><br>
密码:<input name="pass" type="password"><br>
后端(HtmlLoginServlet):
String name = req.getParameter("name"); // 拿到用户名
String pass = req.getParameter("pass"); // 拿到密码
参数名 "name" 和 "pass" 前后完全一致,这就对上了。
8. 登录成功了,怎么跳转页面?
Servlet 处理完之后要跳转页面,有两种方式,区别很大:
方式一:重定向(sendRedirect)
resp.sendRedirect("index.jsp");
- 浏览器地址栏 会变成
index.jsp - 实际发生了两次请求:服务器返回 302,浏览器再请求新地址
- 可以跳转到外部网站(如
resp.sendRedirect("https://baidu.com")) - 因为是两次不同的请求,不能携带 request 数据
方式二:请求转发(forward)
req.getRequestDispatcher("index.jsp").forward(req, resp);
- 浏览器地址栏 不会变,还是原来的 URL
- 一次请求,服务器内部转发
- 只能在同一个应用内跳转
- 可以携带 request 域数据(因为还是同一个 request)
本项目中的实际应用:
// 登录成功 → 重定向到首页
resp.sendRedirect("index.jsp");
// 登录失败 → 输一段 JS,在浏览器端弹窗并跳转
resp.getWriter().print("<script>\n" +
" alert('用户名或密码错误!请重新登录');\n" +
" window.location.href = 'login.jsp';\n" +
"</script>");
失败时用了 JS 来做客户端弹窗再跳转,这是一个很实用的技巧——既给了用户提示,又控制了页面去向。
9. 直接往浏览器输出内容
有时候不需要跳转页面,直接输出内容就行。HttpServletResponse 提供了这个能力:
// 第一步:设置返回内容的类型和编码(必须在 getWriter() 之前!)
resp.setContentType("text/html;charset=UTF-8");
// 第二步:拿到输出流,开写
PrintWriter out = resp.getWriter();
out.print("<html><body>");
out.print("<h1>Hello World!</h1>");
out.print("</body></html>");
本项目中,登录失败的时候就是往浏览器输出了一个 <script> 标签:
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().print("<script>\n" +
" alert('用户名或密码错误!请重新登录');\n" +
" window.location.href = 'login.jsp';\n" +
"</script>");
浏览器收到这段 HTML 后,会执行里面的 JavaScript:弹窗 → 用户点确定 → 跳回登录页。一气呵成。
10. 让 Servlet 连上数据库
真实项目肯定要存数据。这个项目用的是最原始的 JDBC 来连 MySQL。虽然现在 Spring 项目都用 MyBatis 或 JPA 了,但 JDBC 是它们的老祖宗,搞懂它才能理解框架在干什么。
10.1 加载驱动 + 建立连接
// 加载 MySQL 驱动
Class.forName("com.mysql.jdbc.Driver");
// 建立连接
Connection con = DriverManager.getConnection(
"jdbc:mysql:///homework", // jdbc:mysql:///数据库名 = 连本机 3306 端口
"root", // 用户名
"root" // 密码
);
URL 速记: jdbc:mysql:///homework 省略了主机和端口,等价于 jdbc:mysql://localhost:3306/homework。
10.2 查询(SELECT)
Statement stmt = con.createStatement();
String sql = "SELECT * FROM `login` WHERE username='" + name
+ "' AND password='" + pass + "'";
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
// 查到了 → 登录成功
resp.sendRedirect("index.jsp");
} else {
// 没查到 → 登录失败
}
rs.next() 把光标移到下一行,有数据返回 true,没数据返回 false。
10.3 插入(INSERT)
Statement stmt = con.createStatement();
String sql = "INSERT INTO homework.login (username, password) VALUES ('"
+ name + "', '" + pass + "')";
int count = stmt.executeUpdate(sql);
if (count > 0) {
// count 是受影响的行数,>0 表示插入成功
resp.sendRedirect("login.jsp");
}
10.4 JDBC 操作流程总结
Class.forName("驱动类名") → 加载数据库驱动
DriverManager.getConnection() → 获取数据库连接
connection.createStatement() → 创建一个 Statement
stmt.executeQuery(sql) → 执行查询,返回 ResultSet
stmt.executeUpdate(sql) → 执行增删改,返回受影响行数
处理结果
关闭资源(rs → stmt → con,倒序关闭)
11. 密码不能明文存——MD5 加密
密码绝对不能明文存在数据库里。万一数据库泄露,所有用户的密码就全暴露了。
正确的做法是用哈希算法把密码算成一个固定长度的、不可逆的字符串再存。本项目用的是 MD5。
工具类 MD5Encryption.java:
package com.sleepcat;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Encryption {
public static String encryptMD5(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(input.getBytes()); // 把字符串转成字节喂给 MD5
byte[] messageDigest = digest.digest(); // 算出 16 字节的哈希值
// 把 16 字节转换成 32 位的十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String h = Integer.toHexString(0xFF & b);
while (h.length() < 2) h = "0" + h; // 不满两位补 0
hexString.append(h);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
使用方式:
pass = MD5Encryption.encryptMD5(pass); // 先把密码加密,再和数据库比对
效果演示:
用户输入:123456
MD5 加密后:e10adc3949ba59abbe56e057f20f883e
数据库里存的是:e10adc3949ba59abbe56e057f20f883e
用户登录输 123456 → 加密 → 得到 e10adc... → 和数据库的比对 → 一致就放行
这样一来,即使有人看到数据库,也拿不到用户的真实密码,只能看到一堆哈希值。
12. JSP:写 HTML 的页面模板
JSP 本质就是 Servlet 的另一种写法——它会被 Tomcat 在运行时自动编译成一个 Servlet。所以 JSP = Servlet,只是写法不同。
12.1 JSP 的 page 指令
每个 JSP 文件开头都有这么一行:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
它告诉浏览器:“我这页面是 HTML,编码是 UTF-8”。
12.2 在 JSP 里写 Java 代码
<%@ page import="java.util.*" %> <!-- 导包 -->
<%! int count = 0; %> <!-- 声明成员变量 -->
<% count++; %> <!-- 写 Java 代码 -->
<%= count %> <!-- 输出到页面 -->
但实际开发中 JSP 里尽量少写 Java 代码,只做数据展示。本项目就做得很好——三个 JSP 页面基本就是纯 HTML + CSS。
12.3 登录页面完整代码(login.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
<link href="css/login.css" type="text/css" rel="stylesheet">
</head>
<body>
<div class="login">
<div class="message">选课管理系统</div>
<form action="/HtmlLoginServlet" method="post">
用户名:<input name="name" type="text"><br>
密码:<input name="pass" type="password"><br>
<input type="submit" value="登录">
</form>
<a href="register.jsp">用户注册</a>
</div>
</body>
</html>
关注这三个点:
action="/HtmlLoginServlet"→ 表单提交到哪个 Servletmethod="post"→ 用 POST 方式提交(密码不应该出现在 URL 里)name="name"和name="pass"→ 前后端数据交互的桥梁
13. 前端 JS 验证 + 后端 Servlet 验证,一个都不能少
13.1 前端验证
注册页面(register.jsp)有完整的 JavaScript 验证逻辑:
function ver() {
let name = document.getElementsByName("name")[0].value;
let pass1 = document.getElementsByName("pass1")[0].value;
let pass2 = document.getElementsByName("pass2")[0].value;
if (name == '' || pass1 == '' || pass2 == '') {
alert("注册输入项都不能为空!");
return false; // 返回 false,表单不提交
} else if (pass1 != pass2) {
alert("两次密码不一致,请重新输入!");
return false;
} else {
return true; // 验证通过,表单正常提交
}
}
表单通过 onsubmit 绑定验证:
<form action="/HtmlRegisterServlet" method="post" onsubmit="return ver()">
onsubmit="return ver()" 这个写法很关键——当 ver() 返回 false 时,表单不会提交。
13.2 前端验证好还是后端验证好?答案是:都要
| 验证层 | 作用 | 能被绕过吗? |
|---|---|---|
| 前端验证 | 减少无效请求,给用户即时反馈 | 能(禁用 JS、f12 改 HTML 都能绕过) |
| 后端验证 | 最后一道防线,保证数据安全 | 不能 |
本项目只做了前端验证,没做后端验证,这意味着安全性不够。 正确的做法是在 Servlet 里也加上:
if (name == null || name.trim().isEmpty() ||
pass1 == null || pass1.trim().isEmpty()) {
// 参数不合法,返回错误提示
resp.getWriter().print("参数不能为空");
return;
}
13.3 GET 和 POST 的区别
<!-- GET:参数跟在 URL 后面,适合搜索等不敏感的场景 -->
<form action="/search" method="get">
<!-- POST:参数在请求体里,适合传密码等敏感数据 -->
<form action="/login" method="post">
本项目登录和注册都正确用了 POST——这是正确的做法。
14. 把整个流程串起来
说了这么多,来看一次完整的请求是怎么走的。
14.1 整体架构
┌──────────────────────────────────────────────────────┐
│ 浏览器(客户端) │
│ login.jsp(登录页) ⇄ register.jsp(注册页) ⇄ index.jsp(首页) │
└──────────────────────┬───────────────────────────────┘
│ POST 请求
▼
┌──────────────────────────────────────────────────────┐
│ Tomcat(Servlet 容器) │
│ HtmlLoginServlet(登录逻辑) HtmlRegisterServlet(注册逻辑) │
│ ├─ 接收参数 │
│ ├─ MD5 加密密码 │
│ ├─ 查/写数据库 │
│ └─ 返回结果 │
└──────────────────────┬───────────────────────────────┘
│ JDBC
▼
┌──────────────────────────────────────────────────────┐
│ MySQL 数据库 │
│ 数据库:homework 表:login │
│ 字段:username (VARCHAR), password (VARCHAR) │
└──────────────────────────────────────────────────────┘
14.2 登录流程(一步一步走)
1. 浏览器访问 login.jsp → 看到登录表单
2. 用户填好用户名、密码 → 点"登录"
→ 浏览器 POST 请求 /HtmlLoginServlet
→ 请求体:name=zhangsan&pass=123456
3. HtmlLoginServlet.doPost() 干活:
① req.getParameter("name") → "zhangsan"
② req.getParameter("pass") → "123456"
③ MD5Encryption.encryptMD5("123456")
→ "e10adc3949ba59abbe56e057f20f883e"
④ 拼 SQL:
SELECT * FROM login
WHERE username='zhangsan'
AND password='e10adc3949ba59abbe56e057f20f883e'
⑤ 执行查询
4. 判断:
├─ 查到了 → resp.sendRedirect("index.jsp") → 进首页
└─ 没查到 → 输出 JS alert("用户名或密码错误") → 回登录页
14.3 注册流程
1. 点"用户注册"链接 → 进 register.jsp
2. 填信息,JS 先检查:
├─ 有空字段 → alert("不能为空") → 卡住
├─ 两次密码不一致 → alert("不一致") → 卡住
└─ 都 OK → 表单 POST /HtmlRegisterServlet
3. HtmlRegisterServlet.doPost() 干活:
① 拿到 name、pass1
② MD5 加密密码
③ 拼 SQL:INSERT INTO homework.login (username, password) VALUES (...)
④ 执行插入
4. 判断:
├─ count > 0 → sendRedirect("login.jsp") → 去登录
└─ count = 0 → JS alert("注册失败") → 回注册页
14.4 项目文件一览
| 文件 | 角色 | 说明 |
|---|---|---|
HtmlLoginServlet.java |
Servlet | 处理登录请求 |
HtmlRegisterServlet.java |
Servlet | 处理注册请求 |
MD5Encryption.java |
工具类 | MD5 密码加密 |
login.jsp |
JSP | 登录页面 |
register.jsp |
JSP | 注册页面 |
index.jsp |
JSP | 登录成功后的首页 |
web.xml |
配置文件 | 部署描述符 |
mysql-connector-java-5.1.47.jar |
第三方库 | MySQL JDBC 驱动 |
login.css |
样式表 | 登录、注册页的样式 |
15. 项目文件该放哪?标准目录结构
servlet-usage-master/
├── src/ ← 所有 Java 源码放这里
│ └── com/sleepcat/
│ ├── HtmlLoginServlet.java
│ ├── HtmlRegisterServlet.java
│ └── MD5Encryption.java
│
└── web/ ← Web 资源根目录
├── index.jsp ← 用户可以直接访问的页面
├── login.jsp
├── register.jsp
├── css/ ← 样式和样式相关的图片
│ ├── login.css
│ └── img/
├── images/ ← 页面用的图片
└── WEB-INF/ ← 安全目录,用户无法通过 URL 访问!
├── web.xml ← 部署描述符
└── lib/ ← 第三方 jar 包,Tomcat 自动加载
└── mysql-connector-java-5.1.47.jar
最重要的规则:WEB-INF/ 下的东西,浏览器无法直接访问。 所以配置文件、编译后的 class 文件、第三方 jar 包都放这里面,保证安全。
src/*.java→ 编译后 →WEB-INF/classes/*.class(自动的)WEB-INF/lib/*.jar→ Tomcat 启动时自动加载
16. 代码中的坑,我帮你踩过了
学习代码不等于生产代码。这个项目有几处明显的问题,你现在知道了以后就能避开。
坑一:SQL 注入——最严重的安全漏洞
项目中的写法:
String sql = "SELECT * FROM `login` WHERE username='"
+ name + "' AND password='" + pass + "'";
直接把用户输入拼进 SQL 字符串。如果用户在用户名框里输入:
' OR '1'='1' --
拼出来的 SQL 就变成了:
SELECT * FROM `login` WHERE username='' OR '1'='1' --' AND password='xxx'
'1'='1' 永远成立,-- 后面全被注释掉了。结果就是不用密码直接登录。
怎么修?用 PreparedStatement:
String sql = "SELECT * FROM login WHERE username = ? AND password = ?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, name); // 第一个 ? 用 name 的值
pstmt.setString(2, encryptMD5(pass)); // 第二个 ? 用加密后的密码
ResultSet rs = pstmt.executeQuery();
PreparedStatement 会把参数和 SQL 结构分离,用户输入的内容永远被当作"值"而不是 SQL 的一部分来执行,从根本上杜绝了注入。
坑二:数据库资源不关闭
Connection con = DriverManager.getConnection(...);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 用完了,rs、stmt、con 都没关!
每次连数据库不关,连接池很快就被占满,新的请求就连不上数据库了。
怎么修?用 try-with-resources:
String sql = "SELECT * FROM login WHERE username = ? AND password = ?";
try (Connection con = DriverManager.getConnection(url, user, pwd);
PreparedStatement pstmt = con.prepareStatement(sql)) {
pstmt.setString(1, name);
pstmt.setString(2, pass);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
resp.sendRedirect("index.jsp");
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
// try 块结束,con、pstmt、rs 自动关闭,不用你手动处理
括号里的资源只要实现了 AutoCloseable 接口,就会在 try 块结束时自动关闭。
坑三:只有前端验证,没有后端验证
前面第 13 节已经讲过了。注册页只靠 JS 做非空和一致性检查,但 JS 可以被绕过去。正确的做法是前后端都验证:
// Servlet 里必须加这段
if (name == null || name.trim().isEmpty()) {
// 返回错误,不往下走
return;
}
坑四:数据库密码硬编码
DriverManager.getConnection("jdbc:mysql:///homework", "root", "root");
密码直接写在代码里,想改密码就要重新编译。而且一旦代码上 git,密码就公开了。
改进方式: 放在配置文件(如 db.properties)里,代码运行时读取。
# db.properties
jdbc.url=jdbc:mysql:///homework
jdbc.username=root
jdbc.password=root
坑五:MD5 不适合生产环境
MD5 已经被证明有碰撞漏洞,而且计算速度太快,攻击者可以用彩虹表秒破常见密码。生产环境应该用 bcrypt 或 scrypt——它们内置了盐值,而且计算速度故意做得慢,增加暴力破解的成本。
但在学习阶段,用 MD5 理解密码哈希的原理完全没问题,学到这个知识点就够了。
17. 总结
通过这个项目,你应该已经掌握了:
| 知识点 | 核心内容 |
|---|---|
| Servlet 基础 | 继承 HttpServlet,重写 doGet()/doPost() |
| URL 映射 | @WebServlet 注解,web.xml 配置 |
| 获取参数 | req.getParameter("name"),名字要和前端一致 |
| 跳转页面 | sendRedirect() 重定向 vs forward() 转发 |
| 输出响应 | resp.setContentType() + resp.getWriter().print() |
| JDBC 操作 | Class.forName() → getConnection() → executeQuery()/executeUpdate() |
| 密码加密 | MessageDigest 做 MD5 哈希 |
| JSP | page 指令,表单,和 Servlet 的交互 |
| 前后端协作 | 前端 JS 验证 + 后端 Servlet 验证,表单 POST 提交 |
| 项目结构 | WEB-INF/ 安全目录,lib/ 放 jar 包,标准布局 |
| 安全意识 | SQL 注入、资源泄露、硬编码、输入校验 |
Servlet 是 Java Web 开发的基石。Spring MVC 的 DispatcherServlet,本质上也是一个 Servlet。把 Servlet 搞懂了,再学 Spring Boot 你会发现那些"自动配置"不再神秘——你知道下面是怎么回事。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)