Servlet交互_作用域_EL_JSTL详解
Servlet 交互 + 作用域 + EL + JSTL 详解
本章位置:第二阶段 Java 核心框架
前置知识:Servlet、JSP、JavaBean、集合框架、HTTP 基础
本章内容:Servlet 与 JSP 数据交互、四大作用域、EL 表达式、JSTL
下一篇:Web CRUD / 项目 - MVC
学习目标:掌握 Servlet 与 JSP 的数据传递方式、四大作用域的区别、EL 表达式取值规则、JSTL 条件与循环标签,并能够完成传统 Java Web 的动态页面展示。
一、本章要解决什么问题
上一章已经会:
Servlet
↓
request.setAttribute(...)
↓
forward
↓
JSP
但是 JSP 中我们还在写:
<%
Student student =
(Student)
request.getAttribute(
"student"
);
%>
<%= student.getName() %>
或者:
<%
for (
Student student :
students
) {
%>
<p>
<%= student.getName() %>
</p>
<%
}
%>
问题很明显:
Java 代码太多
HTML 被 Java 切碎
强制类型转换多
页面很难维护
所以这一章学习:
作用域
EL
JSTL
把 JSP 页面变得:
更像真正的模板页面
二、Servlet 和 JSP 如何交互
最常见流程:
Servlet 查询数据
↓
放入作用域
↓
forward 到 JSP
↓
JSP 读取数据
↓
生成 HTML
例如:
List<Student> students =
studentService.findAll();
request.setAttribute(
"students",
students
);
request
.getRequestDispatcher(
"/WEB-INF/views/student-list.jsp"
)
.forward(
request,
response
);
JSP:
${students}
这就是最基本的数据交互。
三、什么是作用域
作用域:
Scope
可以理解为:
一个数据能够保存多长时间、能够被哪些请求或页面访问。
JSP / Servlet 中常见四个作用域:
page
request
session
application
范围从小到大:
page
<
request
<
session
<
application
四、page 作用域
作用范围:
当前 JSP 页面
通常通过:
pageContext
保存。
例如:
<%
pageContext.setAttribute(
"message",
"当前页面数据"
);
%>
读取:
<%= pageContext.getAttribute("message") %>
五、page 作用域什么时候消失
当前 JSP 页面执行结束后:
基本就失效
它是:
范围最小的作用域
六、request 作用域
作用范围:
一次 HTTP 请求
Servlet:
request.setAttribute(
"student",
student
);
转发:
request
.getRequestDispatcher(
"/WEB-INF/views/detail.jsp"
)
.forward(
request,
response
);
JSP 仍然能取:
request.getAttribute(
"student"
);
七、为什么 forward 后 request 数据还在
因为 forward:
还是同一次请求
流程:
浏览器
↓
Servlet
↓
JSP
request 没换。
八、为什么 redirect 后 request 数据丢失
因为 redirect:
浏览器重新发送一次新请求
原来的:
request.setAttribute(...)
属于:
旧 request
所以新请求拿不到。
九、request 最适合什么数据
最常见:
查询结果
详情对象
表单错误信息
当前页面列表
当前请求临时数据
例如:
学生列表
商品详情
搜索结果
这些数据只需要:
当前页面展示一次
request 最适合。
十、session 作用域
session:
会话作用域
主要针对:
同一个用户的一段连续访问
例如:
HttpSession session =
request.getSession();
保存:
session.setAttribute(
"loginUser",
user
);
十一、session 常见场景
例如:
登录用户
购物车
验证码状态
用户偏好
跨请求临时业务状态
十二、为什么登录用户适合 session
用户登录成功:
登录请求结束后
后面还要访问:
首页
个人中心
订单页
退出
如果只放 request:
下一次请求就没了
所以放:
session
十三、session 获取数据
User user =
(User)
session.getAttribute(
"loginUser"
);
JSP 后面可以:
${sessionScope.loginUser}
十四、session 删除数据
退出登录:
session.removeAttribute(
"loginUser"
);
或者直接:
session.invalidate();
十五、invalidate()
session.invalidate();
表示:
让整个当前 Session 失效
常见:
退出登录
十六、session 不是永久保存
Session 通常会因为:
超时
服务器重启
主动 invalidate
会话失效
而消失。
所以:
重要业务数据
不能只存在 Session。
十七、application 作用域
application 本质:
ServletContext
获取:
ServletContext application =
getServletContext();
保存:
application.setAttribute(
"onlineCount",
10
);
十八、application 范围
作用范围:
整个 Web 应用
多个用户:
多个 Session
多个请求
多个 Servlet
都可能访问同一个 application 数据。
十九、application 适合什么
例如:
应用级配置
全局统计
全局缓存
公共数据
但一定注意:
线程安全
二十、application 不适合保存登录用户
如果:
application.setAttribute(
"loginUser",
user
);
那么:
整个系统所有用户共享
会严重混乱。
登录用户应该:
session
二十一、四大作用域对比
| 作用域 | 对象 | 生命周期 | 常见用途 |
|---|---|---|---|
| page | pageContext |
当前 JSP | 页面内部临时数据 |
| request | request |
一次请求 | 查询结果、错误信息 |
| session | session |
用户会话 | 登录用户、购物车 |
| application | ServletContext |
整个应用 | 全局配置、共享统计 |
二十二、作用域选择原则
推荐:
能用小作用域,就不要用大作用域。
例如:
当前页面展示学生列表
用:
request
不要:
session
更不要:
application
二十三、为什么小作用域更好
因为大作用域可能:
占内存更久
产生脏数据
状态难管理
用户之间互相影响
线程安全复杂
二十四、EL 是什么
EL:
Expression Language
中文:
表达式语言
作用:
让 JSP 更方便地读取作用域中的数据。
传统:
<%= request.getAttribute("name") %>
EL:
${name}
二十五、EL 基本语法
${表达式}
例如:
${name}
二十六、Servlet 设置数据
request.setAttribute(
"name",
"张三"
);
JSP:
${name}
输出:
张三
二十七、EL 默认查找作用域顺序
如果写:
${name}
EL 会按:
page
↓
request
↓
session
↓
application
顺序查找。
找到第一个就使用。
二十八、为什么最好知道明确作用域
如果多个作用域都有:
name
${name}
可能读到:
范围最近的那个
所以有时可以明确写:
${requestScope.name}
二十九、EL 四个 Scope 对象
pageScope
requestScope
sessionScope
applicationScope
例如:
${requestScope.student}
${sessionScope.loginUser}
${applicationScope.onlineCount}
三十、EL 访问 JavaBean 属性
Servlet:
Student student =
new Student();
student.setName(
"张三"
);
student.setAge(
20
);
request.setAttribute(
"student",
student
);
JSP:
${student.name}
${student.age}
三十一、${student.name} 底层是什么
它不是直接访问:
private String name;
而是按照 JavaBean 规则:
找到 name 属性
↓
调用 getName()
所以前面学习:
JavaBean
内省
Getter
现在真正用上了。
三十二、boolean 属性
例如:
public boolean isEnabled() {
}
EL:
${user.enabled}
仍然可以按照 Bean 属性规则读取。
三十三、EL 访问 Map
Servlet:
Map<String, Object> data =
new HashMap<>();
data.put(
"name",
"张三"
);
request.setAttribute(
"data",
data
);
JSP:
${data.name}
也可以:
${data["name"]}
三十四、点语法和中括号语法
${student.name}
适合:
普通属性名
中括号:
${data["user-name"]}
适合:
包含特殊字符
动态 key
三十五、EL 访问 List
Servlet:
List<String> names =
List.of(
"张三",
"李四",
"王五"
);
request.setAttribute(
"names",
names
);
JSP:
${names[0]}
结果:
张三
三十六、EL 访问数组
String[] colors =
{
"red",
"blue"
};
JSP:
${colors[0]}
三十七、EL 取请求参数
EL 提供:
param
例如浏览器:
/search?keyword=java
JSP:
${param.keyword}
三十八、多值参数
EL:
paramValues
例如 checkbox:
${paramValues.hobby[0]}
三十九、EL 请求头
header
例如:
${header["User-Agent"]}
四十、EL Cookie
EL:
cookie
例如:
${cookie.JSESSIONID.value}
四十一、EL initParam
可以读取:
ServletContext 初始化参数
例如:
${initParam.appName}
四十二、pageContext
EL 中:
${pageContext}
是一个特殊内置对象。
非常常用:
${pageContext.request.contextPath}
四十三、contextPath
推荐 JSP 页面:
${pageContext.request.contextPath}
例如:
<form
action="${pageContext.request.contextPath}/login"
method="post"
>
比传统:
<%= request.getContextPath() %>
更干净。
四十四、EL 运算符
EL 支持:
算术
比较
逻辑
empty
三元
四十五、算术
${10 + 20}
${10 - 3}
${10 * 2}
${10 / 2}
${10 % 3}
四十六、比较运算
${age > 18}
${score >= 60}
${a == b}
${a != b}
也支持文字形式:
gt
ge
lt
le
eq
ne
四十七、逻辑运算
${age >= 18 && enabled}
${a || b}
${!enabled}
也可以:
and
or
not
四十八、empty
非常常用:
${empty student}
${empty students}
${empty message}
可以判断:
null
空字符串
空集合
空数组
四十九、not empty
${not empty students}
表示:
students 不为空
五十、三元表达式
${score >= 60 ? "及格" : "不及格"}
适合:
简单展示判断
不要塞太复杂的业务规则。
五十一、EL null 的特点
如果:
${student.name}
但 student 不存在,
很多情况下 EL 会:
输出空字符串
而不是像传统 Java 那样直接 NPE。
这让 JSP 页面更友好。
五十二、但不能因此忽略业务校验
页面没报错:
不代表数据正确
Servlet / Service 仍然应该:
校验数据
处理不存在
五十三、JSTL 是什么
JSTL:
JSP Standard Tag Library
中文:
JSP 标准标签库
主要用于:
条件判断
循环
变量
URL
格式化
让 JSP 少写 Java 脚本。
五十四、为什么需要 JSTL
传统 JSP:
<%
if (
score >= 60
) {
%>
及格
<%
} else {
%>
不及格
<%
}
%>
JSTL:
<c:choose>
<c:when
test="${score >= 60}"
>
及格
</c:when>
<c:otherwise>
不及格
</c:otherwise>
</c:choose>
虽然标签多一点,
但:
HTML 结构更清楚
五十五、Tomcat 8.5 中 JSTL 依赖
传统 javax Servlet 环境可以添加:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
注意:
Tomcat 8.5
通常是 javax 体系
Tomcat 10+ Jakarta 环境:
依赖坐标和标签库版本会不同
不要混用。
五十六、引入 JSTL Core 标签库
JSP 顶部:
<%@ taglib
prefix="c"
uri="http://java.sun.com/jsp/jstl/core"
%>
之后可以使用:
<c:if>
<c:forEach>
<c:choose>
五十七、prefix=“c”
c
只是:
标签前缀
所以:
<c:if>
表示:
使用 JSTL Core 标签
五十八、c:set
设置变量:
<c:set
var="name"
value="张三"
/>
读取:
${name}
五十九、c:set 指定作用域
<c:set
var="name"
value="张三"
scope="request"
/>
scope 可以:
page
request
session
application
六十、c:remove
删除变量:
<c:remove
var="name"
scope="session"
/>
六十一、c:if
条件判断:
<c:if
test="${student.score >= 60}"
>
<span>
及格
</span>
</c:if>
六十二、c:if 没有 else
JSTL 的:
<c:if>
本身没有:
else
如果需要:
if / else
使用:
c:choose
六十三、c:choose
<c:choose>
<c:when
test="${student.score >= 90}"
>
优秀
</c:when>
<c:when
test="${student.score >= 60}"
>
及格
</c:when>
<c:otherwise>
不及格
</c:otherwise>
</c:choose>
六十四、c:forEach
这是最重要的 JSTL 标签之一。
Servlet:
request.setAttribute(
"students",
students
);
JSP:
<c:forEach
items="${students}"
var="student"
>
<p>
${student.name}
</p>
</c:forEach>
六十五、items
items="${students}"
表示:
要遍历的数据
可以是:
List
Set
数组
Map
六十六、var
var="student"
表示:
当前遍历元素变量名
然后:
${student.name}
六十七、varStatus
<c:forEach
items="${students}"
var="student"
varStatus="status"
>
可以使用:
status.index
status.count
status.first
status.last
六十八、index 和 count
${status.index}
从:
0
开始。
${status.count}
从:
1
开始。
六十九、first / last
${status.first}
${status.last}
返回:
boolean
可以判断:
是不是第一条
是不是最后一条
七十、c:forEach 数字循环
<c:forEach
begin="1"
end="5"
var="i"
>
${i}
</c:forEach>
输出:
1 2 3 4 5
七十一、step
<c:forEach
begin="1"
end="10"
step="2"
var="i"
>
得到:
1 3 5 7 9
七十二、遍历 Map
Servlet:
Map<String, Integer> scores =
new HashMap<>();
scores.put(
"张三",
90
);
scores.put(
"李四",
85
);
request.setAttribute(
"scores",
scores
);
JSP:
<c:forEach
items="${scores}"
var="entry"
>
${entry.key}
=
${entry.value}
</c:forEach>
七十三、c:out
安全输出:
<c:out
value="${student.name}"
/>
七十四、为什么推荐 c:out
如果用户输入:
<script>
alert(1)
</script>
直接:
${comment.content}
在某些场景下可能被浏览器当 HTML 解释。
c:out 默认会:
转义 XML / HTML 特殊字符
可以降低:
XSS 风险
七十五、escapeXml
<c:out
value="${content}"
escapeXml="true"
/>
默认通常:
true
不要轻易关闭。
七十六、c:url
用于生成 URL:
<c:url
value="/student/list"
var="studentListUrl"
/>
使用:
<a
href="${studentListUrl}"
>
学生列表
</a>
七十七、c:url 的价值
它可以帮助处理:
Context Path
URL 编码
传统写法:
${pageContext.request.contextPath}/student/list
也很常用。
七十八、c:param
可以给 URL 添加参数:
<c:url
value="/student/detail"
var="detailUrl"
>
<c:param
name="id"
value="${student.id}"
/>
</c:url>
结果类似:
/demo/student/detail?id=1
七十九、c:redirect
<c:redirect
url="/login.jsp"
/>
可以重定向。
但请求控制更推荐:
Servlet
八十、c:catch
可以捕获标签体异常:
<c:catch
var="error"
>
...
</c:catch>
但 JSP 不应该承担复杂异常处理。
八十一、JSTL Core 常用标签总结
必须掌握:
c:set
c:remove
c:if
c:choose
c:when
c:otherwise
c:forEach
c:out
c:url
c:param
八十二、EL + JSTL 学生列表
Servlet:
@WebServlet(
"/student/list"
)
public class StudentListServlet
extends HttpServlet {
private final StudentService
studentService =
new StudentService();
@Override
protected void doGet(
HttpServletRequest request,
HttpServletResponse response
)
throws ServletException,
IOException {
List<Student> students =
studentService.findAll();
request.setAttribute(
"students",
students
);
request
.getRequestDispatcher(
"/WEB-INF/views/student-list.jsp"
)
.forward(
request,
response
);
}
}
八十三、student-list.jsp 标准版
<%@ page
contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"
%>
<%@ taglib
prefix="c"
uri="http://java.sun.com/jsp/jstl/core"
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
学生列表
</title>
</head>
<body>
<h1>
学生列表
</h1>
<c:choose>
<c:when
test="${empty students}"
>
<p>
暂无学生数据
</p>
</c:when>
<c:otherwise>
<table
border="1"
>
<thead>
<tr>
<th>
序号
</th>
<th>
学号
</th>
<th>
姓名
</th>
<th>
年龄
</th>
<th>
专业
</th>
<th>
成绩
</th>
</tr>
</thead>
<tbody>
<c:forEach
items="${students}"
var="student"
varStatus="status"
>
<tr>
<td>
${status.count}
</td>
<td>
<c:out
value="${student.studentNo}"
/>
</td>
<td>
<c:out
value="${student.name}"
/>
</td>
<td>
${student.age}
</td>
<td>
<c:out
value="${student.major}"
/>
</td>
<td>
${student.score}
</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:otherwise>
</c:choose>
</body>
</html>
八十四、传统脚本版和 EL/JSTL 版对比
传统:
<%
List<Student> students =
(List<Student>)
request.getAttribute(
"students"
);
for (
Student student :
students
) {
%>
<%= student.getName() %>
<%
}
%>
EL / JSTL:
<c:forEach
items="${students}"
var="student"
>
${student.name}
</c:forEach>
明显更:
清晰
接近 HTML
减少 Java 代码
八十五、登录状态显示案例
登录成功:
request
.getSession()
.setAttribute(
"loginUser",
user
);
JSP:
<c:choose>
<c:when
test="${not empty sessionScope.loginUser}"
>
欢迎:
<c:out
value="${sessionScope.loginUser.username}"
/>
</c:when>
<c:otherwise>
<a
href="${pageContext.request.contextPath}/login.jsp"
>
登录
</a>
</c:otherwise>
</c:choose>
八十六、退出登录
Servlet:
@WebServlet(
"/logout"
)
public class LogoutServlet
extends HttpServlet {
@Override
protected void doPost(
HttpServletRequest request,
HttpServletResponse response
)
throws IOException {
HttpSession session =
request.getSession(
false
);
if (
session != null
) {
session.invalidate();
}
response.sendRedirect(
request.getContextPath()
+ "/login.jsp"
);
}
}
八十七、getSession(false)
request.getSession(
false
);
表示:
如果已有 Session
就返回
如果没有
不要新建
退出登录时很适合。
八十八、getSession()
默认:
request.getSession();
如果没有 Session:
会创建一个
八十九、Session ID
Servlet 容器通常通过:
JSESSIONID
识别用户 Session。
浏览器一般通过:
Cookie
保存。
九十、为什么不同用户 Session 不一样
因为每个浏览器会话通常携带:
不同 JSESSIONID
Tomcat 根据 ID 找:
对应 HttpSession
九十一、Session 超时
web.xml 可以配置:
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
通常单位:
分钟
九十二、Session 超时后
例如:
30 分钟无操作
Session 可能失效。
如果:
loginUser
在 Session 中:
用户需要重新登录
九十三、登录状态判断
Servlet:
HttpSession session =
request.getSession(
false
);
User loginUser = null;
if (
session != null
) {
loginUser =
(User)
session.getAttribute(
"loginUser"
);
}
如果:
loginUser == null
说明:
未登录
九十四、以后为什么要学 Filter
如果每个 Servlet 都写:
if (
loginUser == null
) {
redirect login;
}
大量重复。
后面可以用:
Filter
统一做:
登录校验
编码处理
日志
九十五、作用域名称冲突案例
假设:
request.setAttribute(
"name",
"request-name"
);
session.setAttribute(
"name",
"session-name"
);
JSP:
${name}
会优先找到:
request
因为查找顺序:
page
request
session
application
九十六、明确指定作用域
${requestScope.name}
${sessionScope.name}
这样最清楚。
九十七、作用域数据覆盖不是同一个 Map
即使 key 都是:
name
它们分别存在:
不同作用域
所以不会互相真正覆盖。
只是:
EL 自动查找时
存在优先级
九十八、EL 访问嵌套属性
例如:
class Student {
private School school;
}
School:
class School {
private String name;
}
JSP:
${student.school.name}
相当于:
student.getSchool().getName()
九十九、EL 动态 Map key
${data[key]}
如果 key 变量值:
name
就等价:
${data["name"]}
一百、EL 中字符串
${status == "ENABLED"}
可以用于简单展示判断。
但复杂业务规则不要堆在 JSP。
一百零一、JSTL if 展示按钮
例如:
<c:if
test="${sessionScope.loginUser.role == 'ADMIN'}"
>
<button>
删除用户
</button>
</c:if>
注意:
前端隐藏按钮
不等于真正权限控制
一百零二、为什么页面权限不安全
用户可以:
自己构造 HTTP 请求
即使按钮隐藏,
仍可能直接调用:
删除接口
所以权限必须:
后端再次校验
一百零三、EL / JSTL 只是展示层
应该用于:
展示判断
循环
页面输出
不应该承担:
真正业务权限
事务
数据库逻辑
一百零四、表单错误信息
Servlet:
request.setAttribute(
"error",
"用户名不能为空"
);
JSP:
<c:if
test="${not empty error}"
>
<p>
<c:out
value="${error}"
/>
</p>
</c:if>
一百零五、表单回显
Servlet:
request.setAttribute(
"form",
student
);
JSP:
<input
name="name"
value="${form.name}"
>
一百零六、HTML 属性中的 XSS 风险
如果直接:
value="${form.name}"
用户输入包含:
"
<
>
可能造成 HTML 属性注入风险。
传统 JSP 项目应:
谨慎转义
实际生产项目通常使用:
安全模板机制
框架转义
输入验证
一百零七、c:out 更适合文本节点
例如:
<p>
<c:out
value="${form.name}"
/>
</p>
安全性通常比直接输出更好。
一百零八、JSTL 格式化标签库预览
除了 core:
c
还有:
fmt
用于:
日期
数字
国际化
引入:
<%@ taglib
prefix="fmt"
uri="http://java.sun.com/jsp/jstl/fmt"
%>
一百零九、格式化数字
<fmt:formatNumber
value="${student.score}"
pattern="0.00"
/>
一百一十、格式化日期
如果对象里是旧式:
java.util.Date
可以:
<fmt:formatDate
value="${user.createTime}"
pattern="yyyy-MM-dd HH:mm:ss"
/>
现代 Java:
LocalDateTime
在老 JSTL 环境中未必能直接处理得很好,
实际可在后端格式化或使用额外方案。
一百一十一、URL 编码
使用:
<c:url>
和:
<c:param>
可以减少手动拼接 URL 参数的问题。
一百一十二、详情链接案例
<c:url
value="/student/detail"
var="detailUrl"
>
<c:param
name="id"
value="${student.id}"
/>
</c:url>
<a
href="${detailUrl}"
>
查看
</a>
一百一十三、删除按钮推荐 POST
不要:
<a href="/student/delete?id=1">
删除
</a>
更推荐:
<form
action="${pageContext.request.contextPath}/student/delete"
method="post"
>
<input
type="hidden"
name="id"
value="${student.id}"
>
<button
type="submit"
>
删除
</button>
</form>
一百一十四、隐藏字段
<input
type="hidden"
name="id"
value="${student.id}"
>
会随表单一起提交。
但注意:
hidden 不代表安全
用户仍然可以修改。
后端必须再次校验。
一百一十五、Servlet 接收 hidden 参数
String idText =
request.getParameter(
"id"
);
和普通表单参数一样。
一百一十六、分页页面案例
Servlet:
request.setAttribute(
"pageNum",
pageNum
);
request.setAttribute(
"totalPages",
totalPages
);
JSP:
<c:forEach
begin="1"
end="${totalPages}"
var="page"
>
<a
href="${pageContext.request.contextPath}/student/list?pageNum=${page}"
>
${page}
</a>
</c:forEach>
一百一十七、当前页高亮
<c:choose>
<c:when
test="${page == pageNum}"
>
<strong>
${page}
</strong>
</c:when>
<c:otherwise>
<a
href="${pageContext.request.contextPath}/student/list?pageNum=${page}"
>
${page}
</a>
</c:otherwise>
</c:choose>
一百一十八、搜索条件回显
Servlet:
request.setAttribute(
"keyword",
keyword
);
JSP:
<input
name="keyword"
value="${keyword}"
>
一百一十九、Servlet 与 JSP 数据交互完整模型
浏览器
↓
Servlet
↓
request.getParameter()
↓
Service
↓
Mapper
↓
Database
↓
Servlet
↓
request.setAttribute()
↓
forward
↓
JSP
↓
EL
↓
JSTL
↓
HTML
↓
浏览器
一百二十、request 和 session 的关键区别
例如:
学生列表
只当前页面用:
request
登录用户跨很多请求:
session
不要反过来。
一百二十一、Session 滥用案例
错误:
session.setAttribute(
"students",
studentService.findAll()
);
如果只是当前列表页面需要:
没必要放 Session
会导致:
数据过期
占用内存
其他页面误用
一百二十二、application 滥用案例
错误:
application.setAttribute(
"currentStudent",
student
);
多个用户:
可能互相覆盖
一百二十三、线程安全再提醒
Session:
一个用户范围
但一个用户也可能:
同时发多个请求
application:
整个系统共享
所以对其中:
可变对象
操作仍要考虑线程安全。
一百二十四、EL 常见错误:属性没有 Getter
Java:
private String name;
但没有:
getName()
JSP:
${student.name}
可能读取失败。
所以 JavaBean:
Getter 很重要
一百二十五、EL 常见错误:attribute 名不一致
Servlet:
request.setAttribute(
"studentList",
list
);
JSP:
${students}
自然拿不到。
一百二十六、JSTL 常见错误:taglib 没引入
写:
<c:forEach>
但没写:
<%@ taglib
prefix="c"
uri="http://java.sun.com/jsp/jstl/core"
%>
JSP 会报:
标签无法识别
一百二十七、JSTL 常见错误:依赖没加
即使写了 taglib,
项目没有 JSTL 实现:
也可能报找不到标签库
Tomcat 8.5 项目通常需要:
javax.servlet:jstl:1.2
一百二十八、JSTL 常见错误:Tomcat 版本体系混用
Tomcat 8.5:
javax.servlet
Tomcat 10+:
jakarta.servlet
JSTL 依赖也要跟着匹配。
一百二十九、EL 常见错误:禁用了 EL
老项目可能出现:
isELIgnored="true"
这会让:
${name}
不执行。
现代一般保持:
EL 开启
一百三十、EL 输出成原样 ${name}
如果页面直接显示:
${name}
而不是值,
检查:
EL 是否被禁用
JSP 版本
web.xml 版本
文件是不是被当普通 HTML 返回
一百三十一、JSP 不能改成 .html 后还期待 EL 执行
普通:
.html
不会经过 JSP 引擎。
所以:
${student.name}
写在纯 HTML 中:
不会被 JSP 解析
一百三十二、forward 后 URL 为什么不变
因为:
浏览器只请求了一次
Servlet 在服务器内部:
把请求交给 JSP
所以地址栏仍然是:
Servlet URL
这是正常现象。
一百三十三、这样反而是 MVC 推荐行为
例如浏览器:
/student/list
页面实际 JSP:
/WEB-INF/views/student-list.jsp
用户地址栏仍然:
/student/list
这更符合:
Controller URL
而不是暴露:
JSP 文件路径
一百三十四、EL 和 JSTL 不负责数据库访问
不要写成:
JSP
↓
JSTL
↓
数据库
正确:
Servlet / Service
↓
数据库
↓
准备数据
↓
JSP 展示
一百三十五、综合案例:学生管理列表
Servlet:
@WebServlet(
"/student/list"
)
public class StudentListServlet
extends HttpServlet {
private final StudentService
studentService =
new StudentService();
@Override
protected void doGet(
HttpServletRequest request,
HttpServletResponse response
)
throws ServletException,
IOException {
String keyword =
request.getParameter(
"keyword"
);
List<Student> students;
if (
keyword == null
|| keyword.isBlank()
) {
students =
studentService.findAll();
} else {
students =
studentService.findByName(
keyword
);
}
request.setAttribute(
"students",
students
);
request.setAttribute(
"keyword",
keyword
);
request
.getRequestDispatcher(
"/WEB-INF/views/student-list.jsp"
)
.forward(
request,
response
);
}
}
一百三十六、列表 JSP
<%@ page
contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"
%>
<%@ taglib
prefix="c"
uri="http://java.sun.com/jsp/jstl/core"
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
学生管理
</title>
</head>
<body>
<h1>
学生管理
</h1>
<form
action="${pageContext.request.contextPath}/student/list"
method="get"
>
<input
type="text"
name="keyword"
value="${keyword}"
placeholder="请输入姓名"
>
<button
type="submit"
>
搜索
</button>
</form>
<c:choose>
<c:when
test="${empty students}"
>
<p>
暂无数据
</p>
</c:when>
<c:otherwise>
<table
border="1"
>
<thead>
<tr>
<th>
#
</th>
<th>
学号
</th>
<th>
姓名
</th>
<th>
年龄
</th>
<th>
专业
</th>
<th>
操作
</th>
</tr>
</thead>
<tbody>
<c:forEach
items="${students}"
var="student"
varStatus="status"
>
<tr>
<td>
${status.count}
</td>
<td>
<c:out
value="${student.studentNo}"
/>
</td>
<td>
<c:out
value="${student.name}"
/>
</td>
<td>
${student.age}
</td>
<td>
<c:out
value="${student.major}"
/>
</td>
<td>
<c:url
value="/student/detail"
var="detailUrl"
>
<c:param
name="id"
value="${student.id}"
/>
</c:url>
<a
href="${detailUrl}"
>
查看
</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:otherwise>
</c:choose>
</body>
</html>
一百三十七、这个页面已经接近标准传统 MVC
JSP 中:
没有 JDBC
没有 MyBatis
没有 Service
没有 Java for 脚本
没有 Java if 脚本
只剩:
HTML
EL
JSTL
这就是比较合理的 JSP 写法。
一百三十八、练习题 1:四大作用域
分别:
page
request
session
application
存一个:
name
然后使用:
${name}
观察优先级。
一百三十九、练习题 2:Scope 对象
分别输出:
${pageScope.name}
${requestScope.name}
${sessionScope.name}
${applicationScope.name}
一百四十、练习题 3:JavaBean EL
Student:
name
age
major
Servlet setAttribute 后,
JSP 使用:
${student.name}
${student.age}
${student.major}
一百四十一、练习题 4:List + c:forEach
Servlet:
List<Student>
JSP 使用:
c:forEach
输出表格。
一百四十二、练习题 5:c:choose
根据:
score
显示:
优秀
及格
不及格
一百四十三、练习题 6:empty
如果:
students 为空
显示:
暂无数据
否则:
显示表格
一百四十四、练习题 7:Session 登录
登录成功:
session.setAttribute(
"loginUser",
user
);
首页:
显示欢迎信息
退出:
invalidate
一百四十五、练习题 8:c:url
生成:
/student/detail?id=1
不要手工拼参数。
一百四十六、练习题 9:Map 遍历
Map:
Java=90
MySQL=85
Spring=95
使用:
c:forEach
输出 key/value。
一百四十七、练习题 10:综合列表页
实现:
搜索
空数据提示
列表循环
详情链接
登录用户名显示
全部使用:
EL + JSTL
不要写 JSP Java 脚本。
一百四十八、必须掌握的 EL 内置对象
pageScope
requestScope
sessionScope
applicationScope
param
paramValues
header
headerValues
cookie
initParam
pageContext
一百四十九、必须掌握的 EL 写法
${name}
${student.name}
${students[0]}
${map.key}
${map["key"]}
${param.id}
${sessionScope.loginUser}
${empty students}
${not empty students}
${score >= 60 ? "及格" : "不及格"}
${pageContext.request.contextPath}
一百五十、必须掌握 JSTL 标签
<c:set>
<c:remove>
<c:if>
<c:choose>
<c:when>
<c:otherwise>
<c:forEach>
<c:out>
<c:url>
<c:param>
一百五十一、必须回答的问题
学完后应该能够回答:
1. JSP 四大作用域是什么?
2. page/request/session/application 生命周期有什么区别?
3. 为什么当前页面数据更适合放 request?
4. 登录用户为什么适合放 session?
5. application 为什么不能保存当前登录用户?
6. EL 是什么?
7. ${student.name} 实际读取什么?
8. EL 默认查找作用域顺序是什么?
9. requestScope 和 sessionScope 有什么作用?
10. empty 可以判断什么?
11. JSTL 是什么?
12. c:if 和 c:choose 有什么区别?
13. c:forEach 的 items、var、varStatus 分别是什么?
14. status.index 和 status.count 有什么区别?
15. c:out 为什么比直接输出更安全?
16. c:url 有什么作用?
17. Session 为什么可以保持登录状态?
18. getSession(false) 有什么作用?
19. 为什么前端隐藏按钮不等于权限控制?
20. 为什么 EL/JSTL 不能承担业务逻辑?
一百五十二、本章知识结构
Servlet / JSP 数据交互
│
├─ Scope
│ ├─ page
│ ├─ request
│ ├─ session
│ └─ application
│
├─ EL
│ ├─ Bean 属性
│ ├─ List
│ ├─ Map
│ ├─ param
│ ├─ scope 对象
│ ├─ empty
│ └─ 运算符
│
├─ JSTL Core
│ ├─ c:set
│ ├─ c:if
│ ├─ c:choose
│ ├─ c:forEach
│ ├─ c:out
│ └─ c:url
│
├─ Session
│ ├─ 登录用户
│ ├─ JSESSIONID
│ ├─ timeout
│ └─ invalidate
│
└─ MVC
├─ Servlet
├─ Service
├─ Mapper
└─ JSP
一百五十三、从 JSP 脚本到 EL/JSTL
以前:
<%
Student student =
(Student)
request.getAttribute(
"student"
);
%>
<%= student.getName() %>
现在:
${student.name}
以前:
<%
for (
Student student :
students
) {
%>
...
<%
}
%>
现在:
<c:forEach
items="${students}"
var="student"
>
...
</c:forEach>
这就是:
JSP 从 Java 脚本页面
向模板页面的进化
一百五十四、从这一章到 Web CRUD
现在我们已经具备:
Servlet 接收请求
Servlet 获取参数
Service 处理业务
MyBatis 操作数据库
request 传递页面数据
session 保存登录状态
EL 输出数据
JSTL 循环和判断
下一篇就可以真正做:
Web CRUD
也就是:
学生管理系统 / 用户管理系统
完整实现:
列表
新增
修改
删除
详情
搜索
分页
登录
MVC 分层
一百五十五、本章总结
这一章最核心的三个关键词:
Scope
EL
JSTL
四大作用域:
page
当前 JSP
request
一次请求
session
当前用户会话
application
整个 Web 应用
选择原则:
能用小作用域,就不要使用更大的作用域。
EL 负责:
方便地取数据
例如:
${student.name}
${sessionScope.loginUser.username}
${empty students}
JSTL 负责:
页面条件
页面循环
安全输出
URL 构造
例如:
<c:if>
<c:choose>
<c:forEach>
<c:out>
<c:url>
传统 MVC 的完整数据流:
浏览器
↓
Servlet
↓
Service
↓
Mapper
↓
MySQL
↓
Servlet
↓
request.setAttribute
↓
forward
↓
JSP
↓
EL + JSTL
↓
HTML
↓
浏览器
到这里,Servlet + JSP 页面交互体系已经基本完整。
下一篇按照课程表进入:
Web CRUD / 项目 - MVC
会把前面的:
Servlet
JSP
EL
JSTL
MyBatis
Service
Mapper
真正整合成一个完整的 Web 项目。