【Day36】EL 表达式与 JSTL 标签库:简化 JSP 开发

本文收录于「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 使用前置准备

  1. 导入依赖 :下载 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>
  2. 引入标签库 :在 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}&nbsp;
    </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 常见问题

  1. JSTL 标签报错 "找不到标签库"
    • 检查 jar 包是否放入WEB-INF/lib目录;
    • 检查taglib指令的 URI 是否正确(核心标签库 URI:http://java.sun.com/jsp/jstl/core);
  2. EL 表达式无法获取数据
    • 检查数据是否正确存入对应域(如sessionScope对应 session 域);
    • 检查 JavaBean 是否有 getter 方法和无参构造;
  3. JSTL 循环标签遍历集合报错
    • 确保集合不为 null(可先用${empty list}判断);
    • 遍历 Map 时,用${entry.key}${entry.value}获取键值对;
  4. EL 表达式输出 null
    • EL 获取不存在的域数据时返回空字符串,若需判断是否为 null,用${variable == null}

六、今日实战小任务

  1. 用 EL+JSTL 实现 "九九乘法表"(无任何 Java 脚本片段);
  2. 编写 Servlet 存储一个商品列表(包含商品名、价格),在 JSP 中用<c:forEach>遍历展示成表格;
  3. 实现 "登录成功后展示用户信息,未登录则重定向到登录页" 的逻辑(仅用 EL+JSTL)。

总结

  1. EL 表达式核心用于简化域数据获取,语法${},支持自动查找域、对象 / 集合访问、各类运算,获取不存在的数据时返回空字符串;
  2. JSTL 标签库需先导入 jar 包和taglib指令,核心标签有<c:if>(条件判断)、<c:choose>(多条件)、<c:forEach>(循环)、<c:set>(存储数据);
  3. 实战中,Servlet 负责业务逻辑和数据存储,JSP 通过 EL+JSTL 获取数据、实现页面逻辑,告别 Java 脚本片段,让 JSP 更简洁易维护。

下一篇【Day37】预告:Java Web 过滤器(Filter):拦截请求、统一处理乱码 / 登录验证,关注专栏持续解锁 Java Web 核心知识点~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!

相关推荐
云泽8082 小时前
深入浅出 C++ 继承:从基础概念到模板、转换与作用域的实战指南
开发语言·c++
Li_yizYa2 小时前
谈谈Java集合中的fail-fast和fail-safe
java·开发语言
十五年专注C++开发2 小时前
CMake进阶:模块模式示例FindOpenCL.cmake详解
开发语言·c++·cmake·跨平台编译
蜜汁小强2 小时前
macOS 上管理不同版本的python
开发语言·python·macos
肥硕之虎2 小时前
从原理到实操:php://filter 伪协议玩转文件包含漏洞
开发语言·网络安全·php
曹轲恒2 小时前
SpringBoot配置文件(1)
java·spring boot·后端
a努力。2 小时前
中国电网Java面试被问:RPC序列化的协议升级和向后兼容
java·开发语言·elasticsearch·面试·职场和发展·rpc·jenkins
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于SSM框架的月子中心管理系统的设计与实现为例,包含答辩的问题和答案
java
csbysj20202 小时前
Bootstrap4 徽章(Badges)
开发语言