1. JSP技术概述
JSP(JavaServer Pages)是Java EE规范中用于动态生成Web内容的核心技术之一。它允许开发者在HTML页面中嵌入Java代码片段,通过JSP容器(如Tomcat)在服务器端执行后生成纯HTML响应给客户端浏览器。与Servlet相比,JSP更侧重于视图层的呈现,实现了业务逻辑与展示逻辑的分离。
注意:JSP本质上会被容器编译为Servlet执行,这种设计既保留了Servlet的强大功能,又提供了更便捷的页面开发方式。
1.1 JSP运行原理
当客户端首次请求一个JSP页面时,容器会经历以下处理流程:
- 翻译阶段:JSP文件被转换为Java Servlet源文件(.java)
- 编译阶段:生成的Java文件被编译成字节码(.class)
- 加载执行:编译后的Servlet类被加载到容器内存
- 请求处理:实例化的Servlet处理HTTP请求并生成响应
java复制// 典型JSP转换后的Servlet代码结构
public class Hello_jsp extends HttpJspBase {
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// 自动生成的页面上下文对象
PageContext pageContext = ...;
// 输出HTML内容
response.setContentType("text/html");
JspWriter out = pageContext.getOut();
out.write("<html>");
out.write("<body>");
// JSP中的Java代码片段会插入到这里
out.write("</body>");
out.write("</html>");
}
}
1.2 JSP与Servlet的对比
| 特性 | JSP | Servlet |
|---|---|---|
| 开发效率 | 快速(适合页面开发) | 较低(需要手动输出HTML) |
| 维护性 | 视图修改方便 | 业务逻辑集中 |
| 学习曲线 | 前端友好 | 需要Java基础 |
| 性能 | 首次访问需编译 | 直接加载执行 |
| 适用场景 | 展示层开发 | 控制器/业务逻辑处理 |
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JSP基础语法详解
2.1 JSP页面结构
标准JSP页面包含以下组成部分:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP示例</title>
</head>
<body>
<%-- JSP注释(不会发送到客户端) --%>
<!-- HTML注释(会发送到客户端) -->
<%! // 声明:定义成员变量和方法 %>
<% // 脚本片段:可执行Java代码 %>
<%= // 表达式:输出变量或方法返回值 %>
<jsp:include page="header.jsp"/>
</body>
</html>
2.2 脚本元素
2.2.1 声明(Declaration)
用于定义类的成员变量和方法,语法为<%! %>:
jsp复制<%!
// 实例变量
private int counter = 0;
// 方法定义
public String getServerInfo() {
return "Apache Tomcat 9.0";
}
%>
注意事项:声明的变量是线程共享的,需要考虑线程安全问题
2.2.2 脚本片段(Scriptlet)
包含有效的Java代码片段,语法为<% %>:
jsp复制<%
// 获取请求参数
String username = request.getParameter("user");
// 业务逻辑处理
if("admin".equals(username)) {
session.setAttribute("role", "administrator");
}
// 数据库操作示例
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
%>
2.2.3 表达式(Expression)
用于输出变量或表达式结果,语法为<%= %>:
jsp复制<p>当前用户: <%= session.getAttribute("currentUser") %></p>
<p>服务器时间: <%= new java.util.Date() %></p>
<p>计算结果: <%= 2 * Math.PI * 10 %></p>
2.3 指令元素
2.3.1 page指令
控制页面全局属性:
jsp复制<%@ page
language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="java.util.*, com.example.*"
session="true"
buffer="8kb"
autoFlush="true"
isThreadSafe="true"
errorPage="/error.jsp"
isErrorPage="false"
info="用户管理页面"
isELIgnored="false"
%>
2.3.2 include指令
静态包含其他文件内容(编译时合并):
jsp复制<%@ include file="copyright.jsp" %>
2.3.3 taglib指令
引入标签库:
jsp复制<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
2.4 动作元素
2.4.1 include动作
动态包含(运行时包含):
jsp复制<jsp:include page="navigation.jsp" flush="true">
<jsp:param name="currentTab" value="home"/>
</jsp:include>
2.4.2 forward动作
请求转发:
jsp复制<jsp:forward page="/processForm.jsp">
<jsp:param name="validation" value="strict"/>
</jsp:forward>
2.4.3 useBean动作
JavaBean操作:
jsp复制<jsp:useBean id="user" class="com.example.User" scope="session">
<jsp:setProperty name="user" property="*"/>
</jsp:useBean>
<p>用户名: <jsp:getProperty name="user" property="username"/></p>
3. JSP隐含对象
JSP提供了9个无需声明即可直接使用的对象:
| 对象名称 | 类型 | 作用域 | 说明 |
|---|---|---|---|
| request | HttpServletRequest | 请求 | 获取客户端请求信息 |
| response | HttpServletResponse | 响应 | 控制发送给客户端的响应 |
| session | HttpSession | 会话 | 维护用户会话状态 |
| application | ServletContext | 应用 | 共享整个应用的全局信息 |
| out | JspWriter | 页面 | 输出内容到客户端 |
| config | ServletConfig | 页面 | 获取Servlet配置信息 |
| pageContext | PageContext | 页面 | 提供对所有其他对象的访问 |
| page | Object (this) | 页面 | 当前Servlet实例 |
| exception | Throwable | 页面 | 仅在错误页面可用 |
3.1 典型使用示例
jsp复制<%
// 获取请求参数
String searchTerm = request.getParameter("q");
// 设置响应头
response.setHeader("Cache-Control", "no-cache");
// 会话管理
Integer visitCount = (Integer)session.getAttribute("visitCount");
if(visitCount == null) {
visitCount = 1;
} else {
visitCount++;
}
session.setAttribute("visitCount", visitCount);
// 应用级数据共享
Integer totalVisits = (Integer)application.getAttribute("totalVisits");
application.setAttribute("totalVisits",
totalVisits == null ? 1 : totalVisits + 1);
// 输出内容
out.println("<p>您搜索的关键词: " + searchTerm + "</p>");
%>
4. JSP开发实践技巧
4.1 避免Java代码过度嵌入
不良实践:
jsp复制<%
List<Product> products = productService.getAllProducts();
for(Product p : products) {
if(p.getStock() > 0) {
%>
<div class="product">
<%= p.getName() %> - $<%= p.getPrice() %>
</div>
<%
}
}
%>
推荐方案:
jsp复制<%-- 使用JSTL和EL表达式 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach items="${productService.allProducts}" var="p">
<c:if test="${p.stock > 0}">
<div class="product">
${p.name} - $${p.price}
</div>
</c:if>
</c:forEach>
4.2 错误处理最佳实践
配置web.xml:
xml复制<error-page>
<error-code>404</error-code>
<location>/errors/404.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/errors/general.jsp</location>
</error-page>
自定义错误页面:
jsp复制<%@ page isErrorPage="true" %>
<h3>抱歉,处理您的请求时出错</h3>
<p>错误信息: ${pageContext.exception.message}</p>
<p>堆栈跟踪:</p>
<pre><% exception.printStackTrace(new java.io.PrintWriter(out)); %></pre>
4.3 性能优化技巧
- 预编译JSP:在应用部署时使用
jspc工具预编译所有JSP - 合理使用缓冲区:根据页面大小设置适当的buffer大小
- 减少同步块:避免在声明的方法中使用
synchronized - 合理使用include:
- 静态包含(
<%@ include %>)适合不经常变化的内容 - 动态包含(
<jsp:include>)适合频繁变化的内容
- 静态包含(
- 启用EL表达式:比脚本表达式更高效
5. JSP与其他技术的集成
5.1 与Servlet协作
典型MVC模式中的分工:
- Servlet作为Controller:处理请求、调用业务逻辑
- JSP作为View:负责展示数据
- JavaBean作为Model:封装业务数据和逻辑
Servlet代码示例:
java复制protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 业务处理
UserService service = new UserService();
List<User> users = service.getAllUsers();
// 设置视图数据
request.setAttribute("userList", users);
// 转发到JSP
RequestDispatcher dispatcher = request.getRequestDispatcher("/users.jsp");
dispatcher.forward(request, response);
}
对应的JSP:
jsp复制<table>
<tr><th>ID</th><th>用户名</th><th>邮箱</th></tr>
<c:forEach items="${userList}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.email}</td>
</tr>
</c:forEach>
</table>
5.2 与JSTL结合使用
JSP标准标签库(JSTL)极大简化了JSP开发:
核心标签库:
jsp复制<c:set var="maxPrice" value="1000"/>
<c:if test="${product.price > maxPrice}">
<p class="warning">高价商品!</p>
</c:if>
<c:choose>
<c:when test="${user.role == 'admin'}">显示管理菜单</c:when>
<c:when test="${user.role == 'vip'}">显示VIP内容</c:when>
<c:otherwise>显示普通内容</c:otherwise>
</c:choose>
格式化标签库:
jsp复制<fmt:formatNumber value="${product.price}" type="currency"/>
<fmt:formatDate value="${order.date}" pattern="yyyy-MM-dd HH:mm"/>
5.3 与EL表达式配合
表达式语言(EL)使JSP更简洁:
jsp复制<%-- 访问作用域变量 --%>
<p>欢迎, ${sessionScope.user.name}</p>
<%-- 访问集合 --%>
<p>第一项: ${items[0].name}</p>
<%-- 访问Map --%>
<p>配置值: ${config['max.upload.size']}</p>
<%-- 使用运算符 --%>
<p>折扣价: ${product.price * (1 - product.discount)}</p>
6. 常见问题排查
6.1 JSP不编译/不更新
症状:修改JSP后页面无变化
- 检查1:确认容器是否配置了开发模式
xml复制<!-- Tomcat的context.xml --> <Context reloadable="true"> - 检查2:清理工作目录下的编译缓存
code复制$TOMCAT_HOME/work/Catalina/localhost/[app-name]/ - 检查3:检查JSP页面编码与pageEncoding是否一致
6.2 中文乱码问题
解决方案:
- 确保JSP文件本身使用UTF-8编码保存
- 页面指令设置正确:
jsp复制<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> - 在web.xml中添加过滤器:
xml复制<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter>
6.3 性能调优建议
- 禁用session:如果页面不需要session,添加指令:
jsp复制<%@ page session="false" %> - 合理设置缓冲区:
jsp复制<%@ page buffer="16kb" %> - 预加载常用JSP:在应用启动时访问关键JSP触发编译
- 使用静态包含:对不常变化的部分使用
<%@ include %>
7. 现代JSP开发模式
虽然现代Java Web开发更倾向于使用模板引擎(Thymeleaf、Freemarker)或前端框架(Vue、React),但JSP在以下场景仍有优势:
- 传统企业应用维护:许多遗留系统仍基于JSP
- 快速原型开发:内嵌Java代码便于快速验证
- 与Java EE生态集成:完美兼容Servlet、JSTL等规范
推荐架构方案:
code复制Browser → Servlet(Controller) → Service → DAO → DB
↓
JSP(View) ← JavaBeans(Model)
