JSP 结构
JSP 在 MVC 中的位置、请求流转、转发与包含。
Key PointsServlet 收请求、调业务、把结果放进 request 域,再 forward 给 JSP 渲染——这是 JSP 时代的标准结构。
MVC 里的位置
Text
Browser
↓ GET /user?id=42
Controller (Servlet)
├─ 取参数、校验
├─ 调 Service / DAO
├─ request.setAttribute("user", user)
└─ forward → /WEB-INF/views/user.jsp
↓
View (JSP) ← 只读数据,不查库、不算业务
↓ HTML
BrowserController 的典型写法
Java
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private final UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
int id;
try {
id = Integer.parseInt(req.getParameter("id"));
} catch (NumberFormatException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "id 不合法");
return;
}
User user = service.find(id);
if (user == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
req.setAttribute("user", user);
req.getRequestDispatcher("/WEB-INF/views/user.jsp").forward(req, resp);
}
}View 只做呈现
JSP
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html><body>
<h1>${user.name}</h1>
<p>邮箱:${user.email}</p>
<c:if test="${empty user.email}">
<p>未填写邮箱</p>
</c:if>
</body></html>Seeing Connection, SELECT or new SimpleDateFormat() in a view means the boundary is broken. Those belong to the controller and service; the view only places data that is already computed.
转发与包含
| Mechanism | Syntax | Behaviour |
|---|---|---|
| forward | <jsp:forward page="a.jsp"/> | 交出控制权,当前页后续内容不执行 |
| include (static) | <%@ include file="h.jsp" %> | 编译期合并成一个文件 |
| include (dynamic) | <jsp:include page="h.jsp"/> | 运行期分别执行再拼输出 |
JSP
<%-- 静态包含:适合页头页脚这种不变的片段,只翻译一次 --%>
<%@ include file="/WEB-INF/parts/header.jsp" %>
<main>正文内容</main>
<%-- 动态包含:适合内容会变的片段,可以传参数 --%>
<jsp:include page="/WEB-INF/parts/sidebar.jsp">
<jsp:param name="section" value="news"/>
</jsp:include>Static include merges at compile time, so duplicate variable names or conflicting
page attributes break it; dynamic include runs at runtime and stays isolated but is slightly slower. Prefer dynamic when the fragment needs variables.为什么视图放 WEB-INF
/WEB-INF/views/user.jsp is reachable only by forward; typing the URL gives 404. That guarantees the controller always runs first — otherwise a user could bypass the permission check and hit a page whose data was never prepared.
MVC 里的位置
Text
Browser
↓ GET /user?id=42
Controller (Servlet)
├─ 取参数、校验
├─ 调 Service / DAO
├─ request.setAttribute("user", user)
└─ forward → /WEB-INF/views/user.jsp
↓
View (JSP) ← 只读数据,不查库、不算业务
↓ HTML
BrowserController 的典型写法
Java
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private final UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
int id;
try {
id = Integer.parseInt(req.getParameter("id"));
} catch (NumberFormatException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "id 不合法");
return;
}
User user = service.find(id);
if (user == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
req.setAttribute("user", user);
req.getRequestDispatcher("/WEB-INF/views/user.jsp").forward(req, resp);
}
}View 只做呈现
JSP
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html><body>
<h1>${user.name}</h1>
<p>邮箱:${user.email}</p>
<c:if test="${empty user.email}">
<p>未填写邮箱</p>
</c:if>
</body></html>视图里出现 Connection、SELECT、new SimpleDateFormat() 就是越界了。这些属于 Controller 与 Service,视图只负责把已经算好的数据摆到该在的位置。
转发与包含
| 机制 | 写法 | 行为 |
|---|---|---|
| 转发 | <jsp:forward page="a.jsp"/> | 交出控制权,当前页后续内容不执行 |
| 静态包含 | <%@ include file="h.jsp" %> | 编译期合并成一个文件 |
| 动态包含 | <jsp:include page="h.jsp"/> | 运行期分别执行再拼输出 |
JSP
<%-- 静态包含:适合页头页脚这种不变的片段,只翻译一次 --%>
<%@ include file="/WEB-INF/parts/header.jsp" %>
<main>正文内容</main>
<%-- 动态包含:适合内容会变的片段,可以传参数 --%>
<jsp:include page="/WEB-INF/parts/sidebar.jsp">
<jsp:param name="section" value="news"/>
</jsp:include>静态包含在编译期合并,两个文件不能有重复的变量名,也不能各自声明同名的
page 指令属性;动态包含在运行期执行,互不干扰但慢一点。片段里如果用到变量,优先用动态包含。为什么视图放 WEB-INF
/WEB-INF/views/user.jsp 只能被 forward 访问,用户直接敲 URL 会 404。这保证了「必须先经过 Controller」——否则用户可以绕过权限检查直接看页面,而页面上的数据根本没准备好。