背景

本文是《JavaEE 后端从小白到大神》修仙系列第三篇,正式进入JavaEE后端世界。若想详细学习请点击首篇博文,我们开始把。

第三篇:JSP 与 JSTL 表示层开发

  • JSP 原理(编译为 Servlet);
  • EL 表达式;
  • JSTL 标签库;
  • MVC 模式与 JSP 的角色;
  • JSP + Servlet 结合构建 Web 应用;
  • JSP 在现代架构中的替代方案(Thymeleaf、Vue、React 等);

一、 JSP 原理(编译为 Servlet)

JSP 本身不是可执行文件,必须经过 “翻译→编译→运行” 三个阶段,最终以 Servlet 形式处理请求。

1. 核心执行流程

1
客户端请求 JSP → 服务器检查 JSP 是否已编译 → 未编译则执行翻译+编译 → 生成 Servlet 字节码 → 执行 Servlet 生成 HTML → 响应客户端
  • 翻译阶段:Tomcat 的 JspServlet 将 JSP 页面翻译为 Java 源文件(Servlet),翻译规则如下:

    • HTML 静态内容 → 用 out.write() 输出到响应流;
    • <% Java 代码 %> → 直接嵌入 Servlet 的 _jspService() 方法;
    • <%= 表达式 %> → 翻译为 out.print(表达式)
    • <%@ 指令 %> → 转换为 Servlet 类的注解/配置(如 pageEncoding 对应 response.setCharacterEncoding())。
  • 编译阶段:Java 源文件被编译为 .class 字节码(存储在 Tomcat 的 work/Catalina/ 目录)。

  • 运行阶段:容器加载 Servlet 实例,调用 _jspService(HttpServletRequest request, HttpServletResponse response) 方法处理请求,动态生成 HTML 响应。

2. 翻译示例(JSP → Servlet)

JSP 页面(index.jsp)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>JSP 编译示例</title>
</head>
<body>
    <h1>当前时间:<%= new Date() %></h1>
    <% for (int i = 0; i < 3; i++) { %>
        <p>循环输出:<%= i %></p>
    <% } %>
</body>
</html>

翻译后的 Servlet 核心代码(简化版)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class index_jsp extends HttpServlet {
    protected void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html;charset=UTF-8");
        JspWriter out = response.getWriter();
        out.write("<html>\n<head>\n    <title>JSP 编译示例</title>\n</head>\n<body>\n");
        out.write("    <h1>当前时间:");
        out.print(new Date()); // 对应 <%= new Date() %>
        out.write("</h1>\n");
        for (int i = 0; i < 3; i++) { // 对应 <% 循环代码 %>
            out.write("        <p>循环输出:");
            out.print(i);
            out.write("</p>\n");
        }
        out.write("</body>\n</html>");
    }
}

3. 关键特性

  • 预编译:首次请求 JSP 时触发翻译+编译(有延迟),后续请求直接执行已编译的 Servlet;

  • 自动重新编译:JSP 页面修改后,Tomcat 会检测到变化并重新翻译编译(开发环境友好);

  • 内置对象:JSP 提供 9 个内置对象(如 requestresponsesessionout),无需手动创建,直接在页面使用。

