本文收录于「Java 学习日记:从入门到架构师」专栏,聚焦 JSP 进阶开发核心技术,带你告别繁琐的 Java 脚本片段,用 EL 表达式 + JSTL 标签库优雅实现页面逻辑,大幅提升 JSP 开发效率~
一、为什么要学 EL+JSTL?
在上一篇 JSP 的学习中,我们用<% %>脚本片段写页面逻辑,会出现两个核心问题:
- 代码混乱 :HTML 标签和 Java 代码混在一起,可读性差、维护难(比如满屏的
if/for代码); - 语法繁琐 :获取域数据需要写
(String)request.getAttribute("name"),代码冗余; - 易出错:Java 代码语法错误(如少分号)会导致整个 JSP 编译失败,调试成本高。
而EL 表达式(Expression Language)+ JSTL 标签库(JSP Standard Tag Library) 就是为解决这些问题而生:
- EL 表达式:简化域数据的获取,一行代码替代
getAttribute(),语法简洁; - JSTL 标签库:用 XML 标签替代 Java 的
if/for/switch等逻辑代码,让 JSP 页面更 "干净"; - 核心目标:JSP 中几乎不再写 Java 脚本片段,实现 "展示与逻辑分离"。
今天这篇日记,我们从 EL 表达式的核心语法、JSTL 标签库的常用标签入手,手把手实现 "无脚本" 的 JSP 页面。
二、EL 表达式:简化数据获取的 "语法糖"
1. EL 表达式核心认知
EL(Expression Language)表达式是 JSP 内置的表达式语言,核心作用:
- 简化域数据(request/session/application 等)的获取;
- 支持算术、逻辑、比较等运算;
- 语法:
${表达式},无需写<% %>,直接嵌入 HTML 中。
EL 表达式的核心优势:
jsp
<%-- 传统方式获取session数据(繁琐) --%>
<% String username = (String) session.getAttribute("loginUser"); %>
用户名:<%= username %><br>
<%-- EL表达式获取(简洁) --%>
用户名:${sessionScope.loginUser}<br>
2. EL 表达式核心语法
(1)获取域数据(核心)
EL 表达式默认会按「page → request → session → application」的顺序查找域数据,也可指定作用域:
| 语法 | 作用域 | 等价 Java 代码 |
|---|---|---|
${name} |
自动查找所有域 | pageContext.findAttribute("name") |
${pageScope.name} |
页面域 | pageContext.getAttribute("name") |
${requestScope.name} |
请求域 | request.getAttribute("name") |
${sessionScope.name} |
会话域 | session.getAttribute("name") |
${applicationScope.name} |
应用域 | application.getAttribute("name") |
实战示例:存储并获取域数据
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>EL获取域数据</title>
</head>
<body>
<%-- 1. 存储数据到不同域(仅演示,实际开发中由Servlet存储) --%>
<%
pageContext.setAttribute("pageMsg", "页面域数据");
request.setAttribute("reqMsg", "请求域数据");
session.setAttribute("sessMsg", "会话域数据");
application.setAttribute("appMsg", "应用域数据");
%>
<%-- 2. EL表达式获取 --%>
页面域:${pageScope.pageMsg}<br>
请求域:${requestScope.reqMsg}<br>
会话域:${sessionScope.sessMsg}<br>
应用域:${applicationScope.appMsg}<br>
自动查找:${reqMsg} <%-- 等价于requestScope.reqMsg --%>
</body>
</html>
(2)获取对象 / 集合数据
EL 支持直接获取 JavaBean 对象的属性、List 集合、Map 集合的数据,无需手动强转。
准备 JavaBean(User.java):
java
运行
java
// 放在WEB-INF/classes/com/example/目录下
package com.example;
public class User {
private String username;
private Integer age;
// 必须有无参构造(EL反射获取属性需要)
public User() {}
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// getter/setter(必须有,EL通过getter获取属性)
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
EL 获取对象 / 集合数据示例:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"
import="com.example.User,java.util.List,java.util.ArrayList,java.util.Map,java.util.HashMap" %>
<html>
<head>
<title>EL获取对象/集合</title>
</head>
<body>
<%-- 1. 存储对象、List、Map到request域 --%>
<%
// JavaBean对象
User user = new User("Java日记", 18);
request.setAttribute("user", user);
// List集合
List<String> hobbyList = new ArrayList<>();
hobbyList.add("编程");
hobbyList.add("学习");
request.setAttribute("hobbyList", hobbyList);
// Map集合
Map<String, String> cityMap = new HashMap<>();
cityMap.put("bj", "北京");
cityMap.put("sh", "上海");
request.setAttribute("cityMap", cityMap);
%>
<%-- 2. EL获取数据 --%>
<%-- JavaBean对象:通过属性名获取(本质调用getUsername()) --%>
用户名:${user.username}<br>
年龄:${user.age}<br>
<%-- List集合:通过索引获取 --%>
第一个爱好:${hobbyList[0]}<br>
第二个爱好:${hobbyList[1]}<br>
<%-- Map集合:通过key获取(两种方式) --%>
北京:${cityMap.bj}<br>
上海:${cityMap["sh"]}<br>
</body>
</html>
(3)EL 表达式的运算
EL 支持算术、比较、逻辑、空值判断等运算,无需写 Java 代码:
| 运算类型 | 运算符 | 示例 | 说明 |
|---|---|---|---|
| 算术运算 | +、-、*、/、% | ${10 + 20} |
加法(注意:+ 仅做算术运算,不拼接字符串) |
| 比较运算 | ==、!=、>、< | ${user.age >= 18} |
判断年龄是否大于等于 18 |
| 逻辑运算 | &&、||、! | ${flag && isLogin} |
逻辑与(也可写 and/or/not) |
| 空值判断 | empty | ${empty user} |
判断 user 是否为 null / 空集合 |
实战示例:EL 运算
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>EL运算</title>
</head>
<body>
<%
request.setAttribute("num1", 10);
request.setAttribute("num2", 5);
request.setAttribute("flag", true);
request.setAttribute("emptyList", new ArrayList<>());
%>
算术运算:${num1 + num2} → ${num1 - num2} → ${num1 * num2} → ${num1 / num2}<br>
比较运算:${num1 > num2} → ${num1 == num2}<br>
逻辑运算:${flag && (num1 > num2)} → ${!flag}<br>
空值判断:${empty emptyList} → ${not empty num1}<br>
</body>
</html>
3. EL 表达式避坑要点
- 空值处理:EL 获取不存在的域数据时,不会报错,而是返回空字符串(对比 Java 代码会返回 null,易空指针);
- 加号运算 :EL 的
+仅做算术运算,不能拼接字符串(如${"123" + 456}结果是 579,而非 "123456"); - JavaBean 属性 :必须有无参构造和 getter 方法,属性名要和 EL 中的名称一致(如
user.username对应getUsername())。
三、JSTL 标签库:用标签替代 Java 逻辑
1. JSTL 核心认知
JSTL(JSP Standard Tag Library)是 JSP 标准标签库,提供了一系列 XML 标签,替代 JSP 中的 Java 脚本片段(如if/for)。
(1)JSTL 使用前置准备
-
导入依赖 :下载 JSTL 的 jar 包(
jstl-1.2.jar+standard-1.1.2.jar),放入项目的WEB-INF/lib目录;- Maven 依赖(推荐):
xml
XML<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> -
引入标签库 :在 JSP 页面顶部添加
taglib指令,核心标签库的 URI 是http://java.sun.com/jsp/jstl/core:jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
2. JSTL 核心标签(必掌握)
JSTL 有多个标签库,核心常用的是核心标签库(c 标签),涵盖条件判断、循环、域数据操作等。
(1)条件判断标签:<c:if>
替代 Java 的if语句,语法:
jsp
<c:if test="EL表达式" var="变量名" scope="作用域">
满足条件时执行的内容
</c:if>
实战示例:判断用户是否成年
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL if标签</title>
</head>
<body>
<% request.setAttribute("age", 18); %>
<c:if test="${age >= 18}" var="isAdult" scope="request">
<h3>你是成年人!</h3>
</c:if>
<c:if test="${!isAdult}">
<h3>你是未成年人!</h3>
</c:if>
</body>
</html>
(2)多条件判断标签:<c:choose>+<c:when>+<c:otherwise>
替代 Java 的if-else if-else,语法:
jsp
<c:choose>
<c:when test="EL表达式1">条件1满足的内容</c:when>
<c:when test="EL表达式2">条件2满足的内容</c:when>
<c:otherwise>所有条件都不满足的内容</c:otherwise>
</c:choose>
实战示例:成绩等级判断
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL choose标签</title>
</head>
<body>
<% request.setAttribute("score", 85); %>
<c:choose>
<c:when test="${score >= 90}">
成绩等级:优秀
</c:when>
<c:when test="${score >= 80 && score < 90}">
成绩等级:良好
</c:when>
<c:when test="${score >= 60 && score < 80}">
成绩等级:及格
</c:when>
<c:otherwise>
成绩等级:不及格
</c:otherwise>
</c:choose>
</body>
</html>
(3)循环标签:<c:forEach>
替代 Java 的for循环(普通循环 / 增强 for),是 JSTL 最常用的标签,语法:
jsp
<%-- 普通循环(遍历数字) --%>
<c:forEach begin="起始值" end="结束值" step="步长" var="变量名">
循环内容
</c:forEach>
<%-- 增强循环(遍历集合) --%>
<c:forEach items="集合/数组" var="变量名" varStatus="循环状态对象">
循环内容
</c:forEach>
实战示例 1:遍历数字(1-10)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL forEach(数字)</title>
</head>
<body>
<h3>遍历1-10的偶数:</h3>
<c:forEach begin="2" end="10" step="2" var="i">
${i}
</c:forEach>
</body>
</html>
实战示例 2:遍历集合(User 列表)
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"
import="com.example.User,java.util.List,java.util.ArrayList" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL forEach(集合)</title>
</head>
<body>
<%
// 准备User列表
List<User> userList = new ArrayList<>();
userList.add(new User("张三", 20));
userList.add(new User("李四", 22));
userList.add(new User("王五", 19));
request.setAttribute("userList", userList);
%>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>索引</th>
<th>序号</th>
<th>用户名</th>
<th>年龄</th>
</tr>
<c:forEach items="${userList}" var="user" varStatus="status">
<tr>
<td>${status.index}</td> <%-- 索引(从0开始) --%>
<td>${status.count}</td> <%-- 序号(从1开始) --%>
<td>${user.username}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
(4)域数据操作标签:<c:set>+<c:remove>
<c:set>:替代setAttribute(),存储数据到域中;<c:remove>:替代removeAttribute(),删除域中的数据。
实战示例:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL set/remove标签</title>
</head>
<body>
<%-- 存储数据到session域 --%>
<c:set var="msg" value="Hello JSTL" scope="session"></c:set>
存储的内容:${sessionScope.msg}<br>
<%-- 删除session域中的msg --%>
<c:remove var="msg" scope="session"></c:remove>
删除后:${sessionScope.msg} <%-- 返回空字符串 --%>
</body>
</html>
四、实战:EL+JSTL 实现 "无脚本" 登录页面
1. 登录处理 Servlet(LoginServlet.java)
java
运行
java
import com.example.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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 解决乱码
request.setCharacterEncoding("UTF-8");
// 2. 获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 3. 模拟登录校验
if ("admin".equals(username) && "123456".equals(password)) {
// 4. 存储用户信息到session
User user = new User(username, 18);
request.getSession().setAttribute("loginUser", user);
// 5. 转发到个人中心
request.getRequestDispatcher("/userCenter.jsp").forward(request, response);
} else {
// 6. 存储错误信息到request域
request.setAttribute("errorMsg", "用户名或密码错误!");
// 7. 转发回登录页
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
2. 登录页面(login.jsp)
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>
</head>
<body>
<h1>用户登录</h1>
<%-- EL显示错误信息(有则显示,无则不显示) --%>
<c:if test="${not empty errorMsg}">
<font color="red">${errorMsg}</font><br>
</c:if>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
3. 个人中心页面(userCenter.jsp)
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>
</head>
<body>
<%-- 判断是否登录(无脚本) --%>
<c:if test="${empty sessionScope.loginUser}">
<%-- 未登录,重定向到登录页 --%>
<c:redirect url="${pageContext.request.contextPath}/login.jsp"></c:redirect>
</c:if>
<h1>欢迎你,${sessionScope.loginUser.username}!</h1>
<p>年龄:${sessionScope.loginUser.age}</p>
<a href="${pageContext.request.contextPath}/logout">退出登录</a>
</body>
</html>
4. 退出登录 Servlet(LogoutServlet.java)
java
运行
java
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;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 销毁session
request.getSession().invalidate();
// 重定向到登录页
response.sendRedirect(request.getContextPath() + "/login.jsp");
}
}
五、避坑指南:EL+JSTL 常见问题
- JSTL 标签报错 "找不到标签库" :
- 检查 jar 包是否放入
WEB-INF/lib目录; - 检查
taglib指令的 URI 是否正确(核心标签库 URI:http://java.sun.com/jsp/jstl/core);
- 检查 jar 包是否放入
- EL 表达式无法获取数据 :
- 检查数据是否正确存入对应域(如
sessionScope对应 session 域); - 检查 JavaBean 是否有 getter 方法和无参构造;
- 检查数据是否正确存入对应域(如
- JSTL 循环标签遍历集合报错 :
- 确保集合不为 null(可先用
${empty list}判断); - 遍历 Map 时,用
${entry.key}和${entry.value}获取键值对;
- 确保集合不为 null(可先用
- EL 表达式输出 null :
- EL 获取不存在的域数据时返回空字符串,若需判断是否为 null,用
${variable == null}。
- EL 获取不存在的域数据时返回空字符串,若需判断是否为 null,用
六、今日实战小任务
- 用 EL+JSTL 实现 "九九乘法表"(无任何 Java 脚本片段);
- 编写 Servlet 存储一个商品列表(包含商品名、价格),在 JSP 中用
<c:forEach>遍历展示成表格; - 实现 "登录成功后展示用户信息,未登录则重定向到登录页" 的逻辑(仅用 EL+JSTL)。
总结
- EL 表达式核心用于简化域数据获取,语法
${},支持自动查找域、对象 / 集合访问、各类运算,获取不存在的数据时返回空字符串; - JSTL 标签库需先导入 jar 包和
taglib指令,核心标签有<c:if>(条件判断)、<c:choose>(多条件)、<c:forEach>(循环)、<c:set>(存储数据); - 实战中,Servlet 负责业务逻辑和数据存储,JSP 通过 EL+JSTL 获取数据、实现页面逻辑,告别 Java 脚本片段,让 JSP 更简洁易维护。
下一篇【Day37】预告:Java Web 过滤器(Filter):拦截请求、统一处理乱码 / 登录验证,关注专栏持续解锁 Java Web 核心知识点~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!