零基础学习JSP服务器端脚本语言的基础语法和运行机制从静态页面到动态网站的开发实战教程
嘿,朋友!看到”JSP”这三个字母,是不是感觉有点眼熟但又不知道从哪里下手?别担心,咱们今天就把这个老当益壮的技术掰开揉碎了讲清楚。你可能会问:现在都是Spring Boot、Vue、React的天下了,谁还学JSP啊?
说实话,我也这么想过。但当你真正去理解Java Web是怎么一步步发展起来的,去读懂那些古老但依然在生产环境运行的系统时,你会发现JSP就像是一个”活化石”——它藏着Java Web世界的底层逻辑。而且,很多公司的老项目、政府系统、传统企业后台,还在用JSP呢!
更重要的是,学会了JSP,你再去看Spring MVC、Thymeleaf这些现代技术,会发现它们的灵魂其实一脉相承。JSP是理解Java服务端渲染的”钥匙”。
好,咱们不废话了,直接开干!
先搞明白:JSP到底是什么玩意儿?
想象一下这个场景:你打开一个网站,点了一个”登录”按钮,网站给你返回了一个好看的页面。这个页面是”动态”的——你登录了显示”欢迎回来,张三”,没登录就显示”请登录”,数据每天都在变。
问题来了:HTML本身是静态的,它不会自己变。那怎么让网页”动起来”呢?
JSP(JavaServer Pages)的答案是:在HTML里嵌入Java代码。
对,就是这么简单粗暴!服务器接收到你的请求,遇到JSP文件,就把里面的Java代码先执行掉,生成一堆HTML,然后发给你的浏览器。所以你看到的永远是处理好的HTML,而不是JSP本身。
浏览器请求JSP页面
↓
服务器接收请求,找到对应的JSP文件
↓
JSP引擎把JSP翻译成Java Servlet代码
↓
执行Java代码,生成动态HTML内容
↓
把HTML返回给浏览器,你看到页面
这个过程叫“服务器端渲染”。你的浏览器只负责展示,所有逻辑都在服务器那端跑完了。
搭建开发环境:让JSP跑起来
在学习语法之前,你得先有一个能跑JSP的环境。最常用的组合是:Tomcat + Eclipse(或IDEA)+ JDK。
第一步:安装JDK
去Oracle官网或者OpenJDK的仓库下载JDK 8或更高版本。安装完之后,打开命令行(Windows叫CMD或PowerShell,Mac/Linux叫Terminal),输入:
java -version
如果能显示版本信息,说明JDK安装成功了。
第二步:下载并启动Tomcat
Tomcat是Apache出的一个免费的Web服务器,专门用来跑Java Web应用。去Apache Tomcat官网下载一个Tomcat 9或Tomcat 10。
下载完解压后,找到bin目录,里面有启动脚本:
- Windows下双击
startup.bat - Mac/Linux下终端运行
./startup.sh
启动成功后,浏览器打开http://localhost:8080,看到Tomcat的默认页面,就说明服务器跑起来了!
第三步:用IDE创建JSP项目
用Eclipse或者IntelliJ IDEA都可以。以Eclipse为例:
- 新建一个”Dynamic Web Project”
- 项目名随便取,比如
JSPDemo - 在
WebContent(或webapp)目录下新建一个文件夹WEB-INF - 在
WEB-INF里新建一个web.xml文件(这是Web应用的配置文件) - 在
WebContent下新建一个index.jsp文件
web.xml的基本配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
然后在index.jsp里写下你的第一行JSP代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>我的第一个JSP页面</title>
</head>
<body>
<h1>Hello, JSP!</h1>
<%
// 这是Java代码嵌入在JSP里
int year = 2024;
out.println("<p>现在是" + year + "年,JSP真有趣!</p>");
%>
</body>
</html>
把这个项目部署到Tomcat,启动服务器,浏览器访问http://localhost:8080/JSPDemo,你会看到:
Hello, JSP!
现在是2024年,JSP真有趣!
恭喜你!你已经迈出了Java Web开发的第一步!
JSP的基础语法:五大核心元素
JSP的语法其实就五个东西,搞懂了这五个,你就掌握了JSP的”半壁江山”。
一、页面指令(Directive)
页面指令用来告诉JSP引擎这个页面的一些”身份信息”和”属性设置”。语法格式是<%@ 指令名 属性="值" %>。
最常见的三个指令:
1. page指令——设置页面属性
<%@ page contentType="text/html;charset=UTF-8"
language="java"
import="java.util.*"
pageEncoding="UTF-8" %>
contentType:告诉浏览器返回什么类型的内容,charset=UTF-8防止乱码import:导入Java包,就像在Java里写import java.util.*;一样pageEncoding:JSP文件本身的编码
2. include指令——静态包含其他文件
<%@ include file="header.jsp" %>
这行代码会把header.jsp的内容原封不动地复制到当前页面,在编译阶段就合并了。适合放页眉、页脚这些所有页面都有的公共部分。
3. taglib指令——引入标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
JSTL(JSP Standard Tag Library)是一组标准的标签,能替代很多繁琐的Java代码。后面的实战部分会详细讲。
二、声明(Declaration)
声明用来定义JSP页面中的变量和方法。语法是<%! ... %>。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSP声明示例</title>
</head>
<body>
<%!
// 声明一个成员变量
private String siteName = "Java编程乐园";
// 声明一个成员方法
public String getGreeting(String name) {
return "欢迎来到" + name;
}
%>
<h1><%= siteName %></h1>
<p><%= getGreeting("张三") %></p>
</body>
</html>
注意:声明和表达式是不一样的!
<%! ... %>是声明,定义变量和方法<%= ... %>是表达式,直接输出值(后面会讲)
三、脚本let(Scriptlet)
脚本let是在JSP中嵌入Java代码最直接的方式,语法是<% ... %>。里面的代码会被直接翻译成Servlet的_jspService()方法里的内容。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.Date" %>
<html>
<head>
<title>脚本let示例</title>
</head>
<body>
<h2>当前时间</h2>
<%
Date now = new Date();
out.println("<p>现在的日期时间是:" + now.toString() + "</p>");
int a = 100;
int b = 200;
out.println("<p>" + a + " + " + b + " = " + (a + b) + "</p>");
if (a > b) {
out.println("<p>a大于b</p>");
} else {
out.println("<p>b大于a</p>");
}
%>
</body>
</html>
重要提醒:在脚本let里写的代码,必须遵循Java语法规范!比如每句话末尾要加分号,变量要先声明再使用。
四、表达式(Expression)
表达式用来直接把Java变量的值输出到页面,语法是<%= ... %>。它后面不能加分号!
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>表达式示例</title>
</head>
<body>
<%
String username = "李四";
int age = 25;
%>
<p>用户名:<%= username %></p>
<p>年龄:<%= age %></p>
<p>问候语:<%= "你好," + username + ",今年" + age + "岁了!" %></p>
</body>
</html>
输出到浏览器的结果是:
用户名:李四
年龄:25
问候语:你好,李四,今年25岁了!
五、注释(Comment)
JSP里有两种注释,区别很大,一定要分清!
1. HTML注释——客户端可见
<!-- 这是一条HTML注释,浏览器查看源代码时能看到 -->
这种注释会被发送到客户端,用户在浏览器里”查看源代码”就能看到。
2. JSP注释——客户端不可见
<%-- 这是一条JSP注释,服务器处理时直接忽略,客户端看不到 --%>
这种注释只在服务器端存在,最终生成的HTML里完全没有这段内容。安全敏感的信息要用这种注释。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>注释示例</title>
</head>
<body>
<!-- 这个注释用户看得到 -->
<p>这是可见的段落</p>
<%-- 这个注释用户看不到,是服务器端的内部备注 --%>
<%
// Java单行注释,在翻译成的Servlet里会保留
String secret = "admin123"; // 密码不应该暴露
%>
</body>
</html>
九大隐式对象:JSP的”内置武器库”
JSP最厉害的地方之一,就是它自动给你准备好了九个常用的对象,你不用new,不用声明,直接在JSP里用就行!这九个对象叫隐式对象(Implicit Objects)。
| 对象名 | 类型 | 作用 |
|---|---|---|
request |
HttpServletRequest | 获取客户端请求的信息 |
response |
HttpServletResponse | 向客户端发送响应 |
out |
JspWriter | 向页面输出内容 |
session |
HttpSession | 保存用户会话信息 |
application |
ServletContext | 应用级别的数据共享 |
config |
ServletConfig | 获取Servlet配置信息 |
pageContext |
PageContext | 页面上下文,管理其他隐式对象 |
page |
Object | 指向当前JSP页面本身(相当于this) |
exception |
Throwable | 异常对象,只在错误页面可用 |
咱们重点讲讲最常用的几个。
request对象——读取用户发来的请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>request对象示例</title>
</head>
<body>
<h2>获取请求参数</h2>
<%
// 获取请求参数
String name = request.getParameter("name");
String email = request.getParameter("email");
// 获取请求头
String userAgent = request.getHeader("User-Agent");
// 获取请求的URL
String servletPath = request.getServletPath();
out.println("<p>你的名字是:" + name + "</p>");
out.println("<p>你的邮箱是:" + email + "</p>");
out.println("<p>浏览器类型:" + userAgent + "</p>");
out.println("<p>访问路径:" + servletPath + "</p>");
%>
</body>
</html>
配合一个HTML表单使用:
<!-- login.html -->
<form action="process.jsp" method="POST">
姓名:<input type="text" name="name"><br>
邮箱:<input type="text" name="email"><br>
<input type="submit" value="提交">
</form>
当用户填完表单点提交,process.jsp就能通过request.getParameter()拿到用户填的内容。
session对象——记住”你是谁”
这是Web开发里超级重要的概念!HTTP协议本身是无状态的,服务器不知道两次请求是不是同一个人发的。session就是为了解决这个问题而生的。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>session对象示例</title>
</head>
<body>
<%
// 设置session属性
session.setAttribute("username", "王五");
session.setAttribute("userId", 1001);
session.setAttribute("isLoggedIn", true);
// 读取session属性
String username = (String) session.getAttribute("username");
Integer userId = (Integer) session.getAttribute("userId");
Boolean isLoggedIn = (Boolean) session.getAttribute("isLoggedIn");
out.println("<p>用户名:" + username + "</p>");
out.println("<p>用户ID:" + userId + "</p>");
out.println("<p>登录状态:" + isLoggedIn + "</p>");
// 获取session ID(每个用户的session是唯一的)
out.println("<p>Session ID:" + session.getId() + "</p>");
%>
<br>
<a href="destroy_session.jsp">销毁session</a>
</body>
</html>
<%-- destroy_session.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 销毁session
session.invalidate();
response.sendRedirect("index.jsp");
%>
实际场景:用户登录后,把用户信息存进session,之后无论用户访问哪个页面,都能通过session拿到用户信息,实现”记住登录状态”的功能。
application对象——所有用户共享的数据
application和session不一样,session是”你一个人”的,application是”所有人共用”的。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>application对象示例</title>
</head>
<body>
<%
// 应用级计数器——记录网站的总访问量
Integer visitCount = (Integer) application.getAttribute("visitCount");
if (visitCount == null) {
visitCount = 0;
}
visitCount++;
application.setAttribute("visitCount", visitCount);
out.println("<p>本网站总访问量:" + visitCount + " 次</p>");
out.println("<p>你的Session ID:" + session.getId() + "</p>");
%>
</body>
</html>
这个计数器所有访问者共享——A用户刷新页面,计数加1,B用户打开页面,计数也会是加1之后的值。
out对象——向页面输出
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>out对象示例</title>
</head>
<body>
<%
out.println("<p>第一行</p>");
out.println("<p>第二行</p>");
// out还有缓冲功能
out.write("这是write方法输出的内容<br>");
// 清空缓冲区但不关闭
out.clear();
out.println("缓冲区已清空,这是新内容<br>");
// 刷新缓冲区
out.flush();
%>
</body>
</html>
小技巧:
out.println()和<%= %>本质上是一回事,<%= value %>最终会被翻译成out.print(value)。但你不能给<%= %>加分号,而out.println()是Java代码,要加分号。
JSP的运行机制:从JSP到Servlet的”变身”过程
很多初学者只知道”JSP能嵌Java代码”,但不知道服务器背后到底发生了什么。搞懂了运行机制,你才能真正做到”知其然,更知其所以然”。
七步变身法
当你的浏览器请求一个JSP页面时,服务器会经历以下过程:
第1步:浏览器发送HTTP请求(比如GET /index.jsp)
↓
第2步:Tomcat收到请求,找到对应的JSP文件
↓
第3步:JSP引擎(Jasper)检查这个JSP是否已经被编译过
如果没有,或者JSP文件被修改过,就重新编译
↓
第4步:把JSP文件翻译成一个Java源文件(Servlet)
比如index.jsp → _index_jsp.java
↓
第5步:编译Java源文件,生成.class字节码文件
比如_index_jsp.java → _index_jsp.class
↓
第6步:加载.class文件,实例化Servlet对象
↓
第7步:调用Servlet的service()方法处理请求,生成HTML响应
把HTML发送回浏览器
看看翻译后的代码长什么样
假设你有这样一个简单的JSP:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>示例</title></head>
<body>
<%
String msg = "Hello JSP";
out.println(msg);
%>
</body>
</html>
Tomcat翻译成的Java Servlet大致长这样(简化版):
public final class _index_jsp extends HttpJspBase {
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
// 九大隐式对象被初始化
javax.servlet.jsp.PageContext pageContext = ...;
javax.servlet.http.HttpSession session = pageContext.getSession();
javax.servlet.ServletContext application = pageContext.getServletContext();
javax.servlet.ServletConfig config = pageContext.getServletConfig();
javax.servlet.jsp.OUT out = pageContext.getOut();
// ... 其他隐式对象
out.write("<html>\r\n<head><title>示例</title></head>\r\n<body>\r\n");
String msg = "Hello JSP";
out.println(msg);
out.write("\r\n</body>\r\n</html>");
}
}
看到了吗?JSP里的每一行内容,最终都被翻译成Servlet里的out.write()调用。这就是为什么JSP本质上就是一个特殊的Servlet。
首次访问慢,后续访问快
理解了编译过程,你就能明白一个现象:第一次访问JSP页面比较慢,后续访问就快了。
因为第一次访问时,服务器要经历”翻译→编译→加载”三个步骤,这要花点时间。但编译出来的.class文件会缓存起来,后续请求直接复用,速度就快了。
JSP标准动作标签:不用写Java代码也能干活
前面说的都是<% %>里嵌Java代码,但这有个问题:HTML和Java代码混在一起,维护起来很痛苦。如果团队里有专门做页面的前端,让他写Java代码就很别扭。
所以JSP提供了一组标准动作标签,这些标签看起来像HTML,但能完成一些常用的功能,不用写Java代码。
常用动作标签一览
<%-- 1. jsp:include -- 动态包含(注意和<%@ include %>的区别)--%>
<jsp:include page="header.jsp" />
<%-- 2. jsp:forward -- 请求转发 --%>
<jsp:forward page="target.jsp" />
<%-- 3. jsp:param -- 传递参数(配合include和forward使用)--%>
<jsp:forward page="target.jsp">
<jsp:param name="userId" value="1001" />
</jsp:forward>
<%-- 4. jsp:useBean -- 创建或获取JavaBean对象 --%>
<jsp:useBean id="user" class="com.example.User" scope="page" />
<%-- 5. jsp:setProperty -- 给JavaBean的属性赋值 --%>
<jsp:setProperty name="user" property="name" value="张三" />
<%-- 6. jsp:getProperty -- 获取JavaBean的属性值 --%>
<jsp:getProperty name="user" property="name" />
<%-- 7. jsp:plugin -- 在页面中插入Java插件(Flash等,现在已经很少用了)--%>
include vs jsp:include 的区别(面试常考!)
<%-- 静态包含:编译时就合并,速度快,但不能动态传参 --%>
<%@ include file="header.jsp" %>
<%-- 动态包含:运行时才合并,可以传参,更灵活 --%>
<jsp:include page="header.jsp">
<jsp:param name="title" value="动态标题" />
</jsp:include>
- 静态包含(
<%@ include %>):在翻译阶段就把两个文件合并成一个,相当于复制粘贴。速度快,但不能传参,被包含的文件修改后需要重新编译。 - 动态包含(
<jsp:include>):在运行阶段才去调用被包含的文件,可以传参,被包含文件修改后下次请求自动生效。
useBean的经典用法
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.example.User" %>
<html>
<head><title>useBean示例</title></head>
<body>
<%-- 创建或获取一个User对象 --%>
<jsp:useBean id="user" class="com.example.User" scope="session" />
<%-- 给属性赋值 --%>
<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>
<%-- 相当于上面那段代码,用Java直接写是这样的:
User user = (User) session.getAttribute("user");
if (user == null) {
user = new User();
session.setAttribute("user", user);
}
user.setName("张三");
user.setAge(28);
%>
</body>
</html>
对应的JavaBean:
package com.example;
public class User {
private String name;
private int age;
// 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; }
}
JSTL:让JSP告别满屏的Java代码
前面提到了JSTL,这是JSP开发里至关重要的一块。JSTL(JSP Standard Tag Library)提供了一套标准的标签,把最常用的功能——循环、条件判断、URL处理、数据库访问——都用标签的形式实现了。
为什么要用JSTL?
想象一下,如果你在JSP里要遍历一个列表,没有JSTL的话:
<%
List<String> fruits = (List<String>) request.getAttribute("fruits");
if (fruits != null) {
for (String fruit : fruits) {
out.println("<li>" + fruit + "</li>");
}
}
%>
有了JSTL:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<ul>
<c:forEach items="${fruits}" var="fruit">
<li>${fruit}</li>
</c:forEach>
</ul>
是不是清爽太多了? 这就是JSTL的价值——让JSP页面保持”页面”的样子,而不是变成Java代码的乱炖。
导入JSTL
在Maven项目里,加依赖:
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
非Maven项目,下载jstl.jar和standard.jar放到WEB-INF/lib目录下。
然后在JSP文件头部加:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
JSTL的核心标签
条件判断:c:if 和 c:choose
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<%-- c:if --%>
<c:if test="${user.age >= 18}">
<p>你已经成年了</p>
</c:if>
<%-- c:choose:相当于Java的if-else if-else --%>
<c:choose>
<c:when test="${user.age < 18}">
<p>你是未成年人</p>
</c:when>
<c:when test="${user.age < 60}">
<p>你是成年人</p>
</c:when>
<c:otherwise>
<p>你是老年人</p>
</c:otherwise>
</c:choose>
</body>
</html>
循环遍历:c:forEach 和 c:forTokens
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<%-- 遍历集合 --%>
<h3>水果列表</h3>
<ul>
<c:forEach items="${fruitList}" var="fruit" varStatus="status">
<li>${status.count}. ${fruit}</li>
</c:forEach>
</ul>
<%-- 遍历字符串(按分隔符) --%>
<c:forTokens items="苹果,香蕉,橘子,葡萄" delims="," var="fruit">
<p>水果:${fruit}</p>
</c:forTokens>
<%-- 带范围的循环 --%>
<c:forEach var="i" begin="1" end="10">
${i}
</c:forEach>
<!-- 输出:1 2 3 4 5 6 7 8 9 10 -->
</body>
</html>
URL处理:c:url 和 c:redirect
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<%-- 生成带 session id 的URL(自动处理URL重写)--%>
<a href="<c:url value='/product/detail.jsp'>
<c:param name="id" value="123"/>
</c:url>">查看详情</a>
<%-- 重定向 --%>
<c:redirect url="https://www.example.com"/>
</body>
</html>
格式化:fmt标签
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<body>
<%-- 日期格式化 --%>
<fmt:formatDate value="${now}" pattern="yyyy年MM月dd日 HH:mm:ss"/>
<%-- 数字格式化 --%>
<fmt:formatNumber value="${price}" pattern="¥#,##0.00"/>
<%-- 国际化 --%>
<fmt:setLocale value="zh_CN"/>
<fmt:bundle basename="messages">
<fmt:message key="welcome"/>
</fmt:bundle>
</body>
</html>
EL表达式:简洁的数据访问方式
EL(Expression Language,表达式语言)是JSP 2.0引入的,专门用来简化在JSP页面中访问数据。语法是${表达式}。
为什么需要EL?
没有EL之前,你要在JSP里显示一个变量:
<%
String name = (String) request.getAttribute("name");
out.println(name);
%>
有了EL:
${name}
一行搞定!
EL的基本语法
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<%
// 在Servlet或JSP里设置各种作用域的属性
request.setAttribute("reqName", "请求作用域");
session.setAttribute("sessionName", "会话作用域");
application.setAttribute("appName", "应用作用域");
// 设置一个对象
User user = new User();
user.setName("张三");
user.setAge(25);
request.setAttribute("user", user);
// 设置列表
List<String> fruits = new ArrayList<>();
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("橘子");
request.setAttribute("fruits", fruits);
%>
<h3>EL表达式示例</h3>
<%-- 访问简单属性 --%>
<p>请求作用域:${reqName}</p>
<p>会话作用域:${sessionName}</p>
<p>应用作用域:${appName}</p>
<%-- 访问对象属性(自动调用getter方法)--%>
<p>用户姓名:${user.name}</p>
<p>用户年龄:${user.age}</p>
<%-- 访问集合元素 --%>
<p>第一个水果:${fruits[0]}</p>
<%-- 访问Map --%>
<%
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
request.setAttribute("map", map);
%>
<p>Map值:${map.key1}</p>
<%-- 条件运算 --%>
<p>是否成年:${user.age >= 18 ? '是' : '否'}</p>
<%-- 空值判断 --%>
<c:if test="${empty user.name}">
<p>用户名为空</p>
</c:if>
</body>
</html>
EL的搜索范围(从左到右依次查找)
pageScope → requestScope → sessionScope → applicationScope
当你写${name}时,JSP会按照上面的顺序,在四个作用域里依次查找名为name的属性,找到就返回,找不到就返回空字符串。
你也可以指定范围:
${requestScope.name} <!-- 只在request中查找 -->
${sessionScope.name} <!-- 只在session中查找 -->
${applicationScope.name} <!-- 只在application中查找 -->
实战项目:从零搭建一个用户登录系统
光说不练假把式!下面咱们一起来做一个完整的用户登录系统,把前面学到的知识全部串起来。
项目结构
LoginSystem/
├── WebContent/
│ ├── WEB-INF/
│ │ ├── web.xml
│ │ └── classes/
│ ├── login.jsp <!-- 登录页面 -->
│ ├── login_process.jsp <!-- 处理登录 -->
│ ├── index.jsp <!-- 主页 -->
│ ├── logout.jsp <!-- 注销 -->
│ └── success.jsp <!-- 登录成功页面 -->
└── src/
└── com/
└── example/
├── User.java <!-- 用户实体类 -->
├── UserService.java <!-- 用户服务类 -->
└── LoginServlet.java <!-- 登录Servlet -->
第一步:创建User实体类
package com.example;
public class User {
private String username;
private String password;
private String email;
public User() {}
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
// getter和setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
第二步:创建UserService(模拟数据库)
package com.example;
import java.util.*;
public class UserService {
// 模拟数据库中的用户数据
private static Map<String, User> users = new HashMap<>();
static {
// 初始化几个测试用户
users.put("admin", new User("admin", "123456", "admin@example.com"));
users.put("zhangsan", new User("zhangsan", "abc123", "zhangsan@example.com"));
users.put("lisi", new User("lisi", "xyz789", "lisi@example.com"));
}
/**
* 验证用户名密码是否正确
*/
public User validateUser(String username, String password) {
User user = users.get(username);
if (user != null && user.getPassword().equals(password)) {
return user;
}
return null;
}
/**
* 根据用户名获取用户
*/
public User getUserByUsername(String username) {
return users.get(username);
}
}
第三步:创建LoginServlet
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private UserService userService = new UserService();
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 设置请求编码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String remember = request.getParameter("remember");
// 验证用户
User user = userService.validateUser(username, password);
if (user != null) {
// 登录成功,把用户信息存入session
HttpSession session = request.getSession();
session.setAttribute("currentUser", user);
session.setAttribute("isLoggedIn", true);
// 如果勾选了"记住我",可以设置cookie
if ("on".equals(remember)) {
Cookie cookie = new Cookie("username", username);
cookie.setMaxAge(7 * 24 * 60 * 60); // 7天
response.addCookie(cookie);
}
// 重定向到主页(防止表单重复提交)
response.sendRedirect(request.getContextPath() + "/index.jsp");
} else {
// 登录失败,返回登录页并带错误信息
request.setAttribute("error", "用户名或密码错误!");
request.setAttribute("username", username);
request.getRequestDispatcher("/login.jsp")
.forward(request, response);
}
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
}
第四步:登录页面 login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
width: 380px;
}
.login-container h2 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #555;
}
.form-group input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.form-group input:focus {
border-color: #667eea;
outline: none;
}
.remember {
margin-bottom: 20px;
color: #666;
font-size: 14px;
}
.btn-login {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
}
.btn-login:hover {
opacity: 0.9;
}
.error-msg {
background: #ffebee;
color: #c62828;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
text-align: center;
}
.demo-info {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
font-size: 13px;
color: #666;
}
</style>
</head>
<body>
<div class="login-container">
<h2>🔐 用户登录</h2>
<%-- 显示错误信息 --%>
<c:if test="${not empty error}">
<div class="error-msg">${error}</div>
</c:if>
<form action="login" method="POST">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username"
value="${username}"
placeholder="请输入用户名"
autofocus>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password"
placeholder="请输入密码">
</div>
<div class="remember">
<input type="checkbox" name="remember" value="on">
记住我
</div>
<button type="submit" class="btn-login">登 录</button>
</form>
<div class="demo-info">
<strong>测试账号:</strong><br>
用户名:admin / 密码:123456<br>
用户名:zhangsan / 密码:abc123
</div>
</div>
</body>
</html>
第五步:主页 index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>主页</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Microsoft YaHei', sans-serif;
background: #f0f2f5;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar h1 { font-size: 22px; }
.navbar .user-info {
display: flex;
align-items: center;
gap: 20px;
}
.container {
max-width: 1000px;
margin: 30px auto;
padding: 0 20px;
}
.welcome-card {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
margin-bottom: 20px;
}
.welcome-card h2 { color: #333; margin-bottom: 15px; }
.welcome-card p { color: #666; line-height: 1.8; }
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
text-align: center;
}
.stat-card h3 { color: #667eea; font-size: 32px; margin-bottom: 8px; }
.stat-card p { color: #888; }
.btn-logout {
background: rgba(255,255,255,0.2);
color: white;
border: 1px solid rgba(255,255,255,0.4);
padding: 8px 20px;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
.btn-logout:hover { background: rgba(255,255,255,0.3); }
</style>
</head>
<body>
<nav class="navbar">
<h1>🌐 Java Web演示系统</h1>
<div class="user-info">
<span>欢迎,<strong>${currentUser.username}</strong></span>
<a href="logout.jsp" class="btn-logout">退出登录</a>
</div>
</nav>
<div class="container">
<div class="welcome-card">
<h2>👋 你好,${currentUser.username}!</h2>
<p>你已成功登录本系统。这是一个基于JSP+Servlet的简单登录系统,展示了从静态页面到动态网站的核心开发流程。</p>
<p style="margin-top: 10px;">
<strong>你的邮箱:</strong>${currentUser.email}<br>
<strong>登录时间:</strong><%= new java.util.Date().toString() %>
</p>
</div>
<div class="stats-grid">
<div class="stat-card">
<h3>3</h3>
<p>测试用户</p>
</div>
<div class="stat-card">
<h3>1</h3>
<p>当前在线</p>
</div>
<div class="stat-card">
<h3>24/7</h3>
<p>系统可用性</p>
</div>
<div class="stat-card">
<h3>JSP</h3>
<p>核心技术</p>
</div>
</div>
</div>
</body>
</html>
第六步:注销页面 logout.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 销毁session
session.invalidate();
// 清除cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
// 重定向回登录页
response.sendRedirect("login.jsp");
%>
第七步:web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>LoginSystem</display-name>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
<!-- 配置编码过滤器,防止中文乱码 -->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
运行效果
启动Tomcat,浏览器访问http://localhost:8080/LoginSystem,你会看到登录页面。用admin/123456登录,就能进入主页看到动态内容。
进阶:用JSP实现一个简单的商品展示系统
刚才的登录系统只是入门,下面咱们做一个功能更全的实战项目——商品展示系统,涵盖列表展示、详情查看、购物车等核心功能。
项目结构
ShopSystem/
├── WebContent/
│ ├── WEB-INF/
│ │ └── web.xml
│ ├── css/
│ │ └── style.css
│ ├── products.jsp <!-- 商品列表 -->
│ ├── product_detail.jsp <!-- 商品详情 -->
│ ├── cart.jsp <!-- 购物车 -->
│ ├── add_to_cart.jsp <!-- 加入购物车处理 -->
│ └── checkout.jsp <!-- 结算 -->
└── src/
└── com/example/
├── Product.java
├── ProductDao.java
├── Cart.java
├── CartItem.java
└── ProductServlet.java
核心代码
Product.java
package com.example;
public class Product {
private int id;
private String name;
private double price;
private String description;
private String image;
private int stock;
public Product() {}
public Product(int id, String name, double price, String description,
String image, int stock) {
this.id = id;
this.name = name;
this.price = price;
this.description = description;
this.image = image;
this.stock = stock;
}
// getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
public int getStock() { return stock; }
public void setStock(int stock) { this.stock = stock; }
}
ProductDao.java(数据访问层)
package com.example;
import java.util.*;
public class ProductDao {
private static List<Product> products = new ArrayList<>();
static {
// 初始化模拟数据
products.add(new Product(1, "MacBook Pro", 12999.00,
"苹果旗舰笔记本电脑,M2芯片,14英寸Retina显示屏",
"https://via.placeholder.com/300x200?text=MacBook", 50));
products.add(new Product(2, "iPhone 15", 7999.00,
"苹果最新旗舰手机,A17 Pro芯片,4800万像素相机",
"https://via.placeholder.com/300x200?text=iPhone", 100));
products.add(new Product(3, "AirPods Pro", 1899.00,
"苹果无线降噪耳机,主动降噪,空间音频",
"https://via.placeholder.com/300x200?text=AirPods", 200));
products.add(new Product(4, "iPad Air", 4799.00,
"苹果平板电脑,M1芯片,10.9英寸Liquid Retina显示屏",
"https://via.placeholder.com/300x200?text=iPad", 80));
products.add(new Product(5, "Apple Watch", 2999.00,
"苹果智能手表,健康监测,GPS + 蜂窝网络",
"https://via.placeholder.com/300x200?text=Watch", 150));
}
/**
* 获取所有商品
*/
public List<Product> getAllProducts() {
return products;
}
/**
* 根据ID获取商品
*/
public Product getProductById(int id) {
for (Product p : products) {
if (p.getId() == id) {
return p;
}
}
return null;
}
/**
* 搜索商品
*/
public List<Product> searchProducts(String keyword) {
List<Product> result = new ArrayList<>();
for (Product p : products) {
if (p.getName().contains(keyword) || p.getDescription().contains(keyword)) {
result.add(p);
}
}
return result;
}
}
Cart.java(购物车)
package com.example;
import java.util.*;
public class Cart {
private Map<Integer, CartItem> items = new HashMap<>();
/**
* 添加商品到购物车
*/
public void addToCart(Product product, int quantity) {
CartItem item = items.get(product.getId());
if (item == null) {
item = new CartItem(product, quantity);
items.put(product.getId(), item);
} else {
item.setQuantity(item.getQuantity() + quantity);
}
}
/**
* 从购物车移除商品
*/
public void removeFromCart(int productId) {
items.remove(productId);
}
/**
* 更新商品数量
*/
public void updateQuantity(int productId, int quantity) {
CartItem item = items.get(productId);
if (item != null) {
item.setQuantity(quantity);
}
}
/**
* 清空购物车
*/
public void clearCart() {
items.clear();
}
/**
* 获取购物车所有商品
*/
public Map<Integer, CartItem> getItems() {
return items;
}
/**
* 计算总价
*/
public double getTotalPrice() {
double total = 0;
for (CartItem item : items.values()) {
total += item.getSubtotal();
}
return total;
}
/**
* 获取商品数量
*/
public int getItemCount() {
int count = 0;
for (CartItem item : items.values()) {
count += item.getQuantity();
}
return count;
}
}
CartItem.java
package com.example;
public class CartItem {
private Product product;
private int quantity;
public CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public Product getProduct() { return product; }
public void setProduct(Product product) { this.product = product; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public double getSubtotal() {
return product.getPrice() * quantity;
}
}
ProductServlet.java
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.List;
public class ProductServlet extends HttpServlet {
private ProductDao productDao = new ProductDao();
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String action = request.getParameter("action");
if ("search".equals(action)) {
// 搜索商品
String keyword = request.getParameter("keyword");
List<Product> products = productDao.searchProducts(keyword);
request.setAttribute("products", products);
request.setAttribute("keyword", keyword);
request.getRequestDispatcher("products.jsp").forward(request, response);
} else if ("detail".equals(action)) {
// 商品详情
int id = Integer.parseInt(request.getParameter("id"));
Product product = productDao.getProductById(id);
request.setAttribute("product", product);
request.getRequestDispatcher("product_detail.jsp")
.forward(request, response);
} else {
// 商品列表
List<Product> products = productDao.getAllProducts();
request.setAttribute("products", products);
request.getRequestDispatcher("products.jsp").forward(request, response);
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String action = request.getParameter("action");
if ("add_to_cart".equals(action)) {
// 加入购物车
int productId = Integer.parseInt(request.getParameter("productId"));
int quantity = Integer.parseInt(request.getParameter("quantity"));
Product product = productDao.getProductById(productId);
// 从session获取购物车(如果没有就创建一个新的)
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
cart.addToCart(product, quantity);
// 重定向回商品列表
response.sendRedirect(request.getContextPath() + "/ProductServlet");
} else if ("remove_from_cart".equals(action)) {
// 从购物车移除
int productId = Integer.parseInt(request.getParameter("productId"));
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.removeFromCart(productId);
}
response.sendRedirect(request.getContextPath() + "/cart.jsp");
} else if ("update_cart".equals(action)) {
// 更新购物车
int productId = Integer.parseInt(request.getParameter("productId"));
int quantity = Integer.parseInt(request.getParameter("quantity"));
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.updateQuantity(productId, quantity);
}
response.sendRedirect(request.getContextPath() + "/cart.jsp");
} else if ("clear_cart".equals(action)) {
// 清空购物车
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.clearCart();
}
response.sendRedirect(request.getContextPath() + "/cart.jsp");
} else {
doGet(request, response);
}
}
}
商品列表页面 products.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>商品列表 - 在线商城</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Microsoft YaHei', sans-serif;
background: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 0;
}
.header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 { font-size: 24px; }
.search-bar {
display: flex;
gap: 10px;
}
.search-bar input {
padding: 10px 15px;
border: none;
border-radius: 5px;
width: 300px;
font-size: 14px;
}
.search-bar button {
padding: 10px 20px;
background: #ff6b6b;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.cart-link {
color: white;
text-decoration: none;
background: rgba(255,255,255,0.2);
padding: 10px 20px;
border-radius: 5px;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.breadcrumb {
padding: 15px 0;
color: #888;
font-size: 14px;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 25px;
padding: 20px 0 50px;
}
.product-card {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
transition: transform 0.3s, box-shadow 0.3s;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}
.product-image {
width: 100%;
height: 200px;
object-fit: cover;
background: #f0f0f0;
}
.product-info {
padding: 20px;
}
.product-name {
font-size: 16px;
color: #333;
margin-bottom: 8px;
font-weight: bold;
}
.product-desc {
font-size: 13px;
color: #888;
margin-bottom: 12px;
line-height: 1.5;
height: 40px;
overflow: hidden;
}
.product-price {
font-size: 20px;
color: #ff6b6b;
font-weight: bold;
margin-bottom: 15px;
}
.product-actions {
display: flex;
gap: 10px;
}
.btn-detail {
flex: 1;
padding: 10px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
text-align: center;
font-size: 14px;
}
.btn-cart {
flex: 1;
padding: 10px;
background: #ff6b6b;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.empty-msg {
text-align: center;
padding: 50px;
color: #888;
grid-column: 1 / -1;
}
</style>
</head>
<body>
<header class="header">
<div class="container">
<h1>🛒 在线商城</h1>
<form action="ProductServlet" method="get" class="search-bar">
<input type="text" name="keyword" placeholder="搜索商品..."
value="${keyword}">
<input type="hidden" name="action" value="search">
<button type="submit">搜索</button>
</form>
<a href="cart.jsp" class="cart-link">
🛒 购物车 (${cart.itemCount > 0 ? cart.itemCount : 0})
</a>
</div>
</header>
<div class="container">
<div class="breadcrumb">
首页 > 商品列表
<c:if test="${not empty keyword}">
> 搜索"${keyword}"的结果
</c:if>
</div>
<div class="products-grid">
<c:choose>
<c:when test="${empty products}">
<div class="empty-msg">
<p>😔 暂无商品,请稍后再来~</p>
</div>
</c:when>
<c:otherwise>
<c:forEach items="${products}" var="product">
<div class="product-card">
<img src="${product.image}" alt="${product.name}"
class="product-image">
<div class="product-info">
<h3 class="product-name">${product.name}</h3>
<p class="product-desc">${product.description}</p>
<p class="product-price">
<fmt:formatNumber value="${product.price}"
type="currency"
currencySymbol="¥"/>
</p>
<div class="product-actions">
<a href="ProductServlet?action=detail&id=${product.id}"
class="btn-detail">查看详情</a>
<form action="ProductServlet" method="post"
style="display:inline;">
<input type="hidden" name="action" value="add_to_cart">
<input type="hidden" name="productId"
value="${product.id}">
<input type="hidden" name="quantity" value="1">
<button type="submit" class="btn-cart">加入购物车</button>
</form>
</div>
</div>
</div>
</c:forEach>
</c:otherwise>
</c:choose>
</div>
</div>
</body>
</html>
购物车页面 cart.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>购物车 - 在线商城</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Microsoft YaHei', sans-serif;
background: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 0;
}
.header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 { font-size: 24px; }
.container {
max-width: 1000px;
margin: 0 auto;
padding: 30px 20px;
}
.cart-table {
width: 100%;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
margin-bottom: 20px;
}
.cart-table table {
width: 100%;
border-collapse: collapse;
}
.cart-table th {
background: #f8f9fa;
padding: 15px;
text-align: left;
color: #555;
font-weight: 600;
}
.cart-table td {
padding: 15px;
border-top: 1px solid #eee;
vertical-align: middle;
}
.product-cell {
display: flex;
align-items: center;
gap: 15px;
}
.product-cell img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 5px;
}
.product-cell .name {
font-weight: bold;
color: #333;
}
.qty-input {
width: 60px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
text-align: center;
}
.btn-remove {
background: #ff6b6b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
}
.cart-summary {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.summary-row.total {
border-bottom: none;
font-size: 18px;
font-weight: bold;
}
.total-price {
color: #ff6b6b;
font-size: 24px;
}
.btn-group {
display: flex;
gap: 15px;
margin-top: 20px;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-danger {
background: #ff6b6b;
color: white;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.empty-cart {
text-align: center;
padding: 60px;
background: white;
border-radius: 10px;
}
.empty-cart p {
color: #888;
font-size: 16px;
margin-top: 15px;
}
</style>
</head>
<body>
<header class="header">
<div class="container">
<h1>🛒 购物车</h1>
<a href="ProductServlet" class="btn" style="background:rgba(255,255,255,0.2);color:white;padding:10px 20px;border-radius:5px;text-decoration:none;">← 继续购物</a>
</div>
</header>
<div class="container">
<c:choose>
<c:when test="${empty cart.items}">
<div class="empty-cart">
<h2>🛒 你的购物车是空的</h2>
<p>快去挑选你喜欢的商品吧!</p>
<a href="ProductServlet" class="btn btn-primary" style="margin-top:20px;">去逛逛</a>
</div>
</c:when>
<c:otherwise>
<div class="cart-table">
<table>
<thead>
<tr>
<th>商品</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach items="${cart.items}" var="entry">
<tr>
<td>
<div class="product-cell">
<img src="${entry.value.product.image}"
alt="${entry.value.product.name}">
<span class="name">${entry.value.product.name}</span>
</div>
</td>
<td>
<fmt:formatNumber value="${entry.value.product.price}"
type="currency"
currencySymbol="¥"/>
</td>
<td>
<form action="ProductServlet" method="post"
style="display:inline;">
<input type="hidden" name="action" value="update_cart">
<input type="hidden" name="productId"
value="${entry.key}">
<input type="number" name="quantity"
value="${entry.value.quantity}"
min="1" class="qty-input"
onchange="this.form.submit()">
</form>
</td>
<td>
<fmt:formatNumber value="${entry.value.subtotal}"
type="currency"
currencySymbol="¥"/>
</td>
<td>
<form action="ProductServlet" method="post"
style="display:inline;">
<input type="hidden" name="action" value="remove_from_cart">
<input type="hidden" name="productId"
value="${entry.key}">
<button type="submit" class="btn-remove">删除</button>
</form>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
<div class="cart-summary">
<div class="summary-row">
<span>商品数量</span>
<span>${cart.itemCount} 件</span>
</div>
<div class="summary-row">
<span>商品总价</span>
<span class="total-price">
<fmt:formatNumber value="${cart.totalPrice}"
type="currency"
currencySymbol="¥"/>
</span>
</div>
<div class="summary-row total">
<span>应付金额</span>
<span class="total-price">
<fmt:formatNumber value="${cart.totalPrice}"
type="currency"
currencySymbol="¥"/>
</span>
</div>
<div class="btn-group">
<form action="ProductServlet" method="post" style="display:inline;">
<input type="hidden" name="action" value="clear_cart">
<button type="submit" class="btn btn-danger">清空购物车</button>
</form>
<a href="checkout.jsp" class="btn btn-primary">去结算</a>
<a href="ProductServlet" class="btn btn-secondary">继续购物</a>
</div>
</div>
</c:otherwise>
</c:choose>
</div>
</body>
</html>
JSP开发的最佳实践和常见坑
学了很多东西,但怎么用好才是关键。下面这些经验之谈,是无数开发者踩坑换来的。
一、尽量不用Scriptlet,多用JSTL+EL
<%-- ❌ 不推荐:JSP里写一堆Java代码 --%>
<%
List<User> users = (List<User>) request.getAttribute("users");
if (users != null) {
for (User user : users) {
out.println("<tr>");
out.println("<td>" + user.getName() + "</td>");
out.println("<td>" + user.getAge() + "</td>");
out.println("</tr>");
}
}
%>
<%-- ✅ 推荐:JSTL + EL --%>
<c:forEach items="${users}" var="user">
<tr>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
二、永远不要信任用户输入
// ❌ 危险!直接拼接SQL
String sql = "SELECT * FROM users WHERE name='" + username + "'";
// ✅ 使用预编译语句
PreparedStatement pstmt = connection.prepareStatement(
"SELECT * FROM users WHERE name=?");
pstmt.setString(1, username);
在JSP里显示用户输入时,也要防止XSS攻击:
<%-- ❌ 危险:直接输出用户输入 --%>
<p>${param.username}</p>
<%-- ✅ 安全:使用fn:escapeXml --%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<p>${fn:escapeXml(param.username)}</p>
三、使用UTF-8编码,防止中文乱码
在web.xml里加一个编码过滤器:
package com.example;
import javax.servlet.*;
import java.io.IOException;
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
@Override
public void destroy() {}
}
<!-- web.xml -->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
四、善用Session,但别忘了清理
// 登录成功后存session
session.setAttribute("currentUser", user);
session.setAttribute("isLoggedIn", true);
// 注销时记得清理
session.invalidate();
五、分页是必须的
当数据量大的时候,不要一次性全部查出来,要做分页:
// ProductDao.java 添加分页方法
public List<Product> getPageProducts(int page, int pageSize) {
int startIndex = (page - 1) * pageSize;
List<Product> allProducts = getAllProducts();
if (startIndex >= allProducts.size()) {
return new ArrayList<>();
}
int endIndex = Math.min(startIndex + pageSize, allProducts.size());
return allProducts.subList(startIndex, endIndex);
}
public int getTotalPages(int pageSize) {
return (int) Math.ceil((double) getAllProducts().size() / pageSize);
}
<!-- products.jsp 底部添加分页 -->
<div class="pagination">
<c:if test="${currentPage > 1}">
<a href="ProductServlet?action=list&page=${currentPage - 1}">上一页</a>
</c:if>
<span>第 ${currentPage} 页 / 共 ${totalPages} 页</span>
<c:if test="${currentPage < totalPages}">
<a href="ProductServlet?action=list&page=${currentPage + 1}">下一页</a>
</c:if>
</div>
JSP在现代Java Web开发中的地位
你可能会问:JSP都这么”老”了,现在还有人用吗?
说实话,新的项目确实很少用JSP了。Spring Boot + Thymeleaf、Vue/React前后端分离,已经是主流。但是:
- 大量存量系统在用JSP:银行、政府、国企的老系统,很多都是JSP写的,维护这些系统的人还是需要懂JSP
- 理解JSP有助于理解现代技术:Thymeleaf的本质和JSP很像,都是模板引擎。理解JSP的运作机制,你学Thymeleaf会快很多
- 面试还是会被问到:很多公司的Java后端面试,JSP/Servlet是必考内容
所以,学JSP不是为了”用它开发新项目”,而是为了理解Java Web的底层原理,建立完整的知识体系。
学习路线建议
如果你要从零开始系统地掌握Java Web开发,建议按这个顺序:
1. Java基础(语法、面向对象、集合、IO、异常处理)
↓
2. HTML/CSS/JavaScript基础(前端三件套)
↓
3. HTTP协议基础(请求/响应、状态码、Cookie/Session)
↓
4. Servlet基础(request/response/session/application)
↓
5. JSP基础(语法、隐式对象、JSTL、EL)
↓
6. JDBC基础(数据库连接、CRUD操作)
↓
7. 综合实战(上面的商品系统、登录系统)
↓
8. 进阶:框架学习(Spring、Spring MVC、MyBatis)
↓
9. 现代技术栈(Spring Boot、Vue/React)
JSP就是这个链条里承上启下的一环——前面是Java基础和HTML,后面是各种框架。把这个环节打通了,后面的路会顺很多。
最后的话
写到这里,这篇JSP教程已经覆盖了从”这是什么”到”怎么用”到”怎么用好”的完整路径。
记住几点最重要的:
- JSP本质是Servlet——理解了这一点,很多概念就通了
- EL和JSTL是你的好朋友——少写
<% %>,多写${ }和<c:> - 安全永远第一位——输入验证、防SQL注入、防XSS,一个都不能少
- 动手实践最重要——看懂不等于会做,把上面的代码亲手敲一遍,你会收获更多
学技术就像搭积木,一块一块来,基础打牢了,后面盖多高的楼都不怕。希望这篇教程能帮你顺利跨过JSP这道坎,后面的路越走越顺!
如果你在实践中遇到任何问题,或者想深入了解某个知识点,随时来问。祝你学习愉快,早日成为Java Web开发的高手!🚀