内置对象 类型(类名) 作用说明
request javax.servlet.http.HttpServletRequest 封装客户端请求信息(如参数、头、路径等)
response javax.servlet.http.HttpServletResponse 响应客户端请求,控制输出、重定向等
session javax.servlet.http.HttpSession 代表一次会话(浏览器与服务器之间的会话状态)
application javax.servlet.ServletContext 表示整个 Web 应用的上下文(所有用户共享)
out javax.servlet.jsp.JspWriter 向客户端输出内容(类似于 PrintWriter
config javax.servlet.ServletConfig 表示当前 JSP 对应的 Servlet 配置信息
pageContext javax.servlet.jsp.PageContext JSP 页面级对象,封装其他 8 个对象的访问入口
page java.lang.Object 代表当前 JSP 页面(Servlet 实例本身),等价于 this
exception java.lang.Throwable 表示当前页面中发生的异常,仅在错误页面(isErrorPage="true")中有效

(1)request

类型: HttpServletRequest

作用: 用于获取客户端发送的请求信息,包括参数、头信息、IP、请求方式等。

常用方法:

1
2
3
4
request.getParameter("username");     // 获取表单参数
request.getHeader("User-Agent");      // 获取请求头
request.getMethod();                  // 获取请求方式(GET/POST)
request.getRequestURI();              // 获取请求路径

示例 JSP:

用户名:<%= request.getParameter("username") %>

(2)response

类型: HttpServletResponse

作用: 用于生成响应,比如重定向、设置响应头、输出数据类型等。

常用方法:

1
2
3
response.sendRedirect("login.jsp");    // 重定向
response.setContentType("text/html");  // 设置响应类型
response.addCookie(cookie);            // 添加 Cookie

示例 JSP:

<%
    response.setHeader("Refresh", "3;URL=home.jsp");
%>
页面3秒后自动跳转...

(3)session

类型: HttpSession

作用: 保存每个用户的会话数据(跨多个请求保持状态)。

常用方法:

1
2
3
session.setAttribute("username", "Tom");
String name = (String) session.getAttribute("username");
session.invalidate(); // 销毁会话

示例 JSP:

欢迎你:<%= session.getAttribute("username") %>

(4)application

类型: ServletContext

作用: 整个 Web 应用的全局对象,所有用户共享。

常用方法:

1
2
application.setAttribute("count", 100);
int count = (int) application.getAttribute("count");

示例 JSP:

访问总人数:<%= application.getAttribute("count") %>

(5)out

类型: JspWriter

作用: 输出内容到浏览器,是 JSP 的主要输出流。

常用方法:

1
2
out.print("Hello JSP");
out.flush();

示例 JSP:

<%
    out.println("当前时间:" + new java.util.Date());
%>

注意:out<%= ... %> 最终都会写入同一个缓冲区。

(6)config

类型: ServletConfig

作用: 代表 JSP 对应的 Servlet 配置信息(从 web.xml 传入的初始化参数)。

常用方法:

1
config.getInitParameter("charset");

示例 JSP:

初始化编码:<%= config.getInitParameter("charset") %>

(7)pageContext

类型: PageContext

作用: JSP 页面范围对象,能访问所有其他内置对象,还能设置作用域变量。

常用方法:

1
2
3
pageContext.setAttribute("msg", "Hello", PageContext.SESSION_SCOPE);
String msg = (String) pageContext.getAttribute("msg", PageContext.SESSION_SCOPE);
pageContext.forward("next.jsp");  // 页面跳转

示例 JSP:

<%
    pageContext.setAttribute("greeting", "你好", PageContext.REQUEST_SCOPE);
%>
<jsp:forward page="next.jsp"/>

(8)page

类型: java.lang.Object(实际上是当前 Servlet 实例)

作用: 当前 JSP 页面自身,等价于 Java 中的 this

示例 JSP:

<%= page.getClass().getName() %>

输出类似:

1
org.apache.jsp.index_jsp

(9)exception

类型: java.lang.Throwable

作用: 保存 JSP 页面中发生的异常,仅在错误页面(isErrorPage="true")中可用。

示例 JSP(error.jsp):

<%@ page isErrorPage="true" %>
错误信息:<%= exception.getMessage() %>

二、 EL 表达式

EL(Expression Language)表达式用于 在 JSP 中简化 Java 代码访问数据,避免直接嵌入 <%= %> 代码,使页面更简洁。

1. 核心语法与作用

  • 语法${ 表达式 }(如 ${user.name}${sessionScope.user});

  • 核心作用

    1. 访问四大域对象的属性(pageScoperequestScopesessionScopeapplicationScope);
    2. 访问请求参数(${param.username} 等价于 request.getParameter("username"));
    3. 执行算术/逻辑运算(${1+1}${user.age > 18});
    4. 访问集合/数组(${list[0]}${map.key})。

2. 域对象访问规则

EL 表达式访问属性时,默认按域范围从小到大查找pageScope → requestScope → sessionScope → applicationScope),找到即返回。

示例 等价 Java 代码
${username} pageContext.findAttribute("username")
${requestScope.user} request.getAttribute("user")
${sessionScope.cart} session.getAttribute("cart")
${applicationScope.appName} application.getAttribute("appName")

3. 常用内置对象

EL 提供 11 个内置对象,无需定义直接使用:

