为什么我们还要聊 JSP?
说实话,每当有人问我现在学什么技术最好找工作,JSP 几乎从来不是第一个被推荐的名字。HTML5、Vue、React、Spring Boot……这些词听起来更“现代”,更“酷”。但是,如果你正在维护一个跑了十年的企业级系统,或者你想真正理解 Java Web 的底层脉络,JSP 就像是一本陈旧但经典的教科书——它不完美,甚至有点啰嗦,但它讲清楚了一切是如何从最原始的状态发展而来的。
我记得刚入行那年,老板扔给我一堆 .jsp 文件说:“去改改这个报表页面。” 我当时的反应是:“这是什么古董?” 但当我真正沉下心去了解 JSP 时,我发现它并不是洪水猛兽,而是一把钥匙,帮你打开 Java Servlet 世界的大门。今天,我不打算给你堆砌枯燥的定义,我想像老朋友聊天一样,带你从原理到实战,把 JSP 这块硬骨头啃下来。
打破迷思:JSP 到底是个什么鬼?
很多人对 JSP 有误解,觉得它就是“嵌了 Java 代码的 HTML”。这没错,但太浅了。
你要明白,浏览器看不懂 Java。无论你在 .jsp 文件里写了多么精妙的 for 循环,浏览器最终只能理解 HTML、CSS 和 JavaScript。那么,Java 代码是怎么跑到浏览器上的呢?
这就是 JSP 最核心的魔法:编译与转换。
当一个 JSP 文件第一次被访问时,Web 服务器(比如 Tomcat)会做以下几件事:
- 翻译:把
.jsp文件转换成对应的.java文件(这是一个 Servlet)。 - 编译:把
.java文件编译成.class文件(字节码)。 - 加载与执行:实例化这个 Servlet,处理请求,生成 HTML 响应发给浏览器。
之后,如果 JSP 文件没有修改,Tomcat 会直接使用缓存的 Servlet,不再重新编译。这就是为什么 JSP 首次访问慢,后续访问快的原因。
一张图看懂 JSP 的工作流程
graph LR
A[浏览器发起请求] --> B(Tomcat 容器)
B --> C{JSP 是否已编译?}
C -- 否 --> D[翻译: JSP -> Servlet Java]
D --> E[编译: Java -> Class]
C -- 是 --> F[执行已有 Servlet]
E --> F
F --> G[生成 HTML 响应]
G --> A
第一关:Hello World 的诞生
别急着上框架,我们先写点最纯粹的代码。假设你有一个简单的 JSP 页面,想显示“Hello, World!”,并且想显示当前的服务器时间。
传统写法:Scriptlets(脚本片段)
在早期的 JSP 开发中,开发者喜欢直接在 HTML 中写 Java 代码,这叫 Scriptlet,语法是 <% ... %>。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>简单的 JSP 示例</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>当前时间是:</p>
<%
// 这是 Java 代码块,可以直接写任何合法的 Java 语句
java.util.Date date = new java.util.Date();
out.println("<b>" + date.toLocaleString() + "</b>");
out.println("<br>");
int sum = 0;
for(int i=1; i<=5; i++) {
sum += i;
}
out.println("1 到 5 的和是: " + sum);
%>
</body>
</html>
注意:这里的 out 是内置对象,相当于 response.getWriter(),用来向浏览器输出内容。
这种写法有个很大的问题:** HTML 和 Java 代码混在一起,维护起来简直是噩梦**。想象一下,如果页面有 1000 行,中间夹杂着 200 行 Java 逻辑,你怎么调试?
为了解决这个问题,JSP 2.0 引入了 Expression Language (EL) 和 JSP Standard Tag Library (JSTL),让页面更像纯 HTML,逻辑尽量往后端走。
第二关:从“乱码”到“优雅”——EL 表达式
EL 表达式是 ${...} 这样的语法,它让 JSP 页面变得干净多了。它不仅能输出变量,还能自动处理一些类型转换和空值检查。
EL 表达式实战
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>EL 表达式示例</title>
</head>
<body>
<h2>使用 EL 表达式</h2>
<%-- 假设 Servlet 中已经存入了一个用户对象 user --%>
<%
// 模拟后端传值
request.setAttribute("username", "张三");
request.setAttribute("age", 25);
request.setAttribute("isVip", true);
%>
<p>用户名: ${username}</p>
<p>年龄: ${age}</p>
<p>是否VIP: ${isVip}</p>
<%-- EL 也可以做简单的运算 --%>
<p>年龄加 5 岁: ${age + 5}</p>
<%-- 三元表达式 --%>
<p>会员状态: ${isVip ? '尊贵会员' : '普通用户'}</p>
</body>
</html>
看到区别了吗?${username} 比 <%= username %> 简洁太多了。而且,EL 在变量不存在时不会报错,而是输出空字符串,这对前端页面来说非常友好。
第三关:JSTL —— 告别 <% if %>
在 JSP 页面里写 if 判断和 for 循环是极其糟糕的实践。JSTL 提供了一系列标签来替代这些逻辑,让页面结构清晰。
最常用的 JSTL 核心标签库是 c:。使用前,记得在页面顶部导入:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
实战案例:用户列表展示
假设我们从数据库查询出一堆用户,要显示在表格中。
Servlet 部分(后端):
@WebServlet("/userList")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 模拟数据库数据
List<User> users = new ArrayList<>();
users.add(new User(1, "Alice", 28));
users.add(new User(2, "Bob", 32));
users.add(new User(3, "Charlie", 25));
// 放入 request 作用域
request.setAttribute("userList", users);
// 转发到 JSP
request.getRequestDispatcher("/WEB-INF/userList.jsp").forward(request, response);
}
}
JSP 部分(前端):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>用户列表</title>
<style>
table { border-collapse: collapse; width: 50%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>注册用户列表</h2>
<c:if test="${empty userList}">
<p>暂无用户数据。</p>
</c:if>
<c:forEach var="user" items="${userList}">
<table>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</table>
</c:forEach>
</body>
</html>
关键点解析:
<c:if test="${empty userList}">:判断列表是否为空。<c:forEach var="user" items="${userList}">:遍历列表,var是当前迭代变量的名字,items是要遍历的集合。${user.id}:EL 自动调用user.getId()方法。注意,这里不需要写getUser(),EL 会自动解析 getter 方法。
第四关:九大内置对象——JSP 的“工具箱”
JSP 提供了一些不需要声明就能直接使用的对象,叫做“内置对象”。掌握它们,你就掌握了 JSP 的命脉。
1. 请求与响应
request:客户端发送的请求。你可以从中获取参数:request.getParameter("name")。response:服务器给客户端的响应。设置编码、重定向等操作都在这里:response.sendRedirect("index.jsp")。
2. 会话与应用程序
session:一次会话。比如用户登录后的信息存在这里:session.setAttribute("user", user)。注意,session 是基于 cookie 或 URL 重写的,关闭浏览器通常会失效。application:整个应用程序共享。比如网站的总访问量,存在这里:application.setAttribute("visitCount", count)。所有用户共享同一个 application。
3. 输出与异常
out:向客户端输出内容。虽然 EL 更常用,但底层还是out。exception:异常对象。只有在isErrorPage="true"的页面才能使用,用于显示错误信息。
4. 其他常用对象
config:Servlet 配置信息。pageContext:页面上下文,可以获取其他八个对象。page:指向当前 JSP 页面本身的 Servlet 实例(类似 Java 中的this)。
实战技巧:如何防止页面被直接访问?
很多初学者会把 JSP 放在 WEB-INF 目录下。这个目录下的文件,浏览器无法直接通过 URL 访问,只能通过服务器内部转发。这样可以保护你的敏感逻辑。
// Servlet 中跳转到受保护的 JSP
request.getRequestDispatcher("/WEB-INF/protected/page.jsp").forward(request, response);
第五关:实战项目——一个简单的图书管理系统
光说不练假把式。我们来做一个完整的迷你项目:图书列表展示 + 添加图书。
项目结构
src/
└── com/example/
└── model/
└── Book.java
└── servlet/
└── BookServlet.java
webapp/
├── index.jsp
├── addBook.jsp
└── WEB-INF/
└── web.xml
第一步:定义模型 Book.java
package com.example.model;
public class Book {
private int id;
private String title;
private String author;
private double price;
public Book(int id, String title, String author, double price) {
this.id = id;
this.title = title;
this.author = author;
this.price = price;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}
第二步:处理逻辑 BookServlet.java
package com.example.servlet;
import com.example.model.Book;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/books")
public class BookServlet extends HttpServlet {
// 模拟数据库,使用静态列表存储数据
private static List<Book> bookList = new ArrayList<>();
// 初始化一些数据
static {
bookList.add(new Book(1, "Java 核心技术", "Cay Horstmann", 99.0));
bookList.add(new Book(2, "深入理解 Java 虚拟机", "周志明", 89.0));
bookList.add(new Book(3, "Spring 实战", "Craig Walls", 79.0));
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 将所有书放入请求作用域
request.setAttribute("books", bookList);
// 转发到列表页面
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置编码,防止中文乱码
request.setCharacterEncoding("UTF-8");
String title = request.getParameter("title");
String author = request.getParameter("author");
String priceStr = request.getParameter("price");
// 简单验证
if (title != null && !title.isEmpty() && priceStr != null) {
try {
double price = Double.parseDouble(priceStr);
int newId = bookList.size() + 1;
bookList.add(new Book(newId, title, author, price));
// 添加成功后重定向到列表页,防止重复提交
response.sendRedirect(request.getContextPath() + "/books");
} catch (NumberFormatException e) {
// 价格格式错误,可以存储错误信息并返回
request.setAttribute("error", "价格必须是数字!");
request.getRequestDispatcher("/addBook.jsp").forward(request, response);
}
} else {
request.setAttribute("error", "书名和价格不能为空!");
request.getRequestDispatcher("/addBook.jsp").forward(request, response);
}
}
}
第三步:编写 JSP 页面
index.jsp(图书列表)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>图书管理系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
table { border-collapse: collapse; width: 80%; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #5DADE2; color: white; }
tr:hover { background-color: #f5f5f5; }
a { text-decoration: none; color: white; padding: 5px 10px; background-color: #2ECC71; border-radius: 3px; }
</style>
</head>
<body>
<h1>📚 图书管理系统</h1>
<p><a href="addBook.jsp">➕ 添加新书</a></p>
<table>
<tr>
<th>ID</th>
<th>书名</th>
<th>作者</th>
<th>价格</th>
</tr>
<c:forEach var="book" items="${books}">
<tr>
<td>${book.id}</td>
<td>${book.title}</td>
<td>${book.author}</td>
<td>¥${book.price}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
addBook.jsp(添加图书)
”`jsp <%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
<title>添加图书</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input { width: 300px; padding: 8px; box-sizing: border-box; }
button { padding: 10px 20px; background-color: #2ECC71; color: white; border: none; cursor: pointer; }
.error { color: red; margin-bottom: 10px; }
</style>
<h1>添加新书</h1>
<c:if test="${not empty error}">
<p class="error">${error}</p>
</c:if>
<form action="books" method="post">
<div class="form-group">
<label for="title">书名:</label>
<input type="text" id="title" name="title" required>
</
