从零到一搞懂JavaWeb:Servlet、Cookie、Session、JSP、Filter、Ajax一次讲透
从零到一搞懂JavaWeb:Servlet、Cookie、Session、JSP、Filter、Ajax一次讲透
最近在做一个企业内部管理系统的时候,前端同事问我:"你后端接口返回的状态码是啥意思?我怎么判断用户有没有登录?"我当时愣了一下,发现虽然用了很久的Spring Boot,但对底层的Servlet、Cookie、Session这些JavaWeb基础知识,理解得并不扎实。于是花了一段时间系统梳理了一遍,今天把学习笔记整理出来,希望能帮到有同样困惑的朋友。
一、JavaEE是什么?和JavaWeb有什么关系?
在正式开始之前,先搞清楚一个基本概念:JavaEE(Java Enterprise Edition),Java企业版,是一个用于企业级Web开发的平台规范。最早由Sun公司制定,后来由Oracle负责维护。
JavaEE平台规范了13个技术标准,包括:JDBC、JNDI、EJB、RMI、Servlet、JSP、XML、JMS、Java IDL、JPA、JTA、JavaMail和JAF。
我们常说的"JavaWeb开发",其实就是在JavaEE规范下,使用Servlet、JSP等技术来构建Web应用。只不过现在大家都用Spring Boot了,底层其实还是这些技术在撑着。
简单理解:JavaEE是规范,JavaWeb是基于这个规范的实际开发。就像"交通法规"和"开车上路"的关系一样。
二、Tomcat:JavaWeb应用的"家"
2.1 什么是Tomcat?
Tomcat是Apache开源组织使用Java开发的一款Web容器(也叫Web服务器)。它实现了JavaEE平台下的部分技术标准,比如Servlet、JSP、JNDI等。最关键的是——免费开源。
对比一下其他服务器:
| 服务器 | 厂商 | 是否免费 |
|---|---|---|
| Weblogic | Oracle | 收费 |
| WebSphere | IBM | 收费 |
| JBoss | RedHat | 收费 |
| Tomcat | Apache | 免费 |
| Jetty | Eclipse | 免费 |
2.2 Tomcat的目录结构
下载Tomcat后解压,目录结构是这样的:
apache-tomcat-9.0.x/
├── bin/ # 启动和关闭的命令文件(.bat是Windows的,.sh是Linux的)
├── conf/ # 配置文件(server.xml、web.xml等)
├── lib/ # Tomcat运行需要的jar包
├── logs/ # 运行日志
├── temp/ # 临时文件(清空不影响运行)
├── webapps/ # 存放Web应用的地方(重点!)
└── work/ # JSP编译后的文件
小提示:安装路径不要包含中文、空格和特殊符号,这是个好习惯。
2.3 Tomcat的配置
Tomcat的核心配置文件有4个:
- server.xml:Tomcat的核心配置,可以修改端口号、并发数等
- web.xml:Web应用的默认配置
- context.xml:公用环境配置
- tomcat-users.xml:配置访问Tomcat Manager的用户
修改端口号(默认8080):
<!-- conf/server.xml -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
80是HTTP协议的默认端口,如果改成80,访问时就可以省略端口号。
配置并发数:
<Connector port="8080" protocol="HTTP/1.1"
minSpareThreads="100" <!-- 初始化线程数 -->
maxSpareThreads="500" <!-- 最大空闲线程数 -->
maxThreads="1000" <!-- 最大并发数 -->
acceptCount="700" <!-- 等待队列长度 -->
connectionTimeout="20000"
redirectPort="8443" />
2.4 Tomcat处理请求的过程
当用户访问 http://localhost:8080/test/index.jsp 时,Tomcat内部是这样处理的:
- Connector(连接器)监听8080端口,接收到HTTP请求
- Connector把请求交给Engine(引擎)
- Engine匹配Host(虚拟主机),找到
localhost - Host根据路径
/test匹配Context(Web应用) - Context处理
/index.jsp,生成 HttpServletRequest 和 HttpServletResponse - 业务逻辑执行完毕,响应层层返回,最终到达浏览器
这个过程就像快递:Connector是收件员,Engine是分拣中心,Host是区域站点,Context是具体派送员。
三、HTTP协议:浏览器和服务器的"对话规则"
3.1 什么是HTTP协议?
HTTP(HyperText Transfer Protocol,超文本传输协议)是万维网数据通信的基础。简单说,它定义了浏览器怎么向服务器发请求,服务器怎么给浏览器回响应。
HTTP协议有几个重要特点:
- 支持客户端/服务器模式:必须用浏览器(客户端)主动请求
- 简单快速:只需要传请求方法和路径
- 灵活:可以传输任意类型的数据(通过Content-Type标识)
- 无连接:每次请求完成后就断开(HTTP/1.1默认支持长连接)
- 无状态:服务器不记录之前的请求信息(这是Cookie和Session出现的原因!)
3.2 HTTP请求的组成
一个HTTP请求分为三部分:请求行、请求头、请求体。
POST /login HTTP/1.1 ← 请求行(请求方式 + 路径 + 协议版本)
Host: localhost:8080 ← 请求头(键值对)
Content-Type: application/json
← 空行
{"username":"admin","pwd":"123"} ← 请求体(POST才有)
常见请求头:
| 请求头 | 作用 |
|---|---|
| Host | 指定服务器域名和端口 |
| Connection | 连接方式(keep-alive/close) |
| User-Agent | 浏览器身份标识 |
| Accept | 接受的响应数据类型 |
| Cookie | 携带之前服务器设置的Cookie |
| Content-Type | 请求体的数据格式 |
3.3 GET和POST的区别(面试高频!)
| 对比项 | GET | POST |
|---|---|---|
| 参数位置 | URL中 | 请求体中 |
| 数据量 | 有限制(URL长度) | 理论上无限制 |
| 安全性 | 参数暴露在URL中 | 相对安全 |
| 缓存 | 浏览器会主动缓存 | 不会缓存 |
| 书签 | 可以收藏 | 不可以 |
| 编码 | 只支持ASCII | 支持多种编码 |
| 回退 | 无害 | 会重新提交 |
一句话总结:GET适合查询,POST适合提交数据。敏感信息(密码等)一定用POST。
3.4 HTTP响应的组成
响应也分三部分:响应行、响应头、响应体。
HTTP/1.1 200 OK ← 响应行(协议 + 状态码 + 描述)
Content-Type: text/html ← 响应头
Content-Length: 1024
← 空行
<html>...</html> ← 响应体
常见状态码:
| 状态码 | 含义 |
|---|---|
| 200 | 请求成功 |
| 301 | 永久重定向 |
| 302 | 临时重定向 |
| 304 | 资源未修改,使用缓存 |
| 400 | 请求语法错误 |
| 401 | 未授权 |
| 403 | 无权限访问 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
四、Servlet:JavaWeb的核心
4.1 什么是Servlet?
Servlet(Server Applet)是JavaEE平台下的技术标准,运行在服务器端的Java程序。它的核心功能是:接收HTTP请求,处理业务逻辑,返回HTTP响应。
静态资源:HTML、CSS、JS等,每次访问内容不变。
动态资源:Servlet、JSP等,每次访问可能返回不同的内容。
在MVC模式中,Servlet扮演的是**Controller(控制器)**的角色。
4.2 第一个Servlet
// 继承HttpServlet是开发Servlet的标准方式
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 动态生成数据
int num = new Random().nextInt(100);
String message = num % 2 == 0 ? "happy birthday" : "happy new year";
// 获取输出流,向浏览器写响应
PrintWriter writer = response.getWriter();
writer.write(message);
}
}
然后在 web.xml 中配置映射:
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/myServlet.do</url-pattern>
</servlet-mapping>
访问 http://localhost:8080/项目名/myServlet.do 就能看到效果了。
4.3 Servlet的继承结构
Servlet接口(定义了init、service、destroy等方法)
↓
GenericServlet抽象类(与协议无关的通用实现)
↓
HttpServlet(针对HTTP协议,区分GET/POST等请求方式)
↓
我们自定义的Servlet
实际开发中:直接继承HttpServlet,重写 doGet() 和 doPost() 方法,或者直接重写 service() 方法处理所有请求。
注意:要么重写
doGet/doPost,要么重写service,二选一,必须重写一个。
4.4 Servlet的生命周期(面试重点!)
Servlet的生命周期由容器(Tomcat)管理,分为四个阶段:
| 阶段 | 次数 | 时机 |
|---|---|---|
| 实例化 | 1次 | 第一次请求时(或配置load-on-startup) |
| 初始化 | 1次 | 实例化之后,调用init() |
| 执行服务 | 多次 | 每次请求都调用service() |
| 销毁 | 1次 | 服务器停止时,调用destroy() |
public class MyServlet4 extends HttpServlet {
// 构造方法:创建Servlet对象
public MyServlet4() {
System.out.println("构造方法调用");
}
// 初始化:只调用一次
@Override
public void init() throws ServletException {
System.out.println("init调用");
}
// 服务方法:每次请求都调用
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("service调用");
}
// 销毁:服务器停止时调用
@Override
public void destroy() {
System.out.println("destroy调用");
}
}
重要:Servlet是单实例多线程的!一个Servlet类只会创建一个对象,但每次请求会开一个新线程。所以不要在Servlet中使用成员变量,否则会有线程安全问题!
4.5 HttpServletRequest:获取请求信息
HttpServletRequest 对象封装了客户端发来的所有请求信息。
获取请求行信息:
req.getRequestURL() // 完整URL:http://localhost:8080/demo/servlet1.do
req.getRequestURI() // 资源路径:/demo/servlet1.do
req.getRemoteAddr() // 客户端IP
req.getLocalAddr() // 服务器IP
req.getLocalPort() // 服务器端口
req.getMethod() // 请求方式:GET/POST
req.getContextPath() // 项目部署名
获取请求参数:
// 获取单个参数
String username = req.getParameter("username");
// 获取多个同名参数(比如复选框)
String[] hobbies = req.getParameterValues("hobby");
// 获取所有参数名
Enumeration<String> names = req.getParameterNames();
// 获取所有参数(Map结构)
Map<String, String[]> params = req.getParameterMap();
处理中文乱码:
// POST请求设置编码(必须在获取参数之前调用!)
req.setCharacterEncoding("UTF-8");
4.6 HttpServletResponse:返回响应信息
HttpServletResponse 对象用于向客户端返回响应。
设置响应类型:
// 返回HTML
resp.setContentType("text/html;charset=UTF-8");
// 返回JSON(现在最常用)
resp.setContentType("application/json;charset=UTF-8");
// 返回纯文本
resp.setContentType("text/plain;charset=UTF-8");
// 返回图片
resp.setContentType("image/jpeg");
输出响应内容:
// 字符型响应
PrintWriter writer = resp.getWriter();
writer.write("{\"code\":200,\"message\":\"success\"}");
// 字节型响应(文件下载)
OutputStream os = resp.getOutputStream();
文件下载示例:
// 设置响应头,告诉浏览器这是一个需要下载的文件
resp.setHeader("Content-Disposition",
"attachment;filename=" + new String(fileName.getBytes("gbk"), "iso-8859-1"));
4.7 ServletConfig和ServletContext
ServletConfig:对应web.xml中单个Servlet的配置,每个Servlet独享一个。
// 获取Servlet的初始化参数
ServletConfig config = this.getServletConfig();
String brand = config.getInitParameter("brand"); // 对应<init-param>
ServletContext:整个Web应用全局唯一,所有Servlet共享。
// 获取ServletContext
ServletContext context = this.getServletContext();
// 或者
ServletContext context = req.getServletContext();
// 获取项目部署名
String contextPath = context.getContextPath();
// 相对路径转绝对路径(文件上传下载时很有用)
String realPath = context.getRealPath("upload");
// 获取全局配置参数(对应<context-param>)
String username = context.getInitParameter("username");
// 全局容器(所有Servlet共享)
context.setAttribute("key", value); // 存数据
Object value = context.getAttribute("key"); // 取数据
context.removeAttribute("key"); // 删数据
注意:ServletContext生命周期很长(从服务器启动到关闭),不建议存放业务数据。
4.8 URL匹配规则
Servlet的URL匹配有四种方式:
| 匹配方式 | 配置示例 | 说明 |
|---|---|---|
| 精确匹配 | /demo.do |
必须完全一致 |
| 扩展名匹配 | *.do |
只看扩展名,不看路径 |
| 路径匹配 | /suibian/* |
匹配该路径及子路径 |
| 匹配所有 | /* |
匹配所有请求 |
优先级:精确路径 > 最长路径 > 扩展名
举个例子:
Servlet1 → /abc/*
Servlet2 → /*
Servlet3 → /abc
Servlet4 → *.do
请求 /abc/a.html → 匹配Servlet1(/abc/* 比 /* 更精确)
请求 /abc → 匹配Servlet3(精确匹配优先)
请求 /abc/a.do → 匹配Servlet1(/abc/* 比 *.do 优先级高)
请求 /a.do → 匹配Servlet2(/* 比 *.do 优先级高)
4.9 注解方式开发Servlet
Servlet 3.0之后支持注解开发,不用再写web.xml配置了:
@WebServlet(urlPatterns = "/servlet1.do")
public class Servlet1 extends HttpServlet {
// ...
}
// 带初始化参数
@WebServlet(
urlPatterns = "/servlet2.do",
initParams = {
@WebInitParam(name = "brand", value = "ASUS"),
@WebInitParam(name = "screen", value = "三星")
}
)
public class Servlet2 extends HttpServlet {
// ...
}
五、Cookie和Session:解决"无状态"的利器
5.1 为什么需要Cookie和Session?
HTTP协议是无状态的——服务器不记得你之前来过。但很多时候我们需要记录用户状态,比如:
- 用户登录后,后续请求要能识别是同一个用户
- 购物车功能,用户添加的商品要能保留
- 记住用户偏好设置
这就需要会话管理技术:Cookie和Session。
5.2 Cookie:客户端的"小本本"
Cookie是保存在浏览器端的少量数据。工作流程:
- 第一次请求时,服务器通过响应头
Set-Cookie给浏览器一些数据 - 浏览器把这些数据保存下来
- 之后每次请求,浏览器都会自动通过请求头
Cookie把这些数据带上
Cookie的特点:
- 使用Key-Value结构存储数据
- 单个Cookie大小限制4097字节
- 每个域名最多50个Cookie(Chrome)
- 与域名绑定,不支持跨一级域名
- 分为状态Cookie(内存,关闭浏览器消失)和持久化Cookie(磁盘)
创建和读取Cookie:
@WebServlet(urlPatterns = "/cookie.do")
public class CookieServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// ===== 创建Cookie并响应给浏览器 =====
Cookie c1 = new Cookie("username", "zhangsan");
Cookie c2 = new Cookie("age", "25");
// 设置持久化Cookie(存活60秒)
c2.setMaxAge(60);
resp.addCookie(c1);
resp.addCookie(c2);
// ===== 读取浏览器带来的Cookie =====
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + "=" + cookie.getValue());
}
}
}
}
Cookie不支持中文的处理(Servlet 4.0之前):
// 存储时编码
Cookie c = new Cookie("name", URLEncoder.encode("张三", "UTF-8"));
// 读取时解码
String name = URLDecoder.decode(cookie.getValue(), "UTF-8");
安全提醒:Cookie是明文存储的,安全性很低,不要在Cookie中存放敏感数据!
5.3 Cookie实战:记录访问次数
@WebServlet(urlPatterns = "/visitCount.do")
public class VisitCountServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
boolean found = false;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("visitCount".equals(cookie.getName())) {
// 找到记录,次数+1
int count = Integer.parseInt(cookie.getValue()) + 1;
Cookie newCookie = new Cookie("visitCount", String.valueOf(count));
resp.addCookie(newCookie);
resp.getWriter().write("欢迎您第" + count + "次访问!");
found = true;
break;
}
}
}
if (!found) {
// 第一次访问
Cookie newCookie = new Cookie("visitCount", "1");
resp.addCookie(newCookie);
resp.getWriter().write("欢迎您第一次访问!");
}
}
}
5.4 Session:服务端的"用户档案"
Session(HttpSession)是保存在服务器端的会话信息。工作流程:
- 第一次请求时,服务器创建Session对象,生成一个唯一的 JSESSIONID
- 把JSESSIONID通过Cookie发给浏览器
- 之后每次请求,浏览器通过Cookie带上JSESSIONID
- 服务器根据JSESSIONID找到对应的Session对象
Session的特点:
- 保存在服务器端,安全性高
- 可以存储任何类型的数据
- 使用Key-Value结构,value是Object类型
- 存储大小无限制
- 默认超时时间30分钟
Session的使用:
@WebServlet(urlPatterns = "/session.do")
public class SessionServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取Session(没有就创建新的)
HttpSession session = req.getSession();
// 存储数据
session.setAttribute("username", "zhangsan");
session.setAttribute("role", "admin");
// 读取数据
String username = (String) session.getAttribute("username");
// 获取Session ID
String sessionId = session.getId();
System.out.println("SessionID: " + sessionId);
// 删除数据
session.removeAttribute("role");
// 销毁整个Session
// session.invalidate();
}
}
Session的销毁方式:
<!-- web.xml中配置超时时间(单位:分钟) -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
// 或者手动销毁
session.invalidate();
5.5 Cookie和Session的区别(面试必问!)
| 对比项 | Cookie | Session |
|---|---|---|
| 存储位置 | 浏览器端 | 服务器端 |
| 安全性 | 低(明文存储) | 高 |
| 数据大小 | 限制4KB | 无限制 |
| 数量限制 | 每域名50个 | 无限制 |
| 生命周期 | 可设置MaxAge | 默认30分钟超时 |
| 数据类型 | 只能字符串 | 任意类型 |
使用建议:Session安全性高,适合存放用户登录信息。但Session生命周期不固定,不建议存放大量业务数据。
5.6 四大域对象
在JavaWeb中有四个"域对象",用来在不同范围内共享数据:
| 域对象 | 范围 | 说明 |
|---|---|---|
| PageContext | 当前页面 | JSP中使用 |
| HttpServletRequest | 一次请求 | 请求转发时共享 |
| HttpSession | 一次会话 | 跨请求共享 |
| ServletContext | 整个应用 | 所有用户共享 |
六、JSP:HTML和Java的"混血儿"
6.1 什么是JSP?
JSP(Java Server Pages)是一种动态网页技术,本质上就是Servlet。它的目的是把Java代码嵌入到HTML中,让页面展示更方便。
第一次访问JSP时:Tomcat会把JSP文件编译成一个Java文件(Servlet),然后编译成class文件执行。后续访问如果JSP没修改,就直接执行编译好的class。
6.2 JSP的本质
JSP文件最终会被转译成一个继承自 HttpServlet 的Java类。所以JSP本质上就是Servlet,只是写法更像HTML。
JSP的执行过程:
- 转译:.jsp → .java(Servlet源代码)
- 编译:.java → .class(字节码)
- 执行:运行class,生成响应
JSP为什么慢? 不仅是因为第一次需要转译和编译,更因为JSP内部通过大量IO流输出页面内容,IO是重量级操作。
6.3 JSP中嵌入Java代码
JSP提供了三种方式嵌入Java代码:
<%-- JSP注释,不会出现在转译后的Java代码中 --%>
<!-- 1. 脚本片段:在_JSPService方法中 -->
<%
int num = (int) (Math.random() * 100) + 1;
String grade = num >= 90 ? "优秀" : "及格";
%>
<!-- 2. 表达式:输出值到页面 -->
<p>分数:<%= num %></p>
<p>等级:<%= grade %></p>
<!-- 3. 声明:作为类的成员(不推荐,会有线程安全问题) -->
<%!
private int count = 0; // 成员变量,不推荐!
%>
建议:不要在JSP中定义成员变量,因为JSP本质是Servlet,Servlet是单实例多线程的。
6.4 JSP九大内置对象
JSP内置了9个可以直接使用的对象,不用new:
| 对象 | 类型 | 说明 |
|---|---|---|
| request | HttpServletRequest | 请求对象 |
| response | HttpServletResponse | 响应对象 |
| session | HttpSession | 会话对象 |
| application | ServletContext | 全局应用对象 |
| out | JspWriter | 输出对象 |
| pageContext | PageContext | 页面上下文 |
| config | ServletConfig | 配置对象 |
| page | Object | 当前页面(this) |
| exception | Throwable | 异常对象 |
这些对象其实就是Servlet中那些常用对象的"快捷方式"。
6.5 JSP指令标签
JSP有三种指令标签:
page指令:设置页面属性
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="java.util.*"
session="true"
errorPage="error.jsp" %>
include指令:包含其他文件
<%@ include file="header.jsp" %>
taglib指令:引入标签库
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
6.6 JSP的使用建议
JSP和Servlet本质相同,功能可以互换。但JSP更适合展示数据(在HTML中嵌入少量Java代码),Servlet更适合处理业务逻辑。
在现代开发中,JSP已经很少使用了,前后端分离架构下,后端只提供JSON接口,前端用Vue/React渲染页面。但理解JSP对理解JavaWeb的历史演进很有帮助。
七、MVC模式:分层的艺术
7.1 什么是MVC?
MVC(Model-View-Controller)是一种架构模式,把应用分成三层:
- Model(模型):业务逻辑和数据(JavaBean、Service、DAO)
- View(视图):页面展示(JSP、HTML)
- Controller(控制器):协调模型和视图(Servlet)
用户请求 → Controller → Model(处理业务) → View(展示页面) → 用户
7.2 MVC的优势
- 职责分离:各层独立修改,互不影响
- 代码复用:同一个Model可以被多个View使用
- 团队协作:后端写Model和Controller,前端写View
- 易于维护:改页面不用动业务逻辑,改业务不用动页面
在Spring MVC中,Controller就是
@Controller注解的类,Model就是Service和DAO层,View就是JSP或模板引擎。现在流行的Spring Boot + Vue前后端分离架构,View层完全交给了前端。
八、Filter和Listener:Servlet的"左膀右臂"
8.1 Filter过滤器
Filter可以拦截请求和响应,在Servlet处理之前和之后做一些通用处理。典型应用:
- 统一编码处理
- 登录验证
- 权限控制
- 敏感词过滤
- 压缩响应
Filter的工作流程:
浏览器请求 → Filter(预处理) → Servlet处理 → Filter(后处理) → 浏览器响应
开发一个Filter:
@WebFilter(urlPatterns = "/*") // 拦截所有请求
public class EncodingFilter implements Filter {
// 初始化(服务器启动时调用一次)
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter初始化");
}
// 核心方法:每次请求都会调用
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 预处理:设置编码
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("Filter预处理");
// 放行:交给下一个Filter或Servlet
chain.doFilter(request, response);
// 后处理:Servlet执行完毕后
System.out.println("Filter后处理");
}
// 销毁(服务器停止时调用一次)
@Override
public void destroy() {
System.out.println("Filter销毁");
}
}
Filter的生命周期:
| 阶段 | 次数 | 时机 |
|---|---|---|
| init | 1次 | 服务器启动时 |
| doFilter | 多次 | 每次请求 |
| destroy | 1次 | 服务器停止时 |
多个Filter的执行顺序:按Filter类名的字母顺序执行。如果需要控制顺序,可以在 web.xml 中配置顺序。
8.2 登录验证Filter实战
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {
// 不需要登录就能访问的路径
private static final Set<String> WHITE_LIST = Set.of(
"/login.html", "/login.do", "/register.html", "/register.do"
);
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 获取请求路径
String uri = request.getRequestURI();
String path = uri.substring(uri.lastIndexOf("/"));
// 白名单直接放行
if (WHITE_LIST.contains(path)) {
chain.doFilter(request, response);
return;
}
// 检查是否已登录(Session中有用户信息)
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
// 已登录,放行
chain.doFilter(request, response);
} else {
// 未登录,跳转到登录页
response.sendRedirect(request.getContextPath() + "/login.html");
}
}
}
8.3 Listener监听器
Listener可以监听Web应用中的各种事件,比如:
- Session的创建和销毁
- Session中属性的添加、删除、修改
- Request的创建和销毁
- ServletContext的创建和销毁
Session域监听器(最常用):
@WebListener
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session创建: " + se.getSession().getId());
// 可以在这里统计在线用户数
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session销毁: " + se.getSession().getId());
// 可以在这里清理用户相关数据
}
}
Session属性监听器:
@WebListener
public class MySessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println("Session添加属性: " + event.getName()
+ " = " + event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("Session删除属性: " + event.getName());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("Session修改属性: " + event.getName());
}
}
九、Ajax:页面不刷新的魔法
9.1 什么是Ajax?
Ajax(Asynchronous JavaScript And XML)是一种异步请求技术,可以在不刷新整个页面的情况下,与服务器交换数据并更新部分页面内容。
你刷微博的时候,往下拉自动加载新内容,页面并没有刷新——这就是Ajax在背后工作。
9.2 Ajax的核心对象
Ajax的核心是 XMLHttpRequest 对象:
// 1. 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
// 2. 配置请求(GET请求)
xhr.open("GET", "/api/user?id=1", true);
// 3. 设置回调函数
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 请求完成且成功
var data = xhr.responseText;
console.log(data);
}
};
// 4. 发送请求
xhr.send();
readyState状态说明:
| 值 | 状态 | 说明 |
|---|---|---|
| 0 | UNSENT | 未初始化 |
| 1 | OPENED | 已调用open |
| 2 | HEADERS_RECEIVED | 已收到响应头 |
| 3 | LOADING | 正在接收响应体 |
| 4 | DONE | 请求完成 |
9.3 Ajax实战:用户名校验
前端页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户名校验</title>
</head>
<body>
<form>
用户名:<input type="text" id="username" onblur="checkUsername()">
<span id="msg"></span>
</form>
<script>
function checkUsername() {
var username = document.getElementById("username").value;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/checkUsername.do?username=" + username, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var result = xhr.responseText;
var msgSpan = document.getElementById("msg");
if (result === "available") {
msgSpan.style.color = "green";
msgSpan.innerText = "用户名可用";
} else {
msgSpan.style.color = "red";
msgSpan.innerText = "用户名已存在";
}
}
};
xhr.send();
}
</script>
</body>
</html>
后端Servlet:
@WebServlet(urlPatterns = "/checkUsername.do")
public class CheckUsernameServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
// 模拟数据库查询
boolean exists = "admin".equals(username) || "root".equals(username);
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().write(exists ? "exists" : "available");
}
}
9.4 Ajax的状态码问题
在开发中,有时候Ajax请求会遇到状态码为0的情况。常见原因:
- 跨域请求被浏览器拦截
- 请求被取消(页面跳转、用户刷新)
- 网络不通(服务器未启动)
- 使用了file://协议(本地直接打开HTML文件)
十、请求转发和响应重定向
这是JavaWeb中两个非常重要的概念,面试经常问。
10.1 请求转发(Forward)
// 在Servlet中转发到另一个资源
request.getRequestDispatcher("/target.jsp").forward(request, response);
特点:
- 一次请求,浏览器地址栏不变
- 可以通过request域共享数据
- 只能转发到项目内部资源
10.2 响应重定向(Redirect)
// 重定向到另一个资源
response.sendRedirect("/项目名/target.jsp");
特点:
- 两次请求,浏览器地址栏会变
- 不能通过request域共享数据
- 可以重定向到外部资源
10.3 对比
| 对比项 | 请求转发 | 响应重定向 |
|---|---|---|
| 请求次数 | 1次 | 2次 |
| 地址栏 | 不变 | 变为新地址 |
| 数据共享 | 可以共享request域 | 不能共享 |
| 效率 | 高 | 低(多一次请求) |
| 范围 | 项目内部 | 可以到外部 |
一句话记忆:转发是"内部转手",重定向是"告诉你新地址,你自己再去"。
总结
回顾一下,JavaWeb的核心知识体系是这样的:
HTTP协议(通信规则)
↓
Tomcat(Web容器,管理Servlet生命周期)
↓
Servlet(处理请求的核心)
├── HttpServletRequest(获取请求信息)
├── HttpServletResponse(返回响应信息)
├── Cookie(客户端会话管理)
├── HttpSession(服务端会话管理)
├── ServletContext(全局共享)
├── Filter(请求拦截)
└── Listener(事件监听)
↓
JSP(动态页面,本质是Servlet)
↓
MVC模式(分层架构思想)
虽然现在开发中直接写Servlet的机会很少了,Spring Boot帮我们封装了太多东西。但理解这些底层知识,对排查问题、理解框架原理、应对面试都非常有帮助。
就像学开车,你不需要知道发动机的每个零件怎么工作,但至少要明白油门、刹车、方向盘的基本原理。JavaWeb就是Java Web开发的"基本原理"。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)