JSP 教程
什么是 JSP?
JSP(Java Server Pages)是 Java 平台上的服务端页面技术,用于创建动态 Web 内容。JSP 文件以 .jsp 结尾,在服务器端编译为 Servlet 执行,把 Java 代码与 HTML 模板结合。
JSP 的核心特点
- Java 生态:复用 Java 的强大类库与企业级能力
- 跨平台:Write Once, Run Anywhere
- 与 Servlet 关系紧密:JSP 本质是 Servlet 的模板化
- MVC 支持:常与 Model 2 架构结合,配合 Servlet + JSP
- 标签库:JSTL、自定义标签减少脚本代码
JSP 工作原理
浏览器请求 .jsp 文件
→ 容器(Tomcat)把 JSP 翻译成 Servlet 源文件(.java)
→ 编译成 .class
→ 执行 Java 代码生成 HTML
→ 返回浏览器显示
JSP vs Servlet vs HTML
| 技术 | 定位 | 适用 |
|---|---|---|
| HTML | 纯静态 | 静态页面 |
| Servlet | Java 逻辑 | 处理请求、业务逻辑 |
| JSP | 视图模板 | 页面展示、动态内容 |
第一个 JSP 页面
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h1>Hello JSP!</h1>
<p>当前时间: <%= new java.util.Date() %></p>
</body>
</html>
部署到 Tomcat 的 webapps 目录后访问即可。
JSP 简介
JSP 简介
JSP 由 Sun 公司(现 Oracle)于 1999 年推出,是 Java Web 开发中最经典的视图技术之一。
JSP 的优势
- 与 Java 无缝集成,生态成熟
- 内置 9 大隐式对象(request、response、session 等)
- 支持自定义标签与 JSTL,代码整洁
- 容器自动处理生命周期与编译
- 适合企业级应用(与 Spring MVC 集成)
JSP 与 Servlet 的关系
// Servlet 中输出 HTML 很繁琐
out.println("<html>");
out.println("<body>");
out.println("<h1>Hello</h1>");
out.println("</body>");
// JSP 直接写 HTML,Java 代码混入
<h1>Hello</h1>
<% out.println("JSP 更方便"); %>
JSP 的三个发展阶段
阶段1: 纯 JSP(脚本段 + 表达式) ← 简单但混乱
阶段2: JSP + JavaBean + JSTL ← 标签化,减少脚本
阶段3: MVC(Model 2): Servlet 控制 + JSP 视图
开发环境
JDK + Tomcat(或 Jetty) + IDE(Eclipse/IDEA)
运行流程
1. 编写 index.jsp
2. 放入 Tomcat webapps/项目名/
3. 启动 Tomcat
4. 浏览器访问 http://localhost:8080/项目名/index.jsp
JSP 开发环境搭建
JSP 开发环境搭建
搭建 JSP 开发环境需要 JDK、Servlet 容器(Tomcat)与 IDE。
1. 安装 JDK
# 下载 JDK 并安装
java -version # 验证
# 配置 JAVA_HOME(Windows)
set JAVA_HOME=C:\\Program Files\\Java\\jdk-17
set PATH=%JAVA_HOME%\\bin;%PATH%
2. 安装 Tomcat
1. 从 tomcat.apache.org 下载(如 apache-tomcat-9.0.xx.zip)
2. 解压到如 D:\\tomcat9
3. 目录结构:
bin/ 启动脚本(startup.bat/sh)
conf/ 配置文件(server.xml)
webapps/ 部署目录(放 JSP 项目)
4. 启动: bin/startup.bat
5. 访问: http://localhost:8080 看到 Tomcat 首页
3. 部署第一个 JSP
# 创建项目目录
mkdir webapps/myapp
# 编写页面
echo "<h1>Hello JSP</h1>" > webapps/myapp/index.jsp
# 访问
http://localhost:8080/myapp/index.jsp
4. 配置 IDE(Eclipse)
1. Eclipse → Window → Preferences → Server → Runtime Environments
2. Add → Apache Tomcat v9.0 → 选择 Tomcat 目录
3. 创建 Dynamic Web Project
4. 右键项目 → Run As → Run on Server
5. 常见问题
- 端口 8080 被占用:修改 conf/server.xml 的 port
- 中文乱码:页面加 charset=UTF-8,Tomcat conf 中 URIEncoding=UTF-8
- 404:确认 webapps 下路径与文件名正确
Eclipse JSP/Servlet
Eclipse 开发 JSP/Servlet
Eclipse 配合 WTP 插件可以方便地开发 JSP/Servlet 项目。
创建动态 Web 项目
File → New → Dynamic Web Project
Project name: myweb
Target runtime: Apache Tomcat v9.0(需已配置)
Configuration: 默认
(勾选) Generate web.xml deployment descriptor
点击 Finish
项目结构
myweb/
├── src/ Java 源码(Servlet)
├── WebContent/ Web 资源(JSP、HTML、CSS)
│ ├── WEB-INF/
│ │ ├── web.xml 部署描述符
│ │ └── lib/ 依赖 JAR
│ └── index.jsp
└── build/ 编译输出
创建 Servlet
1. src 上右键 → New → Servlet
2. Package: com.example
Class name: HelloServlet
3. 勾选 URL mappings: /hello
4. Finish 后自动生成代码:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("<h1>Hello Servlet</h1>");
}
}
创建 JSP
1. WebContent 上右键 → New → JSP File
2. 文件名: welcome.jsp
3. 编写:
<%@ page contentType="text/html;charset=UTF-8" %>
<html><body>
<h1>欢迎页面</h1>
<p><%= request.getParameter("name") %></p>
</body></html>
运行项目
1. 右键项目 → Run As → Run on Server
2. 选择 Tomcat → Finish
3. 浏览器打开 http://localhost:8080/myweb/
4. 访问 Servlet: http://localhost:8080/myweb/hello
Servlet 与 JSP 传参
// Servlet 转发到 JSP
request.setAttribute("message", "来自 Servlet 的消息");
request.getRequestDispatcher("/welcome.jsp").forward(request, response);
// JSP 中取出
<p><%= request.getAttribute("message") %></p>
JSP 结构
JSP 页面结构
一个 JSP 页面由模板文本与 JSP 元素组成,元素分为指令、脚本、动作三大类。
JSP 元素总览
<%@ 指令 %> 指令:page、include、taglib
<%! 声明 %> 声明:定义变量与方法
<% 脚本段 %> 脚本:Java 代码片段
<%= 表达式 %> 表达式:输出结果
<jsp:xxx> 动作:jsp:include、jsp:forward 等
<%-- 注释 --%> 注释:仅服务端可见
完整示例
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page import="java.util.*" %>
<%-- 声明 --%>
<%!
private String getGreeting() {
return "欢迎光临!";
}
%>
<html>
<body>
<h1><%= getGreeting() %></h1>
<%-- 脚本段 --%>
<%
Date now = new Date();
out.println("现在时间: " + now);
%>
<ul>
<%
String[] items = {"Java", "JSP", "Servlet"};
for (String item : items) {
%>
<li><%= item %></li>
<%
}
%>
</ul>
</body>
</html>
模板文本
JSP 中非 JSP 元素的内容(HTML、CSS、JS)称为模板文本,原样输出到浏览器。
注释的区别
<%-- JSP 注释:客户端看不到 --%>
<!-- HTML 注释:发送到客户端,可被查看源码看到 --%>
JSP 生命周期
JSP 生命周期
JSP 从创建到销毁经历编译、初始化、服务、销毁四个阶段,由容器管理。
生命周期阶段
1. 编译阶段(Compilation)
JSP → Servlet 源文件 → .class
2. 初始化阶段(Initialization)
调用 jspInit() 方法(只执行一次)
3. 服务阶段(Service)
每次请求调用 _jspService() 方法
4. 销毁阶段(Destruction)
调用 jspDestroy() 方法(应用卸载时)
对应生命周期方法
// 可在 JSP 中重写
<%!
public void jspInit() {
System.out.println("JSP 初始化");
}
public void jspDestroy() {
System.out.println("JSP 销毁");
}
%>
编译时机
首次访问时才编译(首次较慢)
之后直接使用编译结果
修改 JSP 后,容器自动检测并重新编译
生命周期流程图
浏览器请求
│
▼
JSP 文件是否存在?
│
├── 否 → 404
▼
是,首次访问?
├── 是 → 翻译成 Servlet 源码
│ ├─ 编译成 .class
│ ▼
│ jspInit() (一次)
└── 否 → 直接用已编译 Servlet
▼
_jspService() (每次请求)
▼
应用卸载 → jspDestroy()
验证生命周期
1. 写一个打印日志的 JSP
2. 首次访问看 Tomcat 日志(编译 + 初始化 + 服务)
3. 再次访问只看到服务日志
4. 修改文件保存后刷新:触发重新编译
JSP 语法
JSP 语法详解
JSP 语法核心是四种脚本元素:声明、表达式、脚本段与注释。
声明(<%! ... %>)
<%!
// 声明成员变量(实例变量,多请求共享)
private int count = 0;
// 声明方法
public String toUpper(String s) {
return s.toUpperCase();
}
%>
表达式(<%= ... %>)
<!-- 输出表达式的值,等价于 out.print() -->
<p>当前计数: <%= count %></p>
<p>大写: <%= toUpper("hello") %></p>
<p>1 + 2 = <%= 1 + 2 %></p>
<!-- 注意:表达式末尾不能加分号 -->
脚本段(<% ... %>)
<%
// 任意 Java 代码
String name = request.getParameter("name");
if (name == null || name.isEmpty()) {
name = "匿名用户";
}
for (int i = 0; i < 3; i++) {
out.println("<p>第 " + i + " 次: " + name + "</p>");
}
%>
JSP 注释(<%-- --%>)
<%-- 这段注释不会出现在客户端源码中 --%>
<!-- 这段 HTML 注释客户端可见 -->
综合示例
<%@ page contentType="text/html;charset=UTF-8" %>
<%!
// 声明:计算阶乘的方法
public long factorial(int n) {
long result = 1;
for (int i = 2; i <= n; i++) result *= i;
return result;
}
%>
<html><body>
<h2>5! = <%= factorial(5) %></h2>
<%
for (int i = 1; i <= 10; i++) {
out.println("<li>" + i + "! = " + factorial(i) + "</li>");
}
%>
</body></html>
注意事项
- 脚本段中不能嵌套其他 JSP 元素
- 表达式不能以分号结尾
- 声明定义的变量是成员变量,线程不安全,慎用计数器
JSP 指令
JSP 指令(Directives)
指令用于设置页面属性、包含文件、引入标签库,语法为 <%@ ... %>。
page 指令
<%@ page
language="java"
contentType="text/html;charset=UTF-8"
import="java.util.*, java.io.*"
session="true"
buffer="8kb"
isThreadSafe="true"
errorPage="error.jsp"
isErrorPage="false"
%>
page 指令常用属性
contentType 响应类型与编码(中文必设 charset=UTF-8)
import 导入 Java 包,逗号分隔
session true/false 是否可用 session 对象
errorPage 发生异常时跳转的页面
isErrorPage 本页是否为错误页(可访问 exception 对象)
isELIgnored 是否忽略 EL 表达式
pageEncoding 页面文件编码
include 指令(静态包含)
<%@ include file="header.jsp" %>
<p>页面主体</p>
<%@ include file="footer.jsp" %>
<!-- 编译时把文件内容合并进来,只有一个 Servlet --%>
taglib 指令(标签库)
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:forEach var="item" items="${list}">
<p>${item}</p>
</c:forEach>
指令的放置位置
// page 指令习惯放页面顶部
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head><title>示例</title></head>
<body>
...
</body>
</html>
常见错误
1. 未设 charset → 中文乱码
2. import 少了包 → 编译错误
3. 指令属性拼写错误 → 翻译阶段报错
JSP 动作元素
JSP 动作元素(Action)
动作元素控制 JSP 运行时的行为,使用 XML 语法 <jsp:xxx>。
jsp:include(动态包含)
<!-- 请求时包含,每个文件独立编译 -->
<jsp:include page="header.jsp">
<jsp:param name="title" value="我的页面"/>
</jsp:include>
<p>主体内容</p>
<jsp:include page="footer.jsp"/>
jsp:forward(转发)
<%
// 逻辑判断后转发
String role = (String) session.getAttribute("role");
%>
<%
if ("admin".equals(role)) {
%>
<jsp:forward page="admin.jsp"/>
<%
} else {
%>
<jsp:forward page="login.jsp"/>
<%
}
%>
jsp:param(传递参数)
<jsp:include page="info.jsp">
<jsp:param name="user" value="张三"/>
<jsp:param name="age" value="28"/>
</jsp:include>
<!-- 目标页面用 request.getParameter("user") 获取 --%>
jsp:useBean / setProperty / getProperty
<!-- 使用 JavaBean -->
<jsp:useBean id="user" class="com.example.User" scope="request"/>
<jsp:setProperty name="user" property="name" value="李四"/>
<p>用户名: <jsp:getProperty name="user" property="name"/></p>
<!-- 从表单自动填充 -->
<jsp:setProperty name="user" property="*"/>
jsp:plugin(已不常用)
<jsp:plugin type="applet" code="MyApplet.class"></jsp:plugin>
<!-- 老式 Applet 支持,现代项目已弃用 -->
静态 vs 动态包含
静态包含(<%@ include %>): 编译期合并,共享变量,快
动态包含(<jsp:include %>): 请求期执行,独立变量,灵活
JSP 隐式对象
JSP 隐式对象(Implicit Objects)
JSP 内置 9 个隐式对象,无需声明即可直接使用。
9 大隐式对象
request HttpServletRequest 客户端请求
response HttpServletResponse 服务端响应
out JspWriter 输出流
session HttpSession 会话
application ServletContext 应用上下文
config ServletConfig 配置信息
page Object 当前 JSP 实例
pageContext PageContext 页面上下文
exception Throwable 异常(仅错误页)
常用示例
<%-- request --%>
<p>请求方法: <%= request.getMethod() %></p>
<p>请求 URI: <%= request.getRequestURI() %></p>
<p>客户端 IP: <%= request.getRemoteAddr() %></p>
<%-- response --%>
<%
response.setContentType("text/html;charset=UTF-8");
response.setHeader("X-Custom", "value");
%>
<%-- out --%>
<% out.println("直接输出"); %>
<%-- session --%>
<%
session.setAttribute("username", "admin");
String user = (String) session.getAttribute("username");
%>
<%-- application --%>
<%
application.setAttribute("appName", "MySite");
String name = (String) application.getAttribute("appName");
%>
pageContext(页面上下文)
<%
// 设置/获取各作用域属性
pageContext.setAttribute("msg", "页面级");
pageContext.setAttribute("msg", "请求级", PageContext.REQUEST_SCOPE);
pageContext.setAttribute("msg", "会话级", PageContext.SESSION_SCOPE);
pageContext.setAttribute("msg", "应用级", PageContext.APPLICATION_SCOPE);
// 获取
String m = (String) pageContext.getAttribute("msg");
String r = (String) pageContext.getAttribute("msg", PageContext.REQUEST_SCOPE);
%>
四种作用域
page 当前页面(最短)
request 一次请求(转发间共享)
session 一个会话
application 整个应用(所有用户共享)
JSP 客户端请求
JSP 处理客户端请求
通过 request 对象获取客户端发送的各种信息与参数。
获取请求头
<%
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
out.println(name + ": " + request.getHeader(name) + "<br/>");
}
%>
常用请求头
<%
out.println("User-Agent: " + request.getHeader("User-Agent") + "<br/>");
out.println("Referer: " + request.getHeader("Referer") + "<br/>");
out.println("Accept-Language: " + request.getHeader("Accept-Language") + "<br/>");
out.println("Cookie: " + request.getHeader("Cookie") + "<br/>");
%>
获取请求参数
<%
// 单个参数
String name = request.getParameter("name");
// 多个同名参数(复选框)
String[] hobbies = request.getParameterValues("hobby");
// 所有参数名
Enumeration<String> params = request.getParameterNames();
while (params.hasMoreElements()) {
String p = params.nextElement();
out.println(p + " = " + request.getParameter(p) + "<br/>");
}
%>
获取客户端信息
<%
out.println("IP: " + request.getRemoteAddr() + "<br/>");
out.println("主机: " + request.getRemoteHost() + "<br/>");
out.println("端口: " + request.getRemotePort() + "<br/>");
out.println("方法: " + request.getMethod() + "<br/>");
out.println("URI: " + request.getRequestURI() + "<br/>");
out.println("查询串: " + request.getQueryString() + "<br/>");
out.println("协议: " + request.getProtocol());
%>
表单提交处理
<%@ page contentType="text/html;charset=UTF-8" %>
<%
request.setCharacterEncoding("UTF-8"); // POST 中文
String name = request.getParameter("username");
String pwd = request.getParameter("password");
if (name != null && !name.isEmpty()) {
out.println("欢迎, " + name);
}
%>
<form method="post">
用户名: <input type="text" name="username"/><br/>
密码: <input type="password" name="password"/><br/>
<input type="submit" value="提交"/>
</form>
JSP 服务器响应
JSP 服务器响应
通过 response 对象控制发送给客户端的内容与状态。
设置响应头
<%
// 内容类型与编码
response.setContentType("text/html;charset=UTF-8");
// 自定义头
response.setHeader("X-Powered-By", "JSP");
// 缓存控制
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 刷新
response.setHeader("Refresh", "5;url=index.jsp"); // 5 秒后跳转
%>
重定向(Redirect)
<%
// 302 重定向,地址栏会变化
response.sendRedirect("login.jsp");
%>
<!-- 或带参数 --%>
<%
response.sendRedirect("login.jsp?msg=timeout");
%>
设置状态码
<%
// 各种状态码
response.sendError(404, "页面不存在");
// response.setStatus(500);
// response.sendError(403, "禁止访问");
%>
输出缓冲
<%
out.println("第一行");
out.flush(); // 强制刷新缓冲区
out.println("第二行");
// 检查缓冲区
out.println("缓冲大小: " + out.getBufferSize());
out.println("剩余: " + out.getRemaining());
%>
中文输出
<%@ page contentType="text/html;charset=UTF-8" %>
<%
// 同时设置请求与响应编码
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
out.println("中文内容正常显示");
%>
与转发的区别
sendRedirect: 客户端再请求一次(URL 变化,可跨应用)
forward: 服务端内部跳转(URL 不变,共享 request)
JSP HTTP 状态码
JSP HTTP 状态码
HTTP 状态码标识请求的处理结果,开发时常用 3xx/4xx/5xx 控制流程。
状态码分类
1xx 信息
2xx 成功
3xx 重定向
4xx 客户端错误
5xx 服务端错误
常用状态码
200 OK 成功
302 Found 重定向(sendRedirect)
400 Bad Request 请求格式错误
401 Unauthorized 未认证
403 Forbidden 无权限
404 Not Found 资源不存在
405 Method Not Allowed 方法不允许
500 Internal Server Error 服务器内部错误
503 Service Unavailable 服务不可用
设置状态码
<%
// 方法一
response.setStatus(403);
out.println("禁止访问");
// 方法二:带错误页
response.sendError(404, "自定义错误信息");
%>
根据业务返回不同状态
<%
String action = request.getParameter("action");
if ("notfound".equals(action)) {
response.sendError(404, "找不到该资源");
} else if ("forbidden".equals(action)) {
response.sendError(403, "没有访问权限");
} else if ("error".equals(action)) {
response.sendError(500, "服务器内部错误");
} else {
response.setStatus(200);
out.println("访问正常");
}
%>
自定义错误页面
<!-- web.xml 配置 -->
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error/exception.jsp</location>
</error-page>
<!-- 404.jsp -->
<%@ page contentType="text/html;charset=UTF-8" %>
<html><body>
<h1>404 页面不存在</h1>
<a href="index.jsp">返回首页</a>
</body></html>
JSP 表单处理
JSP 表单处理
JSP 可以接收 GET/POST 表单数据并进行处理。
GET 提交
<form method="get" action="search.jsp">
<input type="text" name="keyword" placeholder="搜索关键词"/>
<input type="submit" value="搜索"/>
</form>
<!-- search.jsp --%>
<%
request.setCharacterEncoding("UTF-8");
String kw = request.getParameter("keyword");
out.println("搜索: " + kw);
%>
<!-- URL 形如 search.jsp?keyword=java --%>
POST 提交
<form method="post" action="register.jsp">
用户名: <input type="text" name="username"/><br/>
密码: <input type="password" name="password"/><br/>
性别: <input type="radio" name="gender" value="男"/>男
<input type="radio" name="gender" value="女"/>女<br/>
爱好: <input type="checkbox" name="hobby" value="编程"/>编程
<input type="checkbox" name="hobby" value="阅读"/>阅读<br/>
城市:
<select name="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
</select><br/>
留言: <textarea name="message"></textarea><br/>
<input type="submit" value="注册"/>
</form>
<!-- register.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<%
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");
String city = request.getParameter("city");
String message = request.getParameter("message");
// 多值参数
String[] hobbies = request.getParameterValues("hobby");
out.println("用户名: " + username + "<br/>");
out.println("性别: " + gender + "<br/>");
out.println("城市: " + city + "<br/>");
if (hobbies != null) {
out.println("爱好: " + String.join(", ", hobbies) + "<br/>");
}
out.println("留言: " + message);
%>
输入校验
<%
request.setCharacterEncoding("UTF-8");
String email = request.getParameter("email");
if (email == null || email.trim().isEmpty()) {
out.println("<p style='color:red'>邮箱不能为空</p>");
} else if (!email.contains("@")) {
out.println("<p style='color:red'>邮箱格式不正确</p>");
} else {
out.println("<p style='color:green'>邮箱有效: " + email + "</p>");
}
%>
安全提示
1. 所有输入都可能是恶意的,务必校验
2. 输出时用 c:out 或 HTML 转义防 XSS
3. 数据库操作使用 PreparedStatement 防 SQL 注入
JSP 过滤器
JSP 过滤器(Filter)
过滤器在请求到达 Servlet/JSP 之前和响应返回之前进行拦截处理,用于编码、认证、日志、压缩等。
创建过滤器
package com.example.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*") // 过滤所有请求
public class EncodingFilter implements Filter {
public void init(FilterConfig config) {}
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// 请求前处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 放行到目标资源
chain.doFilter(request, response);
// 响应后处理
System.out.println("请求已处理完成");
}
public void destroy() {}
}
web.xml 配置过滤器
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.example.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
登录认证过滤器
@WebFilter("/admin/*")
public class AuthFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 检查登录状态
Object user = request.getSession().getAttribute("user");
if (user == null) {
// 未登录,跳转到登录页
response.sendRedirect(request.getContextPath() + "/login.jsp");
return; // 不放行
}
// 已登录,放行
chain.doFilter(request, response);
}
}
过滤器链
多个过滤器按映射顺序执行:
Request → Filter1 → Filter2 → Servlet → Filter2 → Filter1 → Response
应用场景
1. 统一编码设置
2. 登录/权限校验
3. 请求日志记录
4. 敏感词过滤
5. 响应压缩(GZip)
6. 缓存控制
JSP Cookie 处理
JSP Cookie 处理
Cookie 存储在客户端浏览器,用于记住用户状态(如记住登录、偏好)。
创建 Cookie
<%
// 创建
Cookie userCookie = new Cookie("username", "zhangsan");
// 设置有效期(秒),不设置则会话级
userCookie.setMaxAge(60 * 60 * 24 * 7); // 7 天
// 设置路径(整个应用可用)
userCookie.setPath("/");
// 写入响应
response.addCookie(userCookie);
out.println("Cookie 已设置");
%>
读取 Cookie
<%
Cookie[] cookies = request.getCookies();
String username = null;
if (cookies != null) {
for (Cookie c : cookies) {
if ("username".equals(c.getName())) {
username = c.getValue();
}
out.println(c.getName() + " = " + c.getValue() + "<br/>");
}
}
if (username != null) {
out.println("欢迎回来: " + username);
} else {
out.println("你是新访客");
}
%>
删除 Cookie
<%
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("username".equals(c.getName())) {
c.setMaxAge(0); // 立即过期
c.setPath("/");
response.addCookie(c); // 重新写入(覆盖删除)
}
}
}
out.println("Cookie 已删除");
%>
记住登录状态示例
<%@ page contentType="text/html;charset=UTF-8" %>
<%
// 登录成功时
if (request.getParameter("login") != null) {
String remember = request.getParameter("remember");
Cookie c = new Cookie("username", request.getParameter("username"));
c.setPath("/");
if ("on".equals(remember)) {
c.setMaxAge(60 * 60 * 24 * 30); // 记住 30 天
} else {
c.setMaxAge(-1); // 会话级
}
response.addCookie(c);
response.sendRedirect("index.jsp");
}
%>
<form method="post">
用户名: <input type="text" name="username"/><br/>
<label><input type="checkbox" name="remember"/>记住我</label><br/>
<input type="submit" name="login" value="登录"/>
</form>
注意事项
1. Cookie 大小限制约 4KB
2. 敏感数据不要明文存 Cookie(可存 Session ID)
3. 中文值需要 URL 编码: URLEncoder.encode(value, "UTF-8")
4. HttpOnly 可防 XSS 读取: c.setHttpOnly(true)
JSP Session
JSP Session 会话
Session 在服务器端保存用户会话数据,配合 Cookie(Session ID)识别用户。
设置与读取
<%
// 设置
session.setAttribute("username", "admin");
session.setAttribute("role", "管理员");
// 读取
String user = (String) session.getAttribute("username");
out.println("当前用户: " + user);
// 移除
// session.removeAttribute("username");
%>
Session 属性
<%
out.println("Session ID: " + session.getId() + "<br/>");
out.println("创建时间: " + new java.util.Date(session.getCreationTime()) + "<br/>");
out.println("最后访问: " + new java.util.Date(session.getLastAccessedTime()) + "<br/>");
out.println("超时时间: " + session.getMaxInactiveInterval() + " 秒");
// 设置超时(秒)
session.setMaxInactiveInterval(30 * 60); // 30 分钟
%>
登录状态管理
<!-- login.jsp --%>
<%
request.setCharacterEncoding("UTF-8");
String u = request.getParameter("username");
String p = request.getParameter("password");
if ("admin".equals(u) && "123456".equals(p)) {
session.setAttribute("user", u);
response.sendRedirect("home.jsp");
} else {
out.println("<p style='color:red'>登录失败</p>");
}
%>
<!-- home.jsp:校验 --%>
<%
Object user = session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
<h1>欢迎, <%= user %></h1>
<a href="logout.jsp">退出登录</a>
<!-- logout.jsp --%>
<%
session.invalidate(); // 销毁会话
response.sendRedirect("login.jsp");
%>
购物车示例
<%
// 购物车用 Session 存储
java.util.List<String> cart =
(java.util.List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new java.util.ArrayList<String>();
session.setAttribute("cart", cart);
}
// 添加商品
String item = request.getParameter("item");
if (item != null) {
cart.add(item);
}
out.println("购物车(" + cart.size() + " 件): " + cart);
%>
会话与 Cookie 的关系
1. 首次访问:服务器创建 Session,通过 Cookie(JSESSIONID)告诉浏览器
2. 之后每次请求带 JSESSIONID,服务器识别会话
3. 浏览器禁用 Cookie 时,Session ID 会放在 URL 中(URL Rewriting)
JSP 文件上传
JSP 文件上传
Servlet 3.0+ 原生支持文件上传,无需第三方库。
上传表单
<form method="post" action="upload.jsp" enctype="multipart/form-data">
选择文件: <input type="file" name="file"/><br/>
<input type="submit" value="上传"/>
</form>
上传处理(Servlet)
package com.example.servlet;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
@WebServlet("/upload")
@MultipartConfig(maxFileSize = 10 * 1024 * 1024) // 10MB
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 获取上传文件
Part part = request.getPart("file");
String fileName = extractFileName(part);
// 保存到 upload 目录
String saveDir = getServletContext().getRealPath("/upload");
File dir = new File(saveDir);
if (!dir.exists()) dir.mkdirs();
part.write(saveDir + File.separator + fileName);
response.getWriter().println("上传成功: " + fileName);
}
private String extractFileName(Part part) {
String contentDisposition = part.getHeader("content-disposition");
for (String token : contentDisposition.split(";")) {
if (token.trim().startsWith("filename")) {
return token.substring(token.indexOf("=") + 1)
.trim().replace(""", "");
}
}
return "unknown";
}
}
多文件上传
<form method="post" action="upload" enctype="multipart/form-data">
<input type="file" name="files" multiple/>
<input type="submit" value="批量上传"/>
</form>
// Servlet 中
Collection<Part> parts = request.getParts();
for (Part part : parts) {
if (part.getSize() > 0 && part.getName().equals("files")) {
String fileName = extractFileName(part);
part.write(saveDir + File.separator + fileName);
}
}
限制文件类型
// 检查扩展名
String fileName = extractFileName(part);
if (!fileName.toLowerCase().endsWith(".jpg")
&& !fileName.toLowerCase().endsWith(".png")) {
response.getWriter().println("只允许上传图片");
return;
}
注意事项
1. 表单必须 enctype="multipart/form-data"
2. web.xml 或注解中配置 @MultipartConfig
3. 限制大小防止 DoS
4. 文件重命名避免重名与路径穿越
JSP 日期处理
JSP 日期处理
JSP 使用 java.util.Date 与 java.text.SimpleDateFormat 处理日期时间。
获取当前时间
<%@ page import="java.util.*, java.text.*" %>
<%
Date now = new Date();
out.println("当前时间: " + now);
%>
格式化日期
<%
Date now = new Date();
// 自定义格式
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy年MM月dd日 EEEE");
out.println("日期: " + sdf1.format(now) + "<br/>");
out.println("时间: " + sdf2.format(now) + "<br/>");
out.println("中文: " + sdf3.format(now));
%>
格式字符说明
yyyy 年(四位)
MM 月(两位)
dd 日
HH 时(24 小时)
hh 时(12 小时)
mm 分
ss 秒
SSS 毫秒
EEEE 星期几(完整)
EEE 星期几(缩写)
字符串解析为日期
<%
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
Date d = sdf.parse("2026-08-18");
out.println("解析成功: " + d);
} catch (Exception e) {
out.println("解析失败: " + e.getMessage());
}
%>
日历操作(Calendar)
<%
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 7); // 7 天后
cal.add(Calendar.MONTH, -1); // 1 个月前
cal.set(2026, Calendar.JANUARY, 1); // 设定日期
Date d = cal.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
out.println("目标日期: " + sdf.format(d));
%>
与 JSTL 标签显示日期
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<jsp:useBean id="now" class="java.util.Date"/>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss"/>
计算时间差
<%
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date start = sdf.parse("2026-01-01 00:00:00");
Date end = new Date();
long diff = end.getTime() - start.getTime(); // 毫秒
long days = diff / (1000 * 60 * 60 * 24);
out.println("已经过去 " + days + " 天");
} catch (Exception e) {
out.println("计算失败");
}
%>
JSP 页面重定向
JSP 页面重定向
重定向让浏览器跳转到另一个 URL,实现登录跳转、支付跳转等场景。
sendRedirect 重定向
<%
// 绝对路径
response.sendRedirect("http://www.example.com");
// 相对路径(当前应用内)
response.sendRedirect("login.jsp");
// 带参数
response.sendRedirect("login.jsp?msg=session_timeout");
%>
转发(forward)vs 重定向(redirect)
// 转发:服务端内部跳转
<jsp:forward page="welcome.jsp"/>
<%
request.getRequestDispatcher("/welcome.jsp").forward(request, response);
%>
// 重定向:客户端跳转
<%
response.sendRedirect("welcome.jsp");
%>
两者的区别
转发(forward):
1. URL 不变
2. 只有一次请求
3. 可以带 request 属性
4. 只能跳转应用内页面
重定向(redirect):
1. URL 改变
2. 两次请求
3. 通过 URL 参数或 Session 传数据
4. 可以跳转外部网站
登录后重定向
<%
String target = (String) session.getAttribute("target");
if (target != null) {
session.removeAttribute("target");
response.sendRedirect(target);
} else {
response.sendRedirect("index.jsp");
}
%>
自动刷新与跳转
<!-- 5 秒后跳转 --%>
<%
response.setHeader("Refresh", "5;url=index.jsp");
%>
<p>5 秒后自动返回首页...</p>
<a href="index.jsp">立即返回</a>
注意事项
1. sendRedirect 之前不要输出内容(会报 IllegalStateException)
2. 重定向后页面剩余代码不再执行(加 return)
3. 中文参数需要 URLEncoder.encode()
JSP 点击量统计
JSP 点击量统计
统计网站访问量通常用 Application 对象(全局计数)或数据库持久化。
基于 Application 的计数器
<%
// 使用 application 对象(所有用户共享)
Integer count = (Integer) application.getAttribute("visitCount");
if (count == null) {
count = 0;
}
count++;
application.setAttribute("visitCount", count);
%>
<p>网站总访问量: <%= count %> 次</p>
多线程安全
<%
synchronized (application) {
Integer count = (Integer) application.getAttribute("visitCount");
if (count == null) count = 0;
count++;
application.setAttribute("visitCount", count);
}
%>
统计在线人数(结合监听器)
// HttpSessionListener 实现
package com.example.listener;
import javax.servlet.http.*;
public class OnlineListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
ServletContext ctx = se.getSession().getServletContext();
Integer ctx.getAttribute("online");
if (online == null)
ctx.setAttribute("online", online + 1);
}
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext ctx = se.getSession().getServletContext();
Integer ctx.getAttribute("online");
if (online == null)
ctx.setAttribute("online", online - 1);
}
}
<!-- web.xml 注册 -->
<listener>
<listener-class>com.example.listener.OnlineListener</listener-class>
</listener>
<!-- JSP 显示 --%>
<p>当前在线: <%= application.getAttribute("online") %> 人</p>
数据库持久化计数器
<%
// 每次访问 +1,存数据库防重启丢失
// 假设已有数据库连接 conn
/*
Statement st = conn.createStatement();
st.executeUpdate("UPDATE stats SET count = count + 1 WHERE id=1");
ResultSet rs = st.executeQuery("SELECT count FROM stats WHERE id=1");
if (rs.next()) {
out.println("总访问量: " + rs.getInt(1));
}
*/
%>
注意事项
1. Application 计数器重启后清零
2. 高并发用 synchronized 或原子类
3. 生产环境建议用数据库/Redis 统计
4. 可按天统计:键名带日期 visitCount_20260818
JSP 自动刷新
JSP 自动刷新
自动刷新用于定时更新页面内容,适合股票行情、实时状态、倒计时等场景。
定时刷新页面
<%@ page contentType="text/html;charset=UTF-8" %>
<%
// 每 5 秒刷新一次
response.setHeader("Refresh", "5");
%>
<p>当前时间: <%= new java.util.Date() %></p>
<p>页面每 5 秒自动刷新</p>
刷新并跳转
<%
// 5 秒后跳转到 index.jsp
response.setHeader("Refresh", "5;url=index.jsp");
%>
<p>5 秒后跳转到首页...</p>
实时时间显示
<%@ page contentType="text/html;charset=UTF-8" %>
<%
response.setHeader("Refresh", "1"); // 每秒刷新
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
%>
<h1>服务器时间: <%= sdf.format(new java.util.Date()) %></h1>
定时跳转(HTML 方式)
<meta http-equiv="refresh" content="5;url=index.jsp"/>
<p>页面 5 秒后跳转</p>
JavaScript 定时刷新
<script>
// 5 秒后刷新页面
setTimeout(function() {
location.reload();
}, 5000);
// 10 秒后跳转
setTimeout(function() {
location.href = "index.jsp";
}, 10000);
</script>
实时数据轮询示例
<%@ page contentType="text/html;charset=UTF-8" %>
<%
response.setHeader("Refresh", "3");
// 模拟实时数据(实际可从数据库/内存读)
int serverLoad = (int)(Math.random() * 100);
%>
<p>服务器负载: <%= serverLoad %>%</p>
注意事项
1. 频繁刷新会增加服务器压力,合理设置间隔
2. 实时性要求高建议用 AJAX 轮询或 WebSocket
3. 刷新页面前确认数据已保存,避免丢失
JSP 发送邮件
JSP 发送邮件
JSP 通过 JavaMail API 发送邮件,需要引入 mail.jar 与 activation.jar。
引入依赖
<!-- WEB-INF/lib 下放置 --%>
mail.jar
activation.jar
<!-- 或 Maven --%>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
发送简单文本邮件
<%@ page import="java.util.*, javax.mail.*, javax.mail.internet.*" %>
<%
final String from = "sender@example.com";
final String password = "授权码";
String to = "receiver@example.com";
String subject = "测试邮件";
String body = "这是一封来自 JSP 的测试邮件。";
// SMTP 配置
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.example.com");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from, password);
}
});
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject, "UTF-8");
message.setText(body, "UTF-8");
Transport.send(message);
out.println("邮件发送成功!");
} catch (MessagingException e) {
out.println("发送失败: " + e.getMessage());
}
%>
发送 HTML 邮件
<%
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("HTML 邮件", "UTF-8");
// HTML 正文
message.setContent(
"<h1>欢迎注册</h1><p>点击<a href='http://example.com/verify'>这里</a>激活账号</p>",
"text/html;charset=UTF-8");
Transport.send(message);
%>
带附件
<%
MimeMessage message = new MimeMessage(session);
message.setSubject("带附件邮件");
// 多部分正文
MimeMultipart multipart = new MimeMultipart();
// 正文部分
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText("附件请查收");
multipart.addBodyPart(textPart);
// 附件部分
MimeBodyPart filePart = new MimeBodyPart();
filePart.attachFile(application.getRealPath("/files/report.pdf"));
multipart.addBodyPart(filePart);
message.setContent(multipart);
Transport.send(message);
%>
表单发邮件应用
<form method="post">
收件人: <input type="text" name="to"/><br/>
主题: <input type="text" name="subject"/><br/>
内容: <textarea name="body"></textarea><br/>
<input type="submit" value="发送"/>
</form>
<%
if (request.getParameter("to") != null) {
// 用表单值调用发送逻辑...
out.println("邮件已发送给 " + request.getParameter("to"));
}
%>
注意事项
1. 使用 SMTP 授权码而非登录密码
2. SSL 端口 465 或 STARTTLS 587
3. 生产环境邮件发送建议放到后台线程/消息队列
4. 发送频率限制,防止被当作垃圾邮件
JSP 标准标签库(JSTL)
JSP 标准标签库(JSTL)
JSTL(JSP Standard Tag Library)是一组标准标签,替代脚本代码,让 JSP 更简洁易维护。
引入 JSTL
1. 下载 jstl.jar 与 standard.jar 放入 WEB-INF/lib
2. 页面引入:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
c:out 输出
<!-- 输出(自动 HTML 转义防 XSS) -->
<c:out value="${username}"/>
<c:out value="${user.name}" default="匿名"/>
c:set / c:remove
<!-- 设置变量 -->
<c:set var="age" value="28" scope="session"/>
<c:set var="score" value="${score + 10}"/>
<!-- 移除 -->
<c:remove var="age"/>
c:if 条件
<c:if test="${score >= 60}">
<p>及格!</p>
</c:if>
<c:if test="${empty user}">
<p>未登录</p>
</c:if>
c:choose 多分支
<c:choose>
<c:when test="${score >= 90}">优秀</c:when>
<c:when test="${score >= 60}">及格</c:when>
<c:otherwise>不及格</c:otherwise>
</c:choose>
c:forEach 循环
<!-- 遍历集合 -->
<c:forEach var="item" items="${productList}">
<p>${item.name} - ${item.price}</p>
</c:forEach>
<!-- 带索引 -->
<c:forEach var="item" items="${list}" varStatus="st">
<p>${st.index}: ${item}</p> <!-- st.index 从 0 开始 --%>
</c:forEach>
<!-- 固定次数 -->
<c:forEach var="i" begin="1" end="5" step="1">
<span>${i} </span>
</c:forEach>
fmt 格式化
<!-- 日期 -->
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd"/>
<!-- 数字 -->
<fmt:formatNumber value="${price}" type="currency"/>
<fmt:formatNumber value="${rate}" type="percent"/>
c:url 构建 URL
<c:url var="detailUrl" value="/product.jsp">
<c:param name="id" value="${product.id}"/>
</c:url>
<a href="${detailUrl}">详情</a>
完整示例:商品列表
<c:if test="${empty products}">
<p>暂无商品</p>
</c:if>
<c:forEach var="p" items="${products}">
<div class="product">
<h3><c:out value="${p.name}"/></h3>
<p>价格: <fmt:formatNumber value="${p.price}" type="currency"/></p>
</div>
</c:forEach>
JSP 连接数据库
JSP 连接数据库
JSP 通过 JDBC 连接数据库执行增删改查,这是 Web 应用的核心能力。
1. 引入驱动
# MySQL
mysql-connector-java-8.0.x.jar → WEB-INF/lib
<!-- Maven --%>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
2. 连接 MySQL
<%@ page import="java.sql.*" %>
<%
String url = "jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8";
String user = "root";
String password = "123456";
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(url, user, password);
out.println("数据库连接成功!");
} catch (Exception e) {
out.println("连接失败: " + e.getMessage());
} finally {
if (conn != null) conn.close();
}
%>
3. 查询数据
<%
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery("SELECT id, name, price FROM products");
out.println("<table border='1'>");
out.println("<tr><th>ID</th><th>名称</th><th>价格</th></tr>");
while (rs.next()) {
out.println("<tr><td>" + rs.getInt("id")
+ "</td><td>" + rs.getString("name")
+ "</td><td>" + rs.getDouble("price")
+ "</td></tr>");
}
out.println("</table>");
} finally {
if (rs != null) rs.close();
if (st != null) st.close();
if (conn != null) conn.close();
}
%>
4. PreparedStatement 防注入
<%
String keyword = request.getParameter("kw");
String sql = "SELECT * FROM products WHERE name LIKE ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "%" + keyword + "%"); // 参数化查询
ResultSet rs = ps.executeQuery();
while (rs.next()) {
out.println(rs.getString("name") + "<br/>");
}
}
%>
5. 插入数据
<%
String name = request.getParameter("name");
double price = Double.parseDouble(request.getParameter("price"));
String sql = "INSERT INTO products (name, price) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
ps.setDouble(2, price);
int rows = ps.executeUpdate();
out.println("插入成功,影响 " + rows + " 行");
}
%>
6. 使用连接池(推荐)
<!-- 在 WEB-INF/context.xml 配置 --%>
<Context>
<Resource name="jdbc/mydb"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/shop"
username="root" password="123456"
maxTotal="20" maxIdle="5" maxWaitMillis="10000"/>
</Context>
<%-- JSP 中使用 --%>
<%
javax.sql.DataSource ds = (javax.sql.DataSource)
new javax.naming.InitialContext().lookup("java:comp/env/jdbc/mydb");
Connection conn = ds.getConnection(); // 从池中取连接
// ... 使用后归还
conn.close();
%>
JSP XML 数据处理
JSP XML 数据处理
JSP 可以使用 DOM、SAX 或 JSTL XML 标签库处理 XML 数据。
1. 使用 JSTL XML 标签
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %>
<!-- 加载 XML --%>
<c:import url="data.xml" var="xmlData"/>
<x:parse xml="${xmlData}" var="doc"/>
<!-- XPath 查询 --%>
<x:forEach select="$doc//product">
<p>
<x:out select="name"/> -
<x:out select="price"/>
</p>
</x:forEach>
<x:if select="$doc//product[@id=1]">
<p>找到 id=1 的商品</p>
</x:if>
2. DOM 解析
<%@ page import="javax.xml.parsers.*, org.w3c.dom.*" %>
<%
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析 XML 文件或字符串
Document doc = builder.parse(application.getRealPath("/data.xml"));
// 获取所有 product 节点
NodeList list = doc.getElementsByTagName("product");
for (int i = 0; i < list.getLength(); i++) {
Element product = (Element) list.item(i);
String name = product.getElementsByTagName("name").item(0).getTextContent();
String price = product.getElementsByTagName("price").item(0).getTextContent();
out.println(name + " - " + price + "<br/>");
}
%>
3. 生成 XML 响应
<%@ page contentType="application/xml;charset=UTF-8" %>
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<message>操作成功</message>
<% for (int i = 1; i <= 3; i++) { %>
<item id="<%= i %>">数据 <%= i %></item>
<% } %>
</response>
4. XML 与 XSLT 转换
<%@ page import="javax.xml.transform.*, javax.xml.transform.stream.*" %>
<%
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer(
new StreamSource(application.getRealPath("/style.xsl")));
t.transform(
new StreamSource(application.getRealPath("/data.xml")),
new StreamResult(response.getWriter()));
%>
注意事项
1. XML 大小适中用 DOM,超大文件用 SAX(流式)
2. XPath 语法要转义特殊字符
3. 输出 XML 时正确设置 contentType
JSP JavaBean
JSP JavaBean
JavaBean 是符合规范的 Java 类(私有字段 + 公有 getter/setter),用于封装数据与业务逻辑。
创建 JavaBean
package com.example.bean;
public class User {
// 私有字段
private String name;
private int age;
private String email;
// 无参构造器(必需)
public User() {}
// Getter/Setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
jsp:useBean 使用
<%@ page import="com.example.bean.User" %>
<!-- 创建/获取 bean --%>
<jsp:useBean id="user" class="com.example.bean.User" scope="request"/>
<!-- 设置属性 --%>
<jsp:setProperty name="user" property="name" value="张三"/>
<jsp:setProperty name="user" property="age" value="28"/>
<!-- 获取属性 --%>
<p>姓名: <jsp:getProperty name="user" property="name"/></p>
<p>年龄: <jsp:getProperty name="user" property="age"/></p>
从表单自动填充
<!-- 表单字段名与 bean 属性名一致 --%>
<form method="post">
姓名: <input type="text" name="name"/><br/>
年龄: <input type="text" name="age"/><br/>
邮箱: <input type="text" name="email"/><br/>
<input type="submit" value="提交"/>
</form>
<%
if (request.getParameter("name") != null) {
%>
<!-- property="*" 自动匹配所有同名表单字段 --%>
<jsp:useBean id="user" class="com.example.bean.User" scope="request"/>
<jsp:setProperty name="user" property="*"/>
<p>提交成功: <jsp:getProperty name="user" property="name"/>
(<jsp:getProperty name="user" property="age"/> 岁)</p>
<%
}
%>
scope 作用域
<jsp:useBean id="u1" class="..." scope="page"/> 页面级
<jsp:useBean id="u2" class="..." scope="request"/> 请求级
<jsp:useBean id="u3" class="..." scope="session"/> 会话级
<jsp:useBean id="u4" class="..." scope="application"/> 应用级
与 EL 表达式配合
<%@ page import="com.example.bean.User" %>
<%
User user = new User();
user.setName("李四");
request.setAttribute("user", user);
%>
<!-- EL 访问 --%>
<p>姓名: ${user.name}</p>
<p>年龄: ${user.age}</p>
JavaBean 规范
1. public 类
2. 无参构造器
3. 私有字段
4. 公有 getter/setter
5. 布尔属性 getter 可用 isXxx()
6. 实现 Serializable(可选但推荐)
JSP 自定义标签
JSP 自定义标签
自定义标签封装可复用的页面逻辑,让 JSP 更简洁,支持 Simple Tag 与 Tag 两种 API。
1. 编写标签处理类(SimpleTag)
package com.example.tag;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
// 简单标签
public class GreetingTag extends SimpleTagSupport {
private String name = "访客";
public void setName(String name) {
this.name = name;
}
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
out.println("欢迎, " + name + "!");
}
}
2. 注册标签(TLD 文件)
<!-- WEB-INF/tags/mytags.tld --%>
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>mytags</short-name>
<uri>http://example.com/mytags</uri>
<tag>
<name>greeting</name>
<tag-class>com.example.tag.GreetingTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>name</name>
<required>false</required>
</attribute>
</tag>
</taglib>
3. 在 JSP 中使用
<%@ taglib uri="http://example.com/mytags" prefix="mt" %>
<mt:greeting/>
<mt:greeting name="张三"/>
带标签体的标签
// 标签处理类
public class WrapTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
getJspContext().getOut().println("<div class='box'>");
getJspBody().invoke(null); // 执行标签体
getJspContext().getOut().println("</div>");
}
}
<!-- TLD: body-content 设为 scriptless --%>
<mt:wrap>
<p>被包裹的内容</p>
</mt:wrap>
自定义标签 vs JSTL
JSTL 覆盖常见需求(循环、条件、格式化)
自定义标签适合:项目特有、高度复用的逻辑
优先考虑 JSTL,不够再用自定义标签
JSP 表达式语言
JSP 表达式语言(EL)
EL(Expression Language)提供简洁的取值语法 ${...},替代繁琐的 Java 脚本。
基本语法
<!-- 取值 --%>
${username}
${user.name}
${user["name"]} <!-- 等价写法 --%>
${list[0]} <!-- 数组/List --%>
${map["key"]} <!-- Map --%>
<!-- 运算符 --%>
${a + b}
${a > b}
${empty list} <!-- 判断空 --%>
${a == null ? "空" : a}
隐式对象
${pageScope.name} 页面作用域
${requestScope.name} 请求作用域
${sessionScope.name} 会话作用域
${applicationScope.name} 应用作用域
${param.name} 请求参数
${paramValues.hobby} 多值参数
${header["User-Agent"]} 请求头
${cookie.username.value} Cookie
${initParam.appName} 初始化参数
${pageContext.request.contextPath} 上下文路径
作用域查找顺序
// 直接用 ${name} 时按顺序查找:
page → request → session → application
// 推荐显式写作用域,避免歧义
${sessionScope.user}
运算符
算术: + - * / %
关系: == != < > <= >=
逻辑: && || !
空值: empty(判断 null 或空集合/空字符串)
三元: ${condition ? "A" : "B"}
字符串: ${"a" += "b"} 或 ${"a" += "b"} 拼接
完整示例
<%@ page contentType="text/html;charset=UTF-8" %>
<%
// 准备数据
request.setAttribute("user", "admin");
request.setAttribute("score", 85);
String[] items = {"Java", "JSP", "EL"};
request.setAttribute("items", items);
%>
<!-- 使用 EL --%>
<p>用户: ${sessionScope.user}</p>
<p>分数: ${score}</p>
<p>是否及格: ${score >= 60 ? "是" : "否"}</p>
<c:forEach var="item" items="${items}">
<span>${item} </span>
</c:forEach>
<!-- 判断空 --%>
<p>${empty items ? "列表为空" : "列表有数据"}</p>
EL 函数
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
${fn:length(list)} 长度
${fn:toUpperCase(name)} 转大写
${fn:contains(name, "a")} 包含判断
${fn:substring(name, 0, 3)} 截取
${fn:trim(name)} 去空格
禁用 EL
<%@ page isELIgnored="true" %> <!-- 本页禁用 --%>
<!-- web.xml 全局禁用 --%>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
JSP 异常处理
JSP 异常处理
JSP 异常处理包括页面内捕获、错误页跳转与 try-catch 结合。
1. try-catch 捕获
<%
try {
int result = 100 / Integer.parseInt(request.getParameter("num"));
out.println("结果: " + result);
} catch (ArithmeticException e) {
out.println("<p style='color:red'>除数不能为零</p>");
} catch (NumberFormatException e) {
out.println("<p style='color:red'>请输入有效数字</p>");
} catch (Exception e) {
out.println("<p style='color:red'>发生错误: " + e.getMessage() + "</p>");
}
%>
2. errorPage 指定错误页
<!-- 普通页面:发生异常时跳转 --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page errorPage="error.jsp" %>
<%
// 故意制造异常
int x = 1 / 0;
%>
<!-- error.jsp:错误页 --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page isErrorPage="true" %>
<html><body>
<h1>出错了!</h1>
<p>错误类型: <%= exception.getClass().getName() %></p>
<p>错误信息: <%= exception.getMessage() %></p>
<a href="index.jsp">返回首页</a>
</body></html>
3. web.xml 全局错误页
<!-- web.xml --%>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/general.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
4. 数据库异常处理
<%@ page errorPage="error.jsp" %>
<%
try (java.sql.Connection conn =
java.sql.DriverManager.getConnection(url, user, pwd)) {
// 数据库操作
} catch (java.sql.SQLException e) {
throw new RuntimeException("数据库操作失败: " + e.getMessage(), e);
}
%>
5. 统一错误处理(过滤器)
// 用 Filter try-catch 包裹所有请求
public void doFilter(req, resp, chain) {
try {
chain.doFilter(req, resp);
} catch (Exception e) {
req.setAttribute("error", e);
req.getRequestDispatcher("/error.jsp").forward(req, resp);
}
}
最佳实践
1. 业务异常捕获处理,系统异常记录日志
2. 生产环境不向用户显示堆栈信息
3. 错误页友好提示 + 返回首页链接
4. 重要操作(转账、写库)务必 try-catch
JSP 调试
JSP 调试技巧
JSP 报错不直观,掌握调试技巧能快速定位问题。
1. 查看完整错误堆栈
<!-- 开发期临时显示堆栈 --%>
<%@ page isErrorPage="true" %>
<pre><%
exception.printStackTrace(new java.io.PrintWriter(out));
%></pre>
2. Tomcat 日志
# 查看 Tomcat 控制台与日志
tomcat/logs/catalina.out # 主日志
tomcat/logs/localhost.YYYY-MM-DD.log # 应用日志
3. 输出调试信息
<%
// 打印变量值
out.println("<!-- DEBUG: name=" + name + " -->");
// 或输出到控制台
System.out.println("DEBUG: name = " + name);
%>
4. 常见错误速查
HTTP 500 + 编译错误:
- 脚本语法错误(缺分号/括号)
- import 缺失
- 类名拼写错误
HTTP 500 + 运行时错误:
- 空指针(NPE)
- 除零
- 数据库连接失败
HTTP 404:
- 文件路径错误
- 未部署
中文乱码:
- 页面 charset 未设置
- 请求/响应编码不一致
5. 分步排查法
1. 先简化:注释掉可疑代码块,缩小范围
2. 加输出:关键变量打印
3. 检查数据:表单值、数据库返回
4. 检查路径:文件、URL、转发路径
5. 查看日志:catalina.out 找异常堆栈
6. 使用 IDE 调试
1. 在 JSP 脚本代码行设置断点
2. 以 Debug 方式启动 Tomcat(Eclipse/IDEA)
3. 访问页面触发断点
4. 检查变量、单步执行
注意:JSP 调试时断点应设在翻译后的 Servlet 源码(更好用)
Eclipse: 在 .java 生成文件上打断点
7. JSP 翻译后的 Servlet
# Tomcat work 目录可查看翻译后的 Java 文件
tomcat/work/Catalina/localhost/项目名/org/apache/jsp/index_jsp.java
# 编译错误行号与它对应,方便定位
JSP 国际化
JSP 国际化(i18n)
国际化让应用根据用户语言环境显示不同语言,常用 JSTL fmt 标签实现。
1. 创建资源文件
# messages.properties(默认英文)
greeting=Hello
welcome=Welcome to our site
button.submit=Submit
# messages_zh_CN.properties(中文)
greeting=你好
welcome=欢迎来到我们的网站
button.submit=提交
# 放置位置: src/messages.properties(编译到 classes)
2. JSP 中使用 fmt 标签
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!-- 指定资源包 --%>
<fmt:setBundle basename="messages" var="bundle"/>
<!-- 输出本地化文本 --%>
<fmt:message key="greeting" bundle="${bundle}"/>
<fmt:message key="welcome" bundle="${bundle}"/>
<!-- 带参数的消息 --%>
<fmt:message key="greeting" bundle="${bundle}">
<fmt:param value="张三"/>
</fmt:message>
<!-- messages.properties: greeting=Hello {0} --%>
3. 根据浏览器语言自动切换
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%
// 获取浏览器语言
String lang = request.getLocale().getLanguage();
session.setAttribute("lang", lang);
%>
<fmt:setLocale value="${sessionScope.lang}"/>
<fmt:setBundle basename="messages" var="bundle"/>
<h1><fmt:message key="welcome" bundle="${bundle}"/></h1>
4. 手动切换语言
<!-- 语言切换链接 --%>
<a href="?lang=zh_CN">中文</a> |
<a href="?lang=en">English</a>
<%
String lang = request.getParameter("lang");
if (lang != null) {
session.setAttribute("lang", lang);
}
if (session.getAttribute("lang") == null) {
session.setAttribute("lang", "zh_CN");
}
%>
<fmt:setLocale value="${sessionScope.lang}"/>
<fmt:setBundle basename="messages" var="bundle"/>
5. 日期与数字本地化
<!-- 日期格式随语言 --%>
<fmt:formatDate value="${now}" dateStyle="long"/>
<!-- 货币 --%>
<fmt:setLocale value="zh_CN"/>
<fmt:formatNumber value="1234.56" type="currency"/> <!-- ¥1,234.56 --%>
<!-- 数字 --%>
<fmt:formatNumber value="0.85" type="percent"/> <!-- 85% --%>
6. 资源文件命名规则
messages.properties 默认(英语)
messages_en.properties 英语
messages_zh_CN.properties 简体中文
messages_zh_TW.properties 繁体中文
messages_ja.properties 日语
注意事项
1. properties 文件用 UTF-8 保存或转 Unicode 转义
2. 所有面向用户的文本都应放入资源文件
3. 日期、货币、数字格式不要硬编码