说起Java Web开发,JSP(JavaServer Pages)虽然是个“老面孔”,但它的底层逻辑和核心思想至今仍是理解现代框架的基石。很多初学者看到“JSP”三个字就犯愁,觉得那是上个时代的产物。其实不然,弄懂JSP,你就相当于掌握了Java服务器端编程的“内功心法”。今天咱们不聊那些枯燥的官方文档,而是像老朋友聊天一样,带你从零基础一步步走到能独立开发动态网页的水平。
为什么还要学JSP?
你可能会问:“现在Spring Boot、Vue、React满天飞,谁还看JSP?”
说实话,确实很少有人新建项目直接用JSP了。但是,JSP的本质是Servlet。每一个.jsp文件在服务器启动时都会被翻译成Java Servlet代码。如果你不懂JSP,你就看不懂Tomcat背后的执行流程;如果你不懂Servlet的生命周期,你就难以真正理解Spring MVC的工作机制。
更重要的是,很多企业遗留系统依然大量使用JSP + Servlet架构。维护老代码、排查线上问题、理解HTTP请求处理流程,JSP都是最好的切入点。它简单、直接、透明——没有Spring的层层封装,你能清晰地看到数据是如何从数据库流向页面的。
所以,别把它当成“过时技术”,而要把JSP当作理解Java Web的“解剖图”。
JSP到底是什么?
从字面意思看,JSP就是“Java服务器页面”。它是一种动态网页技术标准,允许开发者在HTML页面中嵌入Java代码片段。
想象一下这个场景:你有一个电商网站,商品列表需要根据数据库实时查询显示。如果用纯HTML,你每次改数据都要重新写页面;如果用纯Servlet,你要在Java代码里拼接字符串输出HTML,那简直是噩梦——代码可读性极差,维护起来痛不欲生。
JSP的出现就是为了解决这个矛盾:
- HTML部分:负责页面结构和样式,由前端设计师把控。
- Java代码部分:负责业务逻辑,由后端工程师实现。
两者完美结合,既保留了开发效率,又实现了动态内容生成。
第一个JSP程序:Hello World
让我们从最经典的“Hello World”开始。虽然简单,但它揭示了JSP运行的基本原理。
假设你已经安装好JDK和Tomcat服务器,接下来我们在IDE(比如IntelliJ IDEA或Eclipse)中创建一个Web项目。
项目结构
一个标准的JSP项目结构如下:
my-webapp/
├── WEB-INF/
│ ├── web.xml // 配置文件
│ └── classes/ // 编译后的.class文件
├── index.jsp // 首页
└── hello.jsp // 自定义页面
编写index.jsp
在WebRoot目录下新建一个index.jsp文件,内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSP入门</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>当前时间:<%= new java.util.Date() %></p>
</body>
</html>
这看起来像HTML,但多了几行特殊的东西。别急,我们逐步拆解。
关键语法解析
1. 页面指令:<%@ page ... %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
这行代码告诉服务器:
- 生成的响应内容是HTML格式
- 字符编码使用UTF-8(避免中文乱码)
- 使用Java语言编写脚本
这是JSP文件的“身份证”,每个JSP文件开头几乎都需要这行。
2. 表达式:<%= ... %>
<%= new java.util.Date() %>
这是JSP的脚本元素之一。=符号表示“输出”,它会将Java表达式的结果直接渲染到页面上。注意:这里没有分号,因为表达式不需要语句结束符。
对比一下:
<%= new Date() %>—— 输出当前时间<% new Date(); %>—— 执行语句,但不输出
3. 脚本片段:<% ... %>
<%
String userName = "张三";
out.println("欢迎回来," + userName);
%>
<% %> 是Java代码块,可以在里面写任意合法的Java语句。out是JSP内置对象,代表输出流,等同于Servlet中的PrintWriter。
JSP的九大内置对象
JSP之所以强大,是因为它提供了一些“免费”的对象,开发者可以直接使用,无需手动创建。这九个对象是JSP编程的核心工具:
| 对象名 | 类型 | 用途 |
|---|---|---|
request |
HttpServletRequest | 获取客户端请求信息 |
response |
HttpServletResponse | 向客户端发送响应 |
out |
JspWriter | 输出内容到页面 |
session |
HttpSession | 管理用户会话 |
application |
ServletContext | 应用级上下文 |
config |
ServletConfig | Servlet配置信息 |
pageContext |
PageContext | 页面上下文 |
page |
Object | 指向当前JSP页面本身 |
exception |
Throwable | 异常对象(仅在错误页面可用) |
下面我们通过实际代码来理解这些对象的使用。
request对象:获取请求数据
当用户提交表单或携带参数访问页面时,request对象负责接收这些数据。
示例:登录表单处理
创建一个登录页面login.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<h2>请登录</h2>
<%
String errorMsg = "";
if (request.getParameter("error") != null) {
errorMsg = "用户名或密码错误!";
}
%>
<p style="color: red;"><%= errorMsg %></p>
<form action="doLogin.jsp" method="post">
<label>用户名:</label>
<input type="text" name="username" /><br/>
<label>密码:</label>
<input type="password" name="password" /><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
对应的处理页面doLogin.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String username = request.getParameter("username");
String password = request.getParameter("password");
// 简单模拟验证(实际项目中应查数据库)
if ("admin".equals(username) && "123456".equals(password)) {
session.setAttribute("user", username);
response.sendRedirect("welcome.jsp");
} else {
response.sendRedirect("login.jsp?error=1");
}
%>
关键点解析:
request.getParameter("username")获取表单提交的值session.setAttribute("user", username)将用户信息存入会话response.sendRedirect()重定向到其他页面
session对象:保持用户状态
HTTP协议是无状态的,每次请求都是独立的。session对象解决了这个问题——它为每个用户创建一个独立的存储空间,直到超时或主动销毁。
示例:购物车功能
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 从session中获取购物车
java.util.List<String> cart = (java.util.List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new java.util.ArrayList<>();
session.setAttribute("cart", cart);
}
// 添加商品
String action = request.getParameter("action");
String product = request.getParameter("product");
if ("add".equals(action) && product != null) {
cart.add(product);
}
%>
<html>
<head>
<title>我的购物车</title>
</head>
<body>
<h2>购物车商品列表</h2>
<ul>
<% for (String item : cart) { %>
<li><%= item %></li>
<% } %>
</ul>
<a href="addProduct.jsp?product=iPhone">添加iPhone</a>
<a href="addProduct.jsp?product=MacBook">添加MacBook</a>
<hr/>
<a href="clearCart.jsp">清空购物车</a>
</body>
</html>
application对象:应用级共享数据
与session不同,application对象是全局的,所有用户共享同一份数据。常用于统计在线人数、缓存配置信息等场景。
示例:网站计数器
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 从application中获取访问次数
Integer count = (Integer) application.getAttribute("visitCount");
if (count == null) {
count = 0;
}
// 每次访问加1
count++;
application.setAttribute("visitCount", count);
%>
<html>
<body>
<h1>欢迎访问本站!</h1>
<p>您是第 <strong><%= count %></strong> 位访客</p>
</body>
</html>
注意: 由于多个用户可能同时访问,这种写法在高并发下会有数据不一致问题。实际项目中应使用synchronized或AtomicInteger来保证线程安全。
JSP的三种脚本元素
前面我们已经接触了<%=和<%,JSP的脚本元素其实有三种:
1. 表达式 <%= %>
用于输出单个值到页面。
<%= "Hello" %>
<%= 100 + 200 %>
<%= user.getName() %>
等价于:
out.write("Hello");
out.write(String.valueOf(100 + 200));
out.write(user.getName());
2. 脚本片段 <% %>
用于编写Java代码块。
<%
int a = 10;
int b = 20;
if (a > b) {
out.println("a大");
} else {
out.println("b大");
}
%>
注意:脚本片段中的代码会被直接插入到Servlet的_jspService()方法中,因此必须遵循Java语法规范。
3. 声明 <%! %>
用于声明方法或变量,作用范围是类级别(相当于在Servlet类中定义成员变量或方法)。
<%!
private int counter = 0;
public String getMessage() {
return "欢迎访问";
}
%>
<html>
<body>
<p><%= getMessage() %></p>
<p>当前计数:<%= ++counter %></p>
</body>
</html>
重要区别:
<% %>中的变量是局部变量,每次请求都重新创建<%! %>中的变量是成员变量,所有请求共享
这意味着,如果你用<%! %>定义计数器,所有用户访问都会累加同一个值。这在大多数情况下不是我们想要的,所以谨慎使用声明语法。
JSP动作元素:更优雅的编程方式
除了脚本元素,JSP还提供了许多动作元素,它们以<jsp:开头,功能更强大且语义更清晰。
1. <jsp:include>:包含其他页面
<jsp:include page="header.jsp" />
<jsp:include page="footer.jsp" />
这相当于把header.jsp和footer.jsp的内容合并到当前页面。与Java的#include不同,<jsp:include>是在请求时动态包含,而不是在编译时静态包含。
2. <jsp:forward>:请求转发
<jsp:forward page="login.jsp" />
将请求转发给另一个页面,用户浏览器地址栏不变,但服务器端执行的是目标页面。常用于登录验证失败后跳转到登录页。
3. <jsp:useBean>:创建JavaBean实例
这是JSP与Java类结合的关键。JavaBean是一个遵循特定规范的Java类:
- 公有无参构造方法
- 私有属性
- 公有getter/setter方法
定义JavaBean:
// User.java
public class User {
private String name;
private int age;
public User() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在JSP中使用:
<jsp:useBean id="user" class="com.example.User" scope="page" />
<jsp:setProperty name="user" property="name" value="张三" />
<jsp:setProperty name="user" property="age" value="25" />
<p>姓名:<jsp:getProperty name="user" property="name" /></p>
<p>年龄:<jsp:getProperty name="user" property="age" /></p>
这里使用了三个动作:
<jsp:useBean>:创建或获取Bean实例<jsp:setProperty>:设置属性值<jsp:getProperty>:获取属性值
虽然看起来比直接调用getter/setter繁琐,但它与EL表达式结合后更加简洁(后面会讲)。
4. <jsp:param>:传递参数
通常与<jsp:include>或<jsp:forward>配合使用。
<jsp:forward page="target.jsp">
<jsp:param name="userId" value="1001" />
<jsp:param name="action" value="view" />
</jsp:forward>
在目标页面可以通过request.getParameter("userId")获取这些参数。
JSTL:摆脱Java代码的JSP
如果你仔细观察上面的代码,会发现一个问题:JSP页面中混杂了大量Java代码。虽然这能工作,但违背了“关注点分离”的原则。前端工程师看不懂Java代码,后端工程师又不擅长写HTML,协作效率很低。
JSTL(JSP Standard Tag Library,JSP标准标签库)就是为了解决这个问题而生的。它提供了一系列标准的标签,让你可以用纯XML语法完成原本需要Java代码才能做的事。
安装JSTL
首先需要在项目中引入JSTL依赖。如果是Maven项目,在pom.xml中添加:
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
</dependency>
然后在JSP页面顶部声明标签库:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
核心标签用法
1. 条件判断:<c:if>
<c:if test="${user.loggedIn}">
<p>欢迎回来,${user.name}!</p>
</c:if>
<c:if test="${not user.loggedIn}">
<a href="login.jsp">请登录</a>
</c:if>
2. 选择结构:<c:choose>
<c:choose>
<c:when test="${score >= 90}">
<p>优秀</p>
</c:when>
<c:when test="${score >= 60}">
<p>及格</p>
</c:when>
<c:otherwise>
<p>不及格</p>
</c:otherwise>
</c:choose>
3. 循环遍历:<c:forEach>
这是最常用的标签之一,替代了原本需要写Java代码的循环。
<%
// 在Servlet中准备数据
java.util.List<String> fruits = new java.util.ArrayList<>();
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("橙子");
request.setAttribute("fruitList", fruits);
%>
<c:forEach var="fruit" items="${fruitList}">
<li><%= fruit %></li>
</c:forEach>
更复杂的循环,比如遍历Map:
<%
java.util.Map<String, Integer> scores = new java.util.HashMap<>();
scores.put("张三", 95);
scores.put("李四", 87);
scores.put("王五", 92);
request.setAttribute("scoreMap", scores);
%>
<table border="1">
<tr>
<th>姓名</th>
<th>成绩</th>
</tr>
<c:forEach var="entry" items="${scoreMap}">
<tr>
<td>${entry.key}</td>
<td>${entry.value}</td>
</tr>
</c:forEach>
</table>
4. URL处理:<c:url> 和 <c:redirect>
”`jsp
<c:param name="redirect
