你真的理解JSP吗?
说实话,很多人一听到JSP就皱眉——”都什么年代了还用这个”、”太老了别学了”。但我想告诉你一个真相:JSP并不是那种”学了没用”的技术。相反,它是理解Java服务器端编程的一块关键基石。
想象一下,你以后要维护一个老系统(这种系统在国内的企业里多如牛毛),或者你在面试时被问到”JSP的工作原理是什么”,又或者你只是单纯想了解Java Web的发展脉络——不懂JSP,你的Java知识栈就是残缺的。
今天我就把JSP从基础语法到运行机制,再到各种坑和排查方法,一点一点给你讲清楚。我会用很多例子,保证你看完之后能自己动手写出正确的JSP页面,也能排查常见的错误。
第一部分:JSP到底是什么?
1.1 一句话理解JSP
JSP(JavaServer Pages)是一种让Java代码和HTML混写在一起的服务器端技术。
这听起来很简单,但这里面有个关键问题:浏览器只认识HTML,不认识Java。那JSP是怎么工作的呢?
1.2 JSP的工作流程
让我用一个具体的例子来说明:
假设你有一个JSP文件叫 hello.jsp,内容如下:
<%-- 这是一个JSP文件 --%>
<html>
<head><title>你好</title></head>
<body>
<h1><%= "Hello, " + request.getParameter("name") %></h1>
</body>
</html>
当你在浏览器访问 http://localhost:8080/hello.jsp?name=张三 时,服务器会经历以下几个步骤:
第一步:接收请求
- Tomcat服务器收到你的请求
第二步:翻译(Translation)
- Tomcat把
hello.jsp翻译成Java源文件hello_jsp.java - 这个Java文件是一个Servlet类
第三步:编译(Compilation)
- 把
hello_jsp.java编译成字节码hello_jsp.class - 这个.class文件就是一个标准的Servlet
第四步:执行(Execution)
- 加载并执行这个Servlet类
- 生成HTML响应
第五步:返回响应
- 把生成的HTML发送给浏览器
你可以用一个类比来理解这个过程:JSP就像是一个”模板”,服务器在运行时把这个模板和Java代码”烘焙”成一个真正的HTML页面。
1.3 JSP和Servlet的关系
很多人分不清JSP和Servlet,其实它们的关系非常简单:
JSP = HTML + Java代码(简化版Servlet)
Servlet = Java代码 + 输出HTML(完整版Servlet)
JSP本质上就是一个Servlet,只是写法不同。
举个例子,下面这两个文件是等价的:
JSP写法(hello.jsp):
<html>
<body>
<h1><%= request.getParameter("name") %></h1>
</body>
</html>
Servlet写法(HelloServlet.java):
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + request.getParameter("name") + "</h1>");
out.println("</body></html>");
}
}
看到了吗? JSP的写法简洁得多,尤其是当HTML内容很多的时候。这就是JSP存在的意义。
第二部分:JSP的基础语法
JSP的语法可以分为几大类,我会一一讲解。
2.1 JSP的三种基本元素
JSP页面由三种基本元素组成:
- HTML标记:普通的HTML代码,原样输出
- JSP指令:以
<%@开头,告诉服务器如何处理这个JSP - JSP动作:以
<jsp:开头,执行特定的服务器端操作
但最常用的还是下面这些”脚本元素”:
2.2 表达式(Expression)
表达式用于在JSP页面中输出一个Java表达式的值。
语法:
<%= 表达式 %>
注意:
- 不需要分号
- 表达式会被自动转换为字符串并输出
例子:
<h1>当前时间是:<%= new java.util.Date() %></h1>
<h2>用户名字段:<%= request.getParameter("username") %></h2>
<h3>计算结果:<%= 100 + 200 %></h3>
常见错误:很多人会写成这样:
<%= 100 + 200; %> <!-- 错误!不能加分号 -->
这是错的,表达式后面不能加分号。
2.3 脚本片段(Scriptlet)
脚本片段用于在JSP页面中执行Java代码。
语法:
<% Java代码 %>
注意:
- 需要分号
- 可以写多行代码
- 可以定义变量、循环、判断等
例子:
<%
String name = request.getParameter("name");
if (name == null || name.isEmpty()) {
out.println("请先输入名字");
} else {
out.println("你好," + name);
}
// 循环输出1到10
for (int i = 1; i <= 10; i++) {
out.println("<p>第" + i + "行</p>");
}
%>
常见错误:
<%
String name = request.getParameter("name") <!-- 错误!少了分号 -->
%>
2.4 声明(Declaration)
声明用于在JSP页面中定义变量或方法。
语法:
<%! 变量或方法声明 %>
注意:
- 声明的内容会成为Servlet类的成员变量或成员方法
- 可以加分号,也可以不加(和表达式不同)
例子:
<%!
private int counter = 0;
private String appName = "我的JSP应用";
public String getVersion() {
return "1.0.0";
}
%>
<html>
<body>
<h1>应用名称:<%= appName %></h1>
<h2>版本:<%= getVersion() %></h2>
<p>计数器:<%= ++counter %></p>
</body>
</html>
重要提示:声明中的变量是全局的,所有用户共享。这意味着如果你的计数器在多个用户之间共享,可能会出现并发问题。在实际项目中,尽量避免在JSP中使用全局变量。
2.5 注释(Comment)
JSP有两种注释:
第一种:JSP注释(不输出到客户端)
<%-- 这是JSP注释,不会出现在HTML源码中 --%>
第二种:HTML注释(会输出到客户端)
<!-- 这是HTML注释,会出现在HTML源码中 -->
区别:
- JSP注释的内容不会被翻译成Java代码,更安全
- HTML注释会出现在浏览器查看源码中,可能有安全风险
例子:
<%-- 这里是敏感信息,不会发送给客户端 --%>
<%
String password = "123456";
%>
<!-- 下面是普通注释,会显示在HTML源码中 -->
<p>用户名:<%= username %></p>
第三部分:JSP九大隐式对象
JSP提供了9个不需要声明就能直接使用的对象,这叫”隐式对象”。记住它们的名字和用途,是掌握JSP的关键。
3.1 九大对象一览
| 对象名 | 类型 | 用途 |
|---|---|---|
request |
HttpServletRequest | 获取请求参数 |
response |
HttpServletResponse | 设置响应 |
out |
JspWriter | 输出内容 |
session |
HttpSession | 会话管理 |
application |
ServletContext | 应用级别数据 |
config |
ServletConfig | Servlet配置 |
pageContext |
PageContext | 页面上下文 |
page |
Object | 当前Servlet实例 |
exception |
Throwable | 异常对象(仅在错误页面可用) |
3.2 request对象详解
request对象用于获取客户端发送的请求信息。
最常用的方法:
<%-- 获取请求参数 --%>
<%= request.getParameter("username") %>
<%-- 获取所有参数名 --%>
<%
java.util.Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
out.println(name + " = " + request.getParameter(name));
}
%>
<%-- 获取请求方法 --%>
<%= request.getMethod() %>
<%-- 获取请求URL --%>
<%= request.getRequestURL() %>
<%-- 获取请求头 --%>
<%= request.getHeader("User-Agent") %>
实战例子:用户登录表单
<%-- login.jsp --%>
<%
String msg = "";
String username = "";
String password = "";
// 处理POST请求
if ("POST".equals(request.getMethod())) {
username = request.getParameter("username");
password = request.getParameter("password");
// 简单验证
if ("admin".equals(username) && "123456".equals(password)) {
session.setAttribute("user", username);
response.sendRedirect("success.jsp");
return;
} else {
msg = "用户名或密码错误!";
}
}
%>
<html>
<head><title>登录</title></head>
<body>
<h2>用户登录</h2>
<p style="color:red;"><%= msg %></p>
<form method="post" action="login.jsp">
<p>用户名:<input type="text" name="username" value="<%= username %>"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
</body>
</html>
3.3 response对象详解
response对象用于设置响应信息。
最常用的方法:
<%-- 设置响应类型 --%>
<%
response.setContentType("text/html;charset=UTF-8");
%>
<%-- 设置响应头 --%>
<%
response.setHeader("Cache-Control", "no-cache");
%>
<%-- 重定向 --%>
<%
response.sendRedirect("index.jsp");
%>
<%-- 设置Cookie --%>
<%
Cookie cookie = new Cookie("username", "张三");
cookie.setMaxAge(3600); // 1小时
response.addCookie(cookie);
%>
实战例子:防止页面缓存
<%-- 这个页面不会被浏览器缓存 --%>
<%
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
%>
<html>
<body>
<h1>实时数据页面</h1>
<p>当前时间:<%= new java.util.Date() %></p>
</body>
</html>
3.4 out对象详解
out对象用于向客户端输出内容。
最常用的方法:
<%-- 输出普通文本 --%>
<% out.println("Hello World"); %>
<%-- 输出HTML标签 --%>
<% out.println("<h1>标题</h1>"); %>
<%-- 输出表达式(也可以用<%= %>) --%>
<% out.print("当前用户:" + request.getParameter("name")); %>
注意:out.println() 和 <%= %> 的区别:
out.println()是Java方法调用,需要分号<%= %>是JSP表达式,不需要分号
3.5 session对象详解
session对象用于跟踪用户会话。
最常用的方法:
<%-- 设置会话属性 --%>
<%
session.setAttribute("username", "张三");
session.setAttribute("loginTime", new java.util.Date());
%>
<%-- 获取会话属性 --%>
<%= session.getAttribute("username") %>
<%-- 获取会话ID --%>
<%= session.getId() %>
<%-- 销毁会话 --%>
<%
session.invalidate();
%>
实战例子:用户登录状态保持
<%-- main.jsp --%>
<%
// 检查是否登录
String username = (String) session.getAttribute("username");
if (username == null) {
response.sendRedirect("login.jsp");
return;
}
%>
<html>
<head><title>主页面</title></head>
<body>
<h1>欢迎,<%= username %></h1>
<p>会话ID:<%= session.getId() %></p>
<p>登录时间:<%= session.getAttribute("loginTime") %></p>
<a href="logout.jsp">退出登录</a>
</body>
</html>
<%-- logout.jsp --%>
<%
session.invalidate();
response.sendRedirect("login.jsp");
%>
3.6 application对象详解
application对象用于在整个应用程序范围内共享数据。
最常用的方法:
<%-- 设置应用属性 --%>
<%
application.setAttribute("appName", "我的JSP应用");
%>
<%-- 获取应用属性 --%>
<%= application.getInitParameter("appName") %>
<%-- 获取ServletContext --%>
<%= application.getContextPath() %>
实战例子:网站计数器
<%-- counter.jsp --%>
<%
// 获取计数器
Integer count = (Integer) application.getAttribute("visitCount");
if (count == null) {
count = 0;
}
count++;
application.setAttribute("visitCount", count);
%>
<html>
<body>
<h1>欢迎访问我的网站</h1>
<p>总访问量:<%= count %> 次</p>
</body>
</html>
注意:这个计数器在服务器重启后会重置,因为数据存储在内存中。如果需要持久化,应该使用数据库。
第四部分:JSP指令和动作
4.1 page指令
page指令用于设置JSP页面的属性。
常用属性:
<%-- 设置内容类型和编码 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 导入包 --%>
<%@ page import="java.util.*, java.io.*" %>
<%-- 指定错误页面 --%>
<%@ page errorPage="error.jsp" %>
<%-- 声明这是错误页面 --%>
<%@ page isErrorPage="true" %>
<%-- 禁用会话 --%>
<%@ page session="false" %>
<%-- 设置缓冲大小 --%>
<%@ page buffer="8kb" %>
<%-- 设置异步支持 --%>
<%@ page asyncSupported="true" %>
实战例子:完整的page指令
<%@ page contentType="text/html;charset=UTF-8"
language="java"
import="java.util.*, java.io.*"
errorPage="error.jsp"
session="true"
buffer="8kb" %>
4.2 include指令
include指令用于在JSP页面中包含另一个文件。
语法:
<%@ include file="文件路径" %>
注意:include指令是静态包含,在翻译阶段就把内容合并到当前JSP中。
例子:
<%-- header.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head><title>我的网站</title></head>
<body>
<header>
<h1>网站标题</h1>
<nav>
<a href="index.jsp">首页</a>
<a href="about.jsp">关于</a>
<a href="contact.jsp">联系</a>
</nav>
</header>
<hr>
<%-- index.jsp --%>
<%@ include file="header.jsp" %>
<main>
<h2>欢迎首页</h2>
<p>这里是首页内容</p>
</main>
<%@ include file="footer.jsp" %>
include指令 vs jsp:include动作的区别:
| 特性 | include指令 | jsp:include动作 |
|---|---|---|
| 包含时机 | 翻译阶段(静态) | 请求阶段(动态) |
| 性能 | 较快 | 较慢 |
| 灵活性 | 低 | 高 |
| 参数传递 | 不支持 | 支持 |
4.3 taglib指令
taglib指令用于引入自定义标签库。
语法:
<%@ taglib uri="标签库URI" prefix="前缀" %>
例子(引入JSTL标签库): “`jsp <%@ taglib
