Servlet & JSP 教程:从入门到实践
第一部分:基础概念与入门
什么是 Servlet 和 JSP?
在开始编码之前,我们首先要理解这两个技术是什么,以及它们为什么存在。
-
Servlet (Server Applet):
- 本质:一个运行在 Web 服务器端的 Java 小程序。
- 作用:接收来自客户端(通常是浏览器)的请求,处理业务逻辑(如查询数据库、计算数据),然后生成响应(通常是 HTML 页面)并返回给客户端。
- 核心思想:用 Java 代码来动态生成网页内容,你可以把它想象成一个“网页生成器”。
- 缺点:如果直接在 Servlet 中用
out.println()输出大量的 HTML 代码,会使 Java 代码和 HTML 代码高度耦合,导致代码难以阅读和维护。
-
JSP (JavaServer Pages):
- 本质:一个特殊的 Servlet,当你第一次访问一个 JSP 文件时,Web 服务器(如 Tomcat)会自动将其编译成一个 Servlet 类。
- 作用:提供一个更方便的方式来在 HTML 页面中嵌入 Java 代码,实现页面的动态化。
- 核心思想:以 HTML 为基础,在需要动态内容的地方插入 Java 代码(JSP 标签/脚本),它解决了 Servlet 中 HTML 和 Java 代码混合的问题,让“美工”和“程序员”可以更好地分工。
- 优点:视图层和逻辑层初步分离,代码更清晰。
Servlet 更适合处理复杂的业务逻辑,而 JSP 更适合展示数据,在实际开发中,它们总是协同工作的,形成经典的 MVC(Model-View-Controller) 模式雏形:
- Model (模型):JavaBean 或 POJO,封装数据。
- View (视图):JSP 页面,负责显示数据。
- Controller (控制器):Servlet,接收请求,调用 Model 处理业务,然后将数据传递给 View 进行展示。
开发环境搭建
你需要以下三样东西:
-
JDK (Java Development Kit):Java 开发工具包,提供 Java 运行环境和编译/调试工具。
- 下载地址:Oracle JDK 或 OpenJDK
- 安装并配置好
JAVA_HOME环境变量。
-
Web 服务器:用于运行和部署你的 Web 应用,最常用的是 Apache Tomcat。
- 下载地址:Tomcat 官网
- 下载与你的 JDK 版本匹配的 Tomcat(JDK 11/17 下载 Tomcat 9+)。
- 解压即可使用,无需安装,配置好
CATALINA_HOME环境变量。
-
IDE (集成开发环境):用于编写和管理代码,推荐使用 IntelliJ IDEA (Ultimate Edition) 或 Eclipse IDE for Enterprise Java and Web Developers。
- IntelliJ IDEA 对 Maven/Gradle 和 Web 开发支持极佳,是首选。
- Eclipse 是老牌的免费 IDE,社区庞大。
第一个 Servlet 程序
让我们创建一个最简单的 Servlet,它在浏览器上输出 "Hello, World!"。
步骤 1:创建 Web 项目
在 IDEA 中:
File->New->Project。- 选择
Java Enterprise。 - 勾选
Web application和Create from archetype(可选,但推荐使用 Maven/Gradle 管理项目)。 - 选择你的 Application Server (Tomcat)。
- 点击
Finish。
IDEA 会为你创建一个标准的 Maven Web 项目结构,如下所示:
hello-servlet/
├── src/
│ ├── main/
│ │ ├── java/ // 存放 Java 源代码,如 Servlet
│ │ ├── resources/ // 存放配置文件
│ │ └── webapp/ // 存放 Web 资源,如 JSP, HTML, CSS, JS
│ │ ├── WEB-INF/ // 重要!存放 web.xml 和私有资源
│ │ │ └── web.xml // 部署描述符,用于配置 Servlet
│ │ └── index.jsp
│ └── test/
└── pom.xml // Maven 项目配置文件
步骤 2:编写 Servlet 代码
在 src/main/java 目录下创建你的包,com.example.servlet,然后在该包下创建一个 HelloServlet.java 文件。
package com.example.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
// 1. 继承 HttpServlet
public class HelloServlet extends HttpServlet {
// 2. 重写 doGet 或 doPost 方法
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 3. 设置响应内容类型和字符编码
response.setContentType("text/html;charset=UTF-8");
// 4. 获取输出流,向浏览器写入内容
response.getWriter().println("<h1>Hello, Servlet World!</h1>");
response.getWriter().println("当前时间是: " + new java.util.Date());
}
// 如果需要处理 POST 请求,就重写 doPost 方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response); // 通常让 doPost 调用 doGet
}
}
代码解释:
HttpServlet:是专门用于处理 HTTP 请求的 Servlet 基类,我们继承它来获得处理 HTTP 请求的能力。doGet():当客户端通过 GET 方式(如在浏览器地址栏输入 URL 并回车)请求此 Servlet 时,此方法会被调用。response.setContentType("text/html;charset=UTF-8"):告诉浏览器,我返回的内容是 HTML 格式,并且使用 UTF-8 编码,防止中文乱码。response.getWriter():获取一个字符输出流,通过它向响应体中写入内容。
步骤 3:在 web.xml 中配置 Servlet
为了让 Web 服务器知道你的 HelloServlet 存在以及如何访问它,你需要在 src/main/webapp/WEB-INF/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">
<!-- Servlet 配置 -->
<servlet>
<!-- Servlet 的内部名称,可以自定义 -->
<servlet-name>helloServlet</servlet-name>
<!-- Servlet 的全限定类名 -->
<servlet-class>com.example.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet 映射配置 -->
<servlet-mapping>
<!-- 映射到哪个 Servlet,这里的名称要和上面的 <servlet-name> 一致 -->
<servlet-name>helloServlet</servlet-name>
<!-- 访问这个 Servlet 的 URL 模式 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
配置解释:
<servlet>:定义一个 Servlet。<servlet-name>:给 Servlet 起个名字。<servlet-class>:指定 Servlet 类的完整路径。
<servlet-mapping>:将一个 URL 路径映射到某个 Servlet。<servlet-name>:指定要映射的 Servlet 的名字。<url-pattern>:指定访问该 Servlet 的 URL。/hello表示项目的根路径后跟/hello。
步骤 4:部署并运行
- 确保 IDEA 已经配置好了 Tomcat 服务器。
- 点击工具栏上的
Run按钮(绿色三角形)。 - IDEA 会自动将项目打包成
.war文件并部署到 Tomcat,然后启动 Tomcat。 - 打开浏览器,访问
http://localhost:8080/你的项目名/hello。
注意:如果你的项目名是 hello-servlet,URL http://localhost:8080/hello-servlet/hello。
如果一切顺利,你将看到浏览器中显示了 "Hello, Servlet World!" 和当前时间。
第一个 JSP 程序
JSP 的使用比 Servlet 更直观。
步骤 1:创建 JSP 文件
在 src/main/webapp 目录下创建一个 hello.jsp 文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>My First JSP</title>
</head>
<body>
<h1>Hello, JSP World!</h1>
<p>当前时间是: <%= new java.util.Date() %></p>
</body>
</html>
代码解释:
<%@ page ... %>:这是 JSP 指令,用于设置整个页面的属性。contentType设置内容类型和编码,language指定脚本语言。<h1>:这是普通的 HTML 标签。<%= ... %>:这是 JSP 表达式,它会把里面的 Java 代码计算结果,输出到页面上。new java.util.Date()会生成一个日期对象,然后被转换成字符串显示。
步骤 2:访问 JSP
直接在浏览器中访问 http://localhost:8080/你的项目名/hello.jsp。
你会看到页面显示了 JSP 中的内容,当你第一次访问时,Tomcat 会在其工作目录下自动将 hello.jsp 编译成一个 hello_jsp.java 文件(这是一个 Servlet),然后再编译成 .class 文件并执行,后续访问将直接使用编译后的 class 文件,速度更快。
第二部分:核心技术与实践
Servlet 生命周期
理解 Servlet 的生命周期对于掌握其工作原理至关重要,一个 Servlet 的生命周期由三个阶段组成:
-
初始化
- 当 Servlet 第一次被请求时(或服务器启动时配置为
load-on-startup),服务器会创建一个 Servlet 实例。 - 紧接着,服务器会调用
init(ServletConfig config)方法。 init方法只被调用一次,用于执行一次性的初始化操作,如加载配置文件、建立数据库连接等。
- 当 Servlet 第一次被请求时(或服务器启动时配置为
-
处理请求
- 每次客户端请求该 Servlet 时,服务器都会调用
service()方法。 HttpServlet的service()方法会根据请求的 HTTP 方法(GET, POST, PUT...)来调用相应的doGet(),doPost(),doPut()等方法。- 这个方法可以被调用多次,每次请求都会调用一次。
- 每次客户端请求该 Servlet 时,服务器都会调用
-
销毁
- 当 Web 应用被卸载或服务器关闭时,服务器会调用
destroy()方法。 destroy方法也只被调用一次,用于释放资源,如关闭数据库连接。
- 当 Web 应用被卸载或服务器关闭时,服务器会调用
示例:
public class LifecycleServlet extends HttpServlet {
// 1. 初始化阶段
@Override
public void init() throws ServletException {
System.out.println("Servlet 正在被初始化...");
// 在这里执行初始化代码
}
// 2. 处理请求阶段
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet 正在处理 GET 请求...");
resp.getWriter().println("Lifecycle Servlet is running.");
}
// 3. 销毁阶段
@Override
public void destroy() {
System.out.println("Servlet 正在被销毁...");
// 在这里执行清理代码
}
}
Request 和 Response 对象
这是 Servlet 开发的核心。
-
HttpServletRequest(请求对象):代表客户端的请求。- 获取请求信息:
request.getMethod(): 获取 HTTP 方法 (GET, POST)request.getRequestURI(): 获取请求的 URIrequest.getParameter("username"): 获取表单提交的参数值request.getRequestDispatcher("target.jsp").forward(request, response): 请求转发,将请求和响应对象传递给另一个资源。
- 请求转发 vs 重定向:
- 转发 (
forward):服务器行为,地址栏 URL 不会改变,在服务器内部将请求从一个 Servlet/JSP 传递给另一个,共享同一个request对象。 - 重定向 (
sendRedirect):客户端行为,服务器返回一个 302 状态码和新的 Location,浏览器收到后会自动向新的 URL 发送一次新的请求,地址栏 URL 会改变,不共享request对象。
- 转发 (
- 获取请求信息:
-
HttpServletResponse(响应对象):代表服务器对客户端的响应。- 设置响应内容:
response.setContentType("text/html;charset=UTF-8"): 设置响应内容类型和编码。response.getWriter(): 获取字符输出流。response.getOutputStream(): 获取字节输出流(用于下载文件等)。response.sendRedirect("login.jsp"): 实现重定向。
- 设置响应内容:
JSP 核心元素
除了表达式 <%= ... %>,JSP 还有其他核心元素:
-
JSP 脚本片段
- 语法:
<% ... %> - 作用:可以编写任意多的 Java 语句,多个片段代码会被合并到
_jspService方法中。 - 注意:不能在片段中定义方法,但可以调用方法。
<% int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } %> <p>1 到 100 的和是: <%= sum %></p> - 语法:
-
JSP 声明
- 语法:
<%! ... %> - 作用:在生成的 Servlet 类中定义字段或方法,生命周期与 Servlet 实例相同。
<%! private int hitCount = 0; public int getHitCount() { return hitCount; } %> <p>页面访问次数: <%= ++hitCount %></p> - 语法:
-
JSP 注释
- 语法:
<%-- ... --%> - 作用:注释内容不会被发送到客户端,也不会被编译到 Servlet 中,这是推荐的注释方式。
- 对比:HTML 注释
<!-- ... -->会被发送到客户端。
- 语法:
-
JSP 指令
- 语法:
<%@ directive ... %> - 主要指令:
page: 设置整个页面的属性,如contentType,import等。include: 静态包含,将另一个文件的内容原封不动地插入到当前文件中,在翻译阶段完成。taglib: 引入标签库,如 JSTL。
- 语法:
EL 表达式 和 JSTL 标签库
直接在 JSP 中写 Java 代码(脚本片段和表达式)被称为脚本式编程,虽然方便,但破坏了视图层的纯粹性,不符合 MVC 思想,现代 JSP 开发推荐使用 EL (Expression Language) 和 JSTL (JSP Standard Tag Library)。
-
EL 表达式
- 语法:
- 作用:简化从作用域中获取数据的写法。
- 示例:
- JSP 表达式:
<%= request.getAttribute("user") %> - EL 表达式:
${user}
- JSP 表达式:
- EL 会自动从四个作用域(page, request, session, application)中按顺序查找名为 "user" 的属性,找到即返回。
-
JSTL 标签库
- 作用:用标签代替 Java 代码,实现逻辑判断、循环、格式化等。
- 使用前需要引入:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
- 常用 JSTL 标签:
<c:set>: 设置变量。<c:set var="username" value="张三" scope="request" />
<c:if>: 条件判断。<c:if test="${user.age >= 18}"> <p>已成年</p> </c:if><c:forEach>: 循环。<ul> <c:forEach var="item" items="${list}"> <li>${item.name}</li> </c:forEach> </ul>
第三部分:MVC 模式与项目实践
经典的 MVC 模式
我们将 Servlet 和 JSP 结合起来,实现一个简单的 MVC 应用,这个应用的功能是:用户在 JSP 页面输入姓名,提交给 Servlet 处理,Servlet 将处理结果(包含问候语)存入 request 作用域,然后转发给另一个 JSP 页面显示。
项目结构:
webapp/
├── index.jsp // 视图:输入页面
├── welcome.jsp // 视图:显示结果页面
└── WEB-INF/
└── web.xml
src/main/java/
└── com/example/controller/
└── GreetingServlet.java // 控制器
└── com/example/model/
└── User.java // 模型 (JavaBean)
步骤 1:创建 Model (JavaBean)
User.java - 一个简单的 POJO,用于封装数据。
package com.example.model;
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
步骤 2:创建 View 1 (输入页面)
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>输入姓名</title>
</head>
<body>
<form action="greeting" method="post">
请输入您的姓名: <input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>
action="greeting":表单提交的地址,对应 Servlet 的 URL 映射。
步骤 3:创建 Controller (Servlet)
GreetingServlet.java
package com.example.controller;
import com.example.model.User;
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;
// 使用注解配置 Servlet,替代 web.xml
@WebServlet("/greeting")
public class GreetingServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取请求参数
String username = request.getParameter("username");
// 2. 处理业务逻辑 (这里很简单,实际可能更复杂)
User user = new User();
user.setName(username);
// 3. 将数据存入 request 作用域
request.setAttribute("user", user);
// 4. 请求转发到 welcome.jsp 页面
request.getRequestDispatcher("/welcome.jsp").forward(request, response);
}
}
@WebServlet("/greeting"):这是 Servlet 3.0+ 提供的注解,可以替代web.xml中的配置,更加简洁。request.setAttribute(): 将数据存入 request 作用域,以便下一个页面(转发到的页面)可以获取。
步骤 4:创建 View 2 (显示结果页面)
welcome.jsp - 使用 EL 和 JSTL 来显示数据。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>欢迎页面</title>
</head>
<body>
<h1>欢迎您, ${user.name}!</h1>
<p>欢迎来到 MVC 世界。</p>
<a href="index.jsp">返回</a>
</body>
</html>
${user.name}:EL 表达式,从 request 作用域中取出名为 "user" 的对象,然后调用其getName()方法获取值。
步骤 5:配置 web.xml
如果使用了注解,web.xml 中可以不需要 Servlet 映射,但通常我们会保留 web.xml 来配置欢迎页面等。
<web-app ...>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
运行项目,访问 http://localhost:8080/你的项目名/,你将看到一个完整的 MVC 流程。
第四部分:进阶与总结
会话管理
HTTP 是无状态的协议,服务器无法区分两次请求是否来自同一个用户,为了解决这个问题,我们需要会话管理。
-
Cookie:
- 服务器在响应中通过
Set-Cookie头将一小段数据发送到客户端,客户端浏览器会保存它。 - 之后客户端每次请求同一网站时,都会在请求头中带上这个 Cookie。
- 服务器通过读取 Cookie 来识别用户。
- 特点:数据存储在客户端,可以禁用,有大小限制(4KB)。
- 服务器在响应中通过
-
Session:
- Session 是存储在服务器端的会话机制。
- 当一个用户首次访问时,服务器会为他创建一个唯一的 Session ID,并将这个 ID 通过 Cookie 发送给客户端。
- 服务器将用户的会话数据(如登录信息、购物车)与这个 Session ID 关联起来,存储在服务器内存或数据库中。
- 客户端后续请求带上 Session ID,服务器就能找到对应的会话数据。
- 特点:数据存储在服务器端,更安全,无大小限制(但会占用服务器内存)。
Session 使用示例:
// 在 Servlet 中
HttpSession session = request.getSession(); // 获取或创建 Session
// 存储数据
session.setAttribute("userLoginInfo", "some user data");
// 获取数据
String userInfo = (String) session.getAttribute("userLoginInfo");
// 销毁 Session (用户退出登录时)
session.invalidate();
过滤器
过滤器是一个可以拦截请求和响应的对象,它可以在请求到达 Servlet 之前,或在响应返回给客户端之前,对请求和响应进行预处理和后处理。
常见用途:
- 字符编码过滤器:统一处理所有请求的字符编码,避免每个 Servlet 都写一遍。
- 权限验证过滤器:检查用户是否登录,未登录则重定向到登录页面。
- 日志记录过滤器:记录所有请求的详细信息。
示例:一个通用的字符编码过滤器
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*") // 过滤所有请求
public class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 1. 对请求进行预处理
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 2. 将请求和响应传递给下一个过滤器或目标 Servlet
chain.doFilter(request, response);
// 3. 对响应进行后处理 (如果需要)
}
// ... init() 和 destroy() 方法
}
总结与学习路径
Servlet & JSP 的核心地位: 虽然现在 Spring Boot 等框架非常流行,但它们底层依然依赖于 Servlet,理解 Servlet 和 JSP 是理解 Java Web 开发原理的基石。
学习路径建议:
- 基础掌握:熟练编写 Servlet 和 JSP,理解
request,response,session对象。 - MVC 实践:用 Servlet + JSP + JavaBean 完成一个完整的项目(如用户登录、简单的留言板)。
- 框架引入:学习使用 JSTL 和 EL 优化 JSP 页面。
- 进阶技术:掌握过滤器、监听器,理解会话管理。
- 拥抱框架:当你对 Servlet/JSP 有了扎实的理解后,学习 Spring MVC 或 Spring Boot 会事半功倍,你会发现,Spring 框架的本质就是对 Servlet 和 JSP 的高级封装和增强。
推荐资源:
- 书籍:《Head First Servlets & JSP》
- 在线教程:W3Schools, Runoob, B站上的免费视频课程。
- 官方文档:Java EE (Jakarta EE) Specifications
这份教程涵盖了 Servlet 和 JSP 的核心知识点,从理论到实践,希望能帮助你顺利入门 Java Web 开发世界!祝你学习愉快!