内置对象 作用 示例
param 单个请求参数(String ${param.username}
paramValues 多值请求参数(String[] ${paramValues.hobby[0]}
header 单个请求头 ${header.User-Agent}
cookie 访问 Cookie(通过 name.value 取值) ${cookie.JSESSIONID.value}
initParam 全局初始化参数(web.xml ${initParam.db.url}

4. 空值处理

EL 表达式对 null 友好,访问不存在的属性或 null 时返回空字符串(""),不会抛出空指针异常:

<!-- 若 user 为 null,输出空字符串,而非报错 -->
<div>${user?.name}</div> <!-- 可选链语法(EL 3.0+),避免空指针 -->

三、 JSTL 标签库

JSTL(JSP Standard Tag Library)是 JSP 标准标签库,提供一系列标签替代页面中的 Java 代码块(如循环、判断),使 JSP 页面更易维护。

1. 核心概念

  • 依赖:需引入 JSTL 依赖(Maven):

    1
    2
    3
    4
    5
    
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    
  • 标签库声明:使用前需在 JSP 页面声明标签库:

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!-- 核心标签库 -->
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!-- 格式化标签库 -->
    

2. 核心标签库(<c: 前缀)

最常用的标签库,覆盖循环、判断、跳转等核心场景:

标签 作用 示例代码
<c:out> 输出数据(防 XSS 攻击) <c:out value="${user.name}" default="匿名用户"/>
<c:if> 条件判断 <c:if test="${user.age > 18}">成年人</c:if>
<c:choose>/<c:when> 多条件判断 <c:choose><c:when test="${score>=90}">优秀</c:when><c:otherwise>及格</c:otherwise></c:choose>
<c:forEach> 循环遍历(集合/数组/整数范围) <c:forEach items="${list}" var="item" varStatus="status">${status.index}: ${item}</c:forEach>
<c:set> 定义变量/设置域属性 <c:set var="total" value="${100+200}" scope="session"/>
<c:redirect> 重定向请求 <c:redirect url="/login" />
<c:url> 构建 URL(自动拼接 JSESSIONID) <a href="<c:url value="/user?id=1"/>">用户详情</a>

3. 格式化标签库(<fmt: 前缀)

用于日期、数字格式化,避免手动编写格式化代码:

<!-- 格式化日期(默认格式:yyyy-MM-dd) -->
<fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd HH:mm:ss"/>

<!-- 格式化数字(保留 2 位小数) -->
<fmt:formatNumber value="${order.amount}" pattern="0.00"/>

四、 MVC 模式与 JSP 的角色

MVC(Model-View-Controller)是分层架构模式,JSP+Servlet 组合天然契合 MVC 思想,各组件分工明确:

1. MVC 三层分工

层级 职责 技术实现
Model(模型) 封装业务数据和逻辑(如用户信息、数据库操作) JavaBean 实体类、Service 层、DAO 层
View(视图) 展示数据,与用户交互 JSP(结合 EL+JSTL)、HTML、CSS
Controller(控制器) 接收请求、分发处理、返回视图/数据 Servlet(或 Struts2、SpringMVC 框架)

2. JSP 在 MVC 中的定位:纯 View 层

  • 核心职责:仅负责数据展示,不包含复杂业务逻辑(如数据库查询、条件判断应放在 Servlet/Service 层);

  • 最佳实践:JSP 中仅使用 EL 表达式获取数据、JSTL 标签控制页面结构,避免嵌入 <% Java 代码 %>(称为“无脚本 JSP”)。

3. 典型 MVC 流程(JSP+Servlet)

1
2
3
4
5
1. 客户端请求 → Servlet(Controller)接收请求;
2. Servlet 调用 Service/DAO(Model)处理业务逻辑,获取数据;
3. Servlet 将数据存入域对象(request/session);
4. Servlet 转发到 JSP(View);
5. JSP 用 EL+JSTL 渲染数据,生成 HTML 响应客户端。

五、 JSP + Servlet 结合构建 Web 应用

以“用户列表查询”为例,完整实现 JSP+Servlet+EL+JSTL 的 MVC 应用:

1. 步骤 1:定义 Model(实体类+DAO 层)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 实体类(Model)
public class User {
    private Integer id;
    private String username;
    private Integer age;
    // 构造器、getter、setter
}

// DAO 层(数据访问)
public class UserDAO {
    // 模拟查询所有用户
    public List<User> findAll() {
        List<User> list = new ArrayList<>();
        list.add(new User(1, "张三", 20));
        list.add(new User(2, "李四", 22));
        return list;
    }
}

2. 步骤 2:实现 Controller(Servlet)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@WebServlet("/user/list")
public class UserListServlet extends HttpServlet {
    private UserDAO userDAO = new UserDAO();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 调用 Model 层获取数据
        List<User> userList = userDAO.findAll();
        
        // 2. 将数据存入 request 域(供 JSP 访问)
        req.setAttribute("userList", userList);
        
        // 3. 转发到 JSP 视图
        req.getRequestDispatcher("/WEB-INF/views/user/list.jsp").forward(req, resp);
    }
}

3. 步骤 3:实现 View(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>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>用户名</th>
            <th>年龄</th>
        </tr>
        <!-- 用 JSTL 循环遍历用户列表 -->
        <c:forEach items="${userList}" var="user">
            <tr>
                <td>${user.id}</td>
                <td>${user.username}</td>
                <td>${user.age}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

4. 步骤 4:配置与访问

  • 无需额外配置(依赖 @WebServlet 注解);

  • 访问 URL:http://localhost:8080/项目名/user/list,即可看到用户列表页面。

六、 JSP 在现代架构中的替代方案(Thymeleaf、Vue、React 等)

随着前后端分离架构普及,JSP 因“前后端耦合、维护困难”逐渐被替代,主流方案分为两类:

1. 服务端模板引擎(替代传统 JSP+Servlet)

适用于“服务端渲染(SSR)”场景,保留服务端生成 HTML 的优势,但语法更简洁、功能更强:

技术 核心特点 适用场景
Thymeleaf 天然支持 HTML 静态原型(无标签时可正常显示)、语法优雅、Spring 官方推荐 Spring Boot 项目、服务端渲染场景
FreeMarker 模板与数据分离彻底、性能高、支持复杂逻辑 电商网站、静态页面生成(如新闻详情)
Velocity 语法简单、速度快(已停止维护) legacy 项目

Thymeleaf 示例(替代上述 JSP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>用户列表</title>
</head>
<body>
    <h1>用户列表</h1>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>用户名</th>
            <th>年龄</th>
        </tr>
        <tr th:each="user : ${userList}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.username}"></td>
            <td th:text="${user.age}"></td>
        </tr>
    </table>
</body>
</html>

2. 前后端分离架构(主流方案)

彻底分离前后端,后端提供 API 接口(JSON 数据),前端独立开发并调用接口,完全替代 JSP 的视图功能:

前端技术 核心特点 技术栈组合
Vue.js 轻量、易用、生态完善(Vue 3 + Vite) 后端:Spring Boot(API)+ 前端:Vue 3 + Axios
React 组件化强、适合复杂应用(如中台系统) 后端:Spring Cloud + 前端:React + Ant Design
Angular 全功能框架、适合企业级应用 后端:Node.js/Java + 前端:Angular

前后端分离流程

1
客户端 → 前端框架(Vue/React)→ 调用后端 API(JSON)→ 前端渲染页面 → 响应客户端

3. JSP 与现代方案的对比

特性 JSP 服务端模板引擎(Thymeleaf) 前后端分离(Vue/React)
前后端耦合度 高(Java+HTML混合) 中(模板与逻辑分离) 低(完全独立)
开发效率 低(调试复杂) 中(支持热部署) 高(前后端并行开发)
维护成本 高(代码混乱) 低(职责清晰)
适用场景 小型项目、快速原型 服务端渲染、SEO 需求高的项目 中大型项目、交互复杂的应用

七、总结

1. JSP 技术核心

  • 本质:JSP 是 Servlet 的“模板化封装”,最终编译为 Servlet 执行;

  • 生态:EL 表达式简化数据访问,JSTL 标签库替代 Java 代码块,三者结合构成 JSP 核心技术体系;

  • 架构定位:在 MVC 模式中作为 View 层,负责数据展示,避免包含业务逻辑。

2. 技术演进结论

  • JSP 已逐步退出主流架构,仅适用于小型项目、legacy 系统维护;

  • 现代项目优先选择:

    1. 服务端渲染需求 → Thymeleaf/FreeMarker;
    2. 交互复杂、中大型项目 → 前后端分离(Vue/React + 后端 API);
  • 核心趋势:前后端解耦、开发效率提升、维护成本降低

3. 关键启示

  • 学习 JSP 核心价值:理解“服务端动态页面生成”的原理,为学习现代模板引擎和前后端分离架构奠定基础;

  • 技术选型原则:根据项目规模、团队技术栈、SEO 需求选择合适的视图方案,而非固守传统技术。