核心要点:JSP 是一种服务端视图技术,本质上会被编译为 Servlet 执行。虽然在现代项目中已不再是主流,但理解 JSP 有助于掌握 Java Web 技术演进过程。
JSP (Java Server Pages) 是 Java 平台的一种服务端视图技术,允许在 HTML 页面中嵌入 Java 代码片段和 JSP 标签,动态生成 Web 页面。
JSP 的本质:
技术定位:
JSP 文件:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h1>当前时间: <%= new java.util.Date() %></h1>
</body>
</html>编译后的 Servlet(简化版):
public class index_jsp extends HttpServlet {
public void _jspService(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("<html>\n<body>\n");
out.write("<h1>当前时间: ");
out.print(new java.util.Date());
out.write("</h1>\n</body>\n</html>");
}
}| 时间 | 版本 | 特性 |
|---|---|---|
| 1999 | JSP 1.0 | 最初的 JSP 规范,支持脚本片段和标准动作 |
| 2001 | JSP 1.2 | 引入自定义标签库 |
| 2003 | JSP 2.0 | 支持 EL 表达式、简化自定义标签开发 |
| 2006 | JSP 2.1 | 与 JSF 集成、统一表达式语言 |
| 2013 | JSP 2.3 | Java EE 7,小幅改进 |
技术演进趋势:
Servlet → JSP → JSP + EL + JSTL → 模板引擎(Thymeleaf) → 前后端分离(Vue/React)理解 Servlet 容器的工作原理
维护存量项目
面试准备
学习服务端渲染的原理
当浏览器请求一个 .jsp 页面时,Web 容器的处理流程如下:
1. 浏览器发送请求 → http://localhost:8080/hello.jsp
2. Web 容器检查 JSP 文件
├─ 是否存在?
├─ 是否被修改过?
└─ 是否已编译?
3. 如果未编译或已修改
├─ JSP 引擎解析 JSP 文件
├─ 将 JSP 转换为 Servlet 源码
└─ 编译 Servlet 为 .class 文件
4. Web 容器加载并实例化 Servlet
5. Servlet 调用 _jspService() 方法
├─ 设置响应内容类型
├─ 获取输出流(PrintWriter)
└─ 输出 HTML 内容
6. 容器将响应返回给浏览器详细流程图:
┌─────────┐
│ 浏览器 │
└────┬────┘
│ HTTP 请求: hello.jsp
↓
┌─────────────────┐
│ Web 容器 │
│ (Tomcat) │
└────────┬────────┘
│
↓
┌─────────────────────────┐
│ JSP 引擎检查 JSP 文件 │
│ - 是否存在? │
│ - 是否被修改? │
│ - 是否已编译? │
└────────┬────────────────┘
│
↓
┌────┴────┐
│需要编译?│
└────┬────┘
│
┌────┴────────────────┐
│ │
YES NO
│ │
↓ │
┌──────────────┐ │
│ JSP → Servlet│ │
│ 转换与编译 │ │
└──────┬───────┘ │
│ │
↓ │
┌──────────────┐ │
│ 加载 Servlet │◄─────────┘
└──────┬───────┘
│
↓
┌──────────────────┐
│ 执行 _jspService │
│ 生成 HTML 响应 │
└──────┬───────────┘
│
↓
┌─────────────┐
│ 返回给浏览器│
└─────────────┘JSP 的生命周期与 Servlet 类似,但有一些特殊之处:
JSP 文件 hello.jsp:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h1>Hello, <%= request.getParameter("name") %>!</h1>
</body>
</html>生成的 Servlet 类(简化):
public class hello_jsp extends HttpJspBase {
// 初始化方法
public void _jspInit() {
// 初始化资源
}
// 销毁方法
public void _jspDestroy() {
// 释放资源
}
// 服务方法
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// 设置响应类型
response.setContentType("text/html;charset=UTF-8");
// 获取输出流
PrintWriter out = response.getWriter();
// 输出 HTML
out.write("<html>\n<body>\n");
out.write(" <h1>Hello, ");
out.print(request.getParameter("name"));
out.write("!</h1>\n</body>\n</html>");
}
}// JSP 初始化方法
public void jspInit() {
// 初始化数据库连接
// 加载配置文件
// 初始化缓存
}// 每次请求都会调用 _jspService 方法
public void _jspService(HttpServletRequest request,
HttpServletResponse response) {
// 处理请求
// 生成响应
}// JSP 销毁方法
public void jspDestroy() {
// 关闭数据库连接
// 释放资源
// 清理缓存
}核心观点:JSP 本质上就是 Servlet,两者不是平行替代关系。
| 特性 | Servlet | JSP |
|---|---|---|
| 本质 | Java 类 | 文本文件(被编译为 Servlet) |
| 适用场景 | 业务逻辑处理、API 开发 | 视图展示、页面渲染 |
| 编写方式 | 纯 Java 代码 | HTML + Java 代码片段 |
| 部署方式 | 编译后部署 | 容器自动编译 |
| MVC 中的角色 | Controller | View |
最佳实践:
MVC 架构中的分工:
- Model: JavaBean、Entity
- View: JSP、Thymeleaf
- Controller: Servlet、Spring MVC ControllerJSP 指令用于向容器提供全局信息,不直接产生输出。
<%@ page
language="java" // 脚本语言,默认 java
contentType="text/html; charset=UTF-8" // 响应内容类型
pageEncoding="UTF-8" // JSP 文件编码
import="java.util.*,java.sql.*" // 导入 Java 类
session="true" // 是否启用 session,默认 true
buffer="8kb" // 缓冲区大小
autoFlush="true" // 自动刷新缓冲区
isThreadSafe="true" // 是否线程安全
isErrorPage="false" // 是否为错误处理页面
errorPage="error.jsp" // 错误处理页面
%>
<!-- 示例 -->
<%@ page contentType="text/html;charset=UTF-8" import="java.util.*" %>
<%@ page errorPage="error.jsp" %><!-- 静态包含:在编译时将目标文件内容插入到当前文件 -->
<%@ include file="header.jsp" %>
<!-- 示例:布局模板 -->
<html>
<head>
<title>我的网站</title>
</head>
<body>
<%@ include file="header.jsp" %>
<div class="content">
<!-- 页面内容 -->
</div>
<%@ include file="footer.jsp" %>
</body>
</html>静态包含 vs 动态包含:
| 特性 | 静态包含 <%@ include %> | 动态包含 <jsp:include> |
|---|---|---|
| 执行时机 | 编译时 | 运行时 |
| 原理 | 源码合并 | 请求转发 |
| 变量共享 | 共享同一个 request | 独立的 request |
| 性能 | 更高 | 较低 |
| 灵活性 | 较低 | 更高 |
<!-- 引入 JSTL 核心标签库 -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 引入 JSTL 格式化标签库 -->
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!-- 引入自定义标签库 -->
<%@ taglib prefix="my" uri="/WEB-INF/tags/my-tags.tld" %><%
// Java 代码片段
// 可以写任意 Java 代码
String name = request.getParameter("name");
if (name == null || name.isEmpty()) {
name = "Guest";
}
// 循环、条件、方法调用等
for (int i = 0; i < 10; i++) {
out.println("<p>Number: " + i + "</p>");
}
%>
<!-- 注意:不推荐在 JSP 中写大量业务逻辑! --><!-- 表达式会被转换为 out.print() -->
<h1>Welcome, <%= user.getName() %>!</h1>
<p>Current time: <%= new java.util.Date() %></p>
<p>Sum: <%= 10 + 20 %></p>
<!-- 等价于 -->
<h1>Welcome,
<% out.print(user.getName()); %>
!</h1><%!
// 声明成员变量和方法
private int counter = 0;
public int getCounter() {
return ++counter;
}
private String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
%>
<!-- 使用声明的方法 -->
<p>访问次数: <%= getCounter() %></p>
<p>当前时间: <%= formatDate(new Date()) %></p>脚本片段 vs 声明:
| 特性 | 脚本片段 <% %> | 声明 <%! %> |
|---|---|---|
| 位置 | _jspService() 方法内 | Servlet 类体中 |
| 作用域 | 局部变量 | 成员变量/方法 |
| 线程安全 | 线程安全(每次请求独立) | 非线程安全(共享) |
JSP 提供了 9 个隐式对象,无需声明即可直接使用:
<%
// 获取请求参数
String name = request.getParameter("name");
// 获取请求头
String userAgent = request.getHeader("User-Agent");
// 获取请求属性
Object user = request.getAttribute("user");
// 设置请求属性
request.setAttribute("message", "Hello");
%><%
// 设置响应类型
response.setContentType("application/json");
// 设置响应头
response.setHeader("Cache-Control", "no-cache");
// 重定向
// response.sendRedirect("login.jsp");
// 获取输出流
PrintWriter out = response.getWriter();
out.println("Hello");
%><%
// 输出内容到页面
out.println("<h1>Hello World</h1>");
out.print("当前时间: " + new Date());
// 清空缓冲区
// out.clear();
// 刷新缓冲区
out.flush();
%><%
// 获取 session
HttpSession session = request.getSession();
// 设置 session 属性
session.setAttribute("user", userObject);
// 获取 session 属性
User user = (User) session.getAttribute("user");
// 移除 session 属性
session.removeAttribute("user");
// 使 session 失效
// session.invalidate();
%><%
// 获取应用初始化参数
String dbUrl = application.getInitParameter("dbUrl");
// 设置应用范围属性
application.setAttribute("counter", 0);
// 获取应用范围属性
Integer counter = (Integer) application.getAttribute("counter");
// 获取真实路径
String realPath = application.getRealPath("/WEB-INF/config.properties");
%><%
// 获取 Servlet 初始化参数
String param = config.getInitParameter("configParam");
// 获取 Servlet 名称
String servletName = config.getServletName();
%><%
// 获取其他隐式对象
HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
HttpSession sess = pageContext.getSession();
// 设置页面范围属性
pageContext.setAttribute("message", "Hello", PageContext.PAGE_SCOPE);
// 查找属性(依次在 page、request、session、application 范围查找)
Object value = pageContext.findAttribute("user");
// 转发请求
// pageContext.forward("result.jsp");
// 包含其他页面
pageContext.include("header.jsp");
%><%
// page 等价于 this
// 当前 JSP 编译后的 Servlet 实例
String servletName = page.getClass().getName();
%><!-- 在错误处理页面中使用 -->
<%@ page isErrorPage="true" %>
<%
// 获取异常信息
String message = exception.getMessage();
String stackTrace = Arrays.toString(exception.getStackTrace());
%>
<h1>出错了!</h1>
<p>错误信息: <%= message %></p>
<pre><%= stackTrace %></pre>隐式对象作用域总结:
| 对象 | 作用域 | 生命周期 |
|---|---|---|
| pageContext | Page | 当前页面 |
| request | Request | 当前请求 |
| session | Session | 当前会话 |
| application | Application | 整个应用 |
EL (Expression Language) 是 JSP 2.0 引入的表达式语言,用于简化 JSP 页面中的数据访问。
优点:
基本语法:
${expression}<%
// 设置 request 属性
User user = new User("张三", 25);
request.setAttribute("user", user);
%>
<!-- 使用 EL 访问 -->
<p>姓名: ${user.name}</p>
<p>年龄: ${user.age}</p>
<!-- 等价于 -->
<p>姓名: <%= ((User) request.getAttribute("user")).getName() %></p><%
// List
List<String> names = Arrays.asList("张三", "李四", "王五");
request.setAttribute("names", names);
// Map
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 25);
request.setAttribute("userMap", map);
%>
<!-- 访问 List -->
<p>第一个: ${names[0]}</p>
<p>第二个: ${names[1]}</p>
<!-- 访问 Map -->
<p>姓名: ${userMap.name}</p>
<p>年龄: ${userMap['age']}</p><%
// 在不同作用域设置同名属性
pageContext.setAttribute("name", "Page Scope");
request.setAttribute("name", "Request Scope");
session.setAttribute("name", "Session Scope");
application.setAttribute("name", "Application Scope");
%>
<!-- 自动从最小范围开始查找 -->
<p>${name}</p> <!-- 输出: Page Scope -->
<!-- 显式指定作用域 -->
<p>Page: ${pageScope.name}</p>
<p>Request: ${requestScope.name}</p>
<p>Session: ${sessionScope.name}</p>
<p>Application: ${applicationScope.name}</p><p>加法: ${10 + 5}</p> <!-- 15 -->
<p>减法: ${10 - 5}</p> <!-- 5 -->
<p>乘法: ${10 * 5}</p> <!-- 50 -->
<p>除法: ${10 / 5}</p> <!-- 2.0 -->
<p>取模: ${10 % 3}</p> <!-- 1 --><p>相等: ${10 == 10}</p> <!-- true -->
<p>不等: ${10 != 5}</p> <!-- true -->
<p>大于: ${10 > 5}</p> <!-- true -->
<p>小于: ${10 < 15}</p> <!-- true -->
<p>大于等于: ${10 >= 10}</p> <!-- true -->
<p>小于等于: ${10 <= 15}</p> <!-- true --><p>与: ${true && false}</p> <!-- false -->
<p>或: ${true || false}</p> <!-- true -->
<p>非: ${!true}</p> <!-- false --><%
String nullStr = null;
String emptyStr = "";
List<String> emptyList = new ArrayList<>();
request.setAttribute("nullStr", nullStr);
request.setAttribute("emptyStr", emptyStr);
request.setAttribute("emptyList", emptyList);
%>
<p>判断 null: ${empty nullStr}</p> <!-- true -->
<p>判断空字符串: ${empty emptyStr}</p> <!-- true -->
<p>判断空集合: ${empty emptyList}</p> <!-- true -->
<p>判断非空: ${!empty names}</p> <!-- false --><!-- pageScope -->
${pageScope.user}
<!-- requestScope -->
${requestScope.user}
<!-- sessionScope -->
${sessionScope.user}
<!-- applicationScope -->
${applicationScope.user}
<!-- param - 请求参数 -->
${param.username}
${param.password}
<!-- paramValues - 多值请求参数 -->
${paramValues.hobbies[0]}
${paramValues.hobbies[1]}
<!-- header - 请求头 -->
${header["User-Agent"]}
${header.Host}
<!-- headerValues - 多值请求头 -->
${headerValues["Accept-Encoding"][0]}
<!-- cookie -->
${cookie.JSESSIONID.value}
<!-- initParam - 初始化参数 -->
${initParam.dbUrl}
<!-- pageContext -->
${pageContext.request.contextPath}
${pageContext.session.id}JSTL (JavaServer Pages Standard Tag Library) 是 JSP 标准标签库,提供了一组常用标签,用于替代 JSP 脚本片段。
优点:
引入 JSTL:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- if 标签 -->
<c:if test="${user.age >= 18}">
<p>您已成年</p>
</c:if>
<!-- if-else 标签 -->
<c:choose>
<c:when test="${score >= 90}">
<p>优秀</p>
</c:when>
<c:when test="${score >= 80}">
<p>良好</p>
</c:when>
<c:when test="${score >= 60}">
<p>及格</p>
</c:when>
<c:otherwise>
<p>不及格</p>
</c:otherwise>
</c:choose><!-- forEach 遍历集合 -->
<c:forEach items="${users}" var="user" varStatus="status">
<tr>
<td>${status.index + 1}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
<!-- forEach 循环指定次数 -->
<c:forEach begin="1" end="10" step="2" var="i">
<p>${i}</p>
</c:forEach>
<!-- forTokens 分隔字符串 -->
<c:forTokens items="张三,李四,王五" delims="," var="name">
<p>${name}</p>
</c:forTokens><!-- url 生成 URL -->
<a href="<c:url value='/user/profile'/>">用户资料</a>
<!-- url 带参数 -->
<c:url value="/user/search" var="searchUrl">
<c:param name="keyword" value="${keyword}"/>
<c:param name="page" value="1"/>
</c:url>
<a href="${searchUrl}">搜索结果</a><!-- redirect 重定向 -->
<c:redirect url="/login.jsp">
<c:param name="error" value="请先登录"/>
</c:redirect><!-- include 动态包含 -->
<c:import url="/header.jsp" charEncoding="UTF-8">
<c:param name="title" value="首页"/>
</c:import><!-- set 设置变量 -->
<c:set var="message" value="Hello World" scope="request"/>
<c:set var="user" value="${requestScope.user}" scope="session"/>
<!-- set 设置对象属性 -->
<c:set target="${user}" property="name" value="张三"/>
<!-- remove 移除变量 -->
<c:remove var="message" scope="request"/><!-- catch 捕获异常 -->
<c:catch var="exception">
<%
int result = 10 / 0;
%>
</c:catch>
<c:if test="${not empty exception}">
<p>发生错误: ${exception.message}</p>
</c:if><%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!-- 格式化日期 -->
<jsp:useBean id="now" class="java.util.Date"/>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss"/>
<!-- 格式化数字 -->
<fmt:formatNumber value="123456.789" pattern="#,##0.00"/>
<!-- 设置语言环境 -->
<fmt:setLocale value="zh_CN"/>
<!-- 设置时区 -->
<fmt:setTimeZone value="Asia/Shanghai"/>
<!-- 解析日期 -->
<fmt:parseDate value="2024-01-01" pattern="yyyy-MM-dd" var="date"/>
<!-- 解析数字 -->
<fmt:parseNumber value="1,234.56" var="number"/><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!-- 字符串长度 -->
<p>长度: ${fn:length(name)}</p>
<!-- 转换大小写 -->
<p>大写: ${fn:toUpperCase(name)}</p>
<p>小写: ${fn:toLowerCase(name)}</p>
<!-- 截取字符串 -->
<p>截取: ${fn:substring(name, 0, 5)}</p>
<!-- 替换字符串 -->
<p>替换: ${fn:replace(text, "old", "new")}</p>
<!-- 分割字符串 -->
<c:forEach items="${fn:split(names, ',')}" var="name">
<p>${name}</p>
</c:forEach>
<!-- 查找子串 -->
<p>包含: ${fn:contains(text, "keyword")}</p>
<p>开始于: ${fn:startsWith(text, "prefix")}</p>
<p>结束于: ${fn:endsWith(text, "suffix")}</p>
<!-- 去除空格 -->
<p>去除两端空格: ${fn:trim(text)}</p>
<!-- 转义 XML -->
<p>${fn:escapeXml(htmlContent)}</p><%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<!-- 使用 JSTL 标签,避免 Java 代码 -->
<table>
<c:forEach items="${users}" var="user" varStatus="status">
<tr>
<td>${status.index + 1}</td>
<td>${user.name}</td>
<td>${user.age}</td>
<td>
<c:choose>
<c:when test="${user.age >= 18}">
成年
</c:when>
<c:otherwise>
未成年
</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</table>
<!-- 使用 EL 表达式 -->
<p>总计: ${fn:length(users)} 个用户</p>
</body>
</html><%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<!-- × 在 JSP 中写大量 Java 代码 -->
<table>
<%
List<User> users = (List<User>) request.getAttribute("users");
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
%>
<tr>
<td><%= i + 1 %></td>
<td><%= user.getName() %></td>
<td><%= user.getAge() %></td>
<td>
<%
if (user.getAge() >= 18) {
%>
成年
<%
} else {
%>
未成年
<%
}
%>
</td>
</tr>
<%
}
%>
</table>
</body>
</html>// √ 正确:业务逻辑在 Servlet/Controller 中处理
@WebServlet("/users")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 业务逻辑处理
List<User> users = userService.getAllUsers();
// 设置数据到 request
request.setAttribute("users", users);
// 转发到 JSP
request.getRequestDispatcher("/WEB-INF/views/users.jsp").forward(request, response);
}
}<!-- √ 正确:JSP 只负责显示 -->
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<table>
<c:forEach items="${users}" var="user">
<tr>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
</table><!-- layout.jsp - 布局模板 -->
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${pageTitle}</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<!-- 页头 -->
<%@ include file="header.jsp" %>
<!-- 主内容区 -->
<div class="main-content">
<jsp:include page="${contentPage}"/>
</div>
<!-- 页脚 -->
<%@ include file="footer.jsp" %>
<script src="${pageContext.request.contextPath}/js/main.js"></script>
</body>
</html><!-- error.jsp - 错误处理页面 -->
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head>
<title>出错了</title>
</head>
<body>
<h1>抱歉,系统出错了</h1>
<!-- 生产环境隐藏详细错误信息 -->
<c:if test="${not empty exception}">
<p>错误信息: ${exception.message}</p>
<!-- 开发环境显示堆栈跟踪 -->
<c:if test="${applicationScope.env == 'dev'}">
<pre><% exception.printStackTrace(new java.io.PrintWriter(out)); %></pre>
</c:if>
</c:if>
<a href="javascript:history.back()">返回</a>
</body>
</html><%@ page contentType="text/html;charset=UTF-8" %>
<!-- 防止 XSS 攻击 -->
<!-- × 错误:直接输出用户输入 -->
<p>${param.comment}</p>
<!-- √ 正确:使用 fn:escapeXml 转义 -->
<p>${fn:escapeXml(param.comment)}</p>
<!-- √ 正确:使用 <c:out> 标签(默认转义) -->
<c:out value="${param.comment}"/>
<!-- 防止 SQL 注入 -->
<!-- × 错误:在 JSP 中拼接 SQL -->
<%
String sql = "SELECT * FROM users WHERE name = '" + request.getParameter("name") + "'";
%>
<!-- √ 正确:在 Servlet/Service 层使用 PreparedStatement --><!-- × 错误:大量脚本片段影响性能 -->
<%
for (int i = 0; i < 1000; i++) {
out.println("<p>" + i + "</p>");
}
%>
<!-- √ 正确:使用 JSTL 标签 -->
<c:forEach begin="1" end="1000" var="i">
<p>${i}</p>
</c:forEach><!-- 静态包含:适合不变的页面片段 -->
<%@ include file="header.jsp" %>
<!-- 动态包含:适合动态内容 -->
<jsp:include page="advertisement.jsp"/><!-- 不需要 session 的页面 -->
<%@ page session="false" %>
<!-- 避免不必要的 session 创建 -->
<%
// × 错误:无条件自动创建 session
HttpSession session = request.getSession();
%>
<%
// √ 正确:只在需要时创建
HttpSession session = request.getSession(false);
if (session == null) {
session = request.getSession();
}
%>错误认识: JSP 已经过时,完全没有学习的必要。
实际情况:
建议:
错误认识: JSP 和 Servlet 是两种完全独立的技术,可以互相替代。
实际情况:
MVC 架构中的分工:
┌─────────────────────────────────────┐
│ MVC 架构 │
├─────────────────────────────────────┤
│ Model │ JavaBean、Entity │
│ View │ JSP、Thymeleaf │
│ Controller │ Servlet、Spring MVC │
└─────────────────────────────────────┘错误认识: 在 JSP 中可以直接写业务逻辑,方便快捷。
实际情况:
正确做法:
// Servlet/Controller 处理业务逻辑
@WebServlet("/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 业务逻辑
List<User> users = userService.getAllUsers();
request.setAttribute("users", users);
request.getRequestDispatcher("/WEB-INF/views/users.jsp").forward(request, response);
}
}<!-- JSP 只负责显示 -->
<c:forEach items="${users}" var="user">
<tr>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>错误认识: 使用 JSP 就无法实现前后端分离。
实际情况:
示例:JSP 返回 JSON:
<%@ page contentType="application/json;charset=UTF-8" %>
<%@ page import="com.google.gson.Gson" %>
<%
List<User> users = userService.getAllUsers();
Gson gson = new Gson();
out.print(gson.toJson(users));
%>错误认识: EL 表达式 ${user.name} 和脚本片段 <%= user.getName() %> 完全等价。
实际情况:
对比:
<!-- 脚本片段:可能抛出 NullPointerException -->
<%= user.getName() %>
<!-- EL 表达式:null 值安全 -->
${user.name}
<!-- 等价于 -->
<%
if (user != null) {
out.print(user.getName());
} else {
out.print("");
}
%>Thymeleaf 是现代 Java 模板引擎,与 Spring Boot 集成良好。
优点:
示例:
<!-- Thymeleaf 模板 -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<table>
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
</tr>
</table>
</body>
</html>前后端分离 是当前主流架构,前端使用 Vue/React,后端提供 REST API。
优点:
示例:
// Spring Boot Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
}// Vue.js 前端
<template>
<div>
<h1>用户列表</h1>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.age }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
users: []
}
},
mounted() {
fetch('/api/users')
.then(response => response.json())
.then(data => {
this.users = data;
});
}
}
</script>| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 新项目 | 前后端分离 | 用户体验好,易于维护和扩展 |
| 简单管理系统 | Spring Boot + Thymeleaf | 开发效率高,学习成本低 |
| 老项目维护 | JSP + JSTL | 保持技术栈一致,降低风险 |
| 内容型网站 | Spring Boot + Thymeleaf | SEO 友好,服务端渲染 |
答案:
_jspService() 方法答案:
| 隐式对象 | 类型 | 作用 |
|---|---|---|
| request | HttpServletRequest | 获取请求参数和属性 |
| response | HttpServletResponse | 设置响应内容 |
| out | JspWriter | 输出内容到页面 |
| session | HttpSession | 管理会话数据 |
| application | ServletContext | 管理应用范围数据 |
| config | ServletConfig | 获取 Servlet 配置 |
| pageContext | PageContext | 提供页面范围的上下文 |
| page | Object | 当前 Servlet 实例 |
| exception | Throwable | 异常信息(错误页面) |
答案:
| 特性 | 静态包含 <%@ include %> | 动态包含 <jsp:include> |
|---|---|---|
| 执行时机 | 编译时 | 运行时 |
| 原理 | 源码合并 | 请求转发 |
| 变量共享 | 共享同一个 request | 独立的 request |
| 性能 | 更高 | 较低 |
| 灵活性 | 较低 | 更高 |
答案:
答案:
要求:使用 JSP + Servlet 实现用户登录,包括登录页面、登录处理、主页展示。
提示:
要求:使用 JSP + JSTL 实现数据分页展示。
提示:
<c:forEach> 遍历数据要求:分析一个包含大量脚本片段的 JSP 页面,进行优化。
提示:
官方文档
经典书籍
开源项目
最后更新:2026-03-29
维护人:AI 助手
| 特性 | 旧版(JSP 时代) | 当前 |
|---|---|---|
| 视图技术 | JSP(javax.servlet.jsp) | 新项目推荐 Thymeleaf/前后端分离(Vue/React) |
| 包名 | javax.servlet.jsp.* | jakarta.servlet.jsp.*(Tomcat 10+) |
| 弃用状态 | JSP 仍可用 | Oracle 已将其列入可移除技术(JEP 尚未执行),不推荐新项目使用 |
| 维护场景 | 老系统 JSP 页面 | Spring Boot 3 已不再默认支持 JSP,需额外配置 |
JSP 作为历史技术,理解其原理有助于维护遗留系统;新项目不建议使用 JSP,Spring Boot 3.5.x 推荐 Thymeleaf 或前后端分离架构。