系统架构概述

一、系统架构的形式

系统架构主要包括以下两种形式:

  • C/S 架构(Client/Server)
  • B/S 架构(Browser/Server)

二、C/S 架构详解

1. 什么是 C/S 架构?

C/S 架构即客户端 / 服务器架构,是一种典型的两层结构。

2. 常见的 C/S 架构系统有哪些?

  • QQ
  • 微信

3. C/S 架构的特点

  • 需要安装特定的客户端软件。

4. C/S 架构的优缺点

✅ 优点:

  • 速度快:客户端本地存储大量数据,仅需少量网络传输。
  • 体验好:界面响应快,交互流畅。
  • 界面酷炫:可使用专业开发语言实现丰富界面。
  • 服务器压力小:大部分处理在客户端完成。
  • 安全性高:数据分散在多个客户端,即使服务器故障也不易丢失全部数据。

❌ 缺点:

  • 升级维护成本高:每个客户端都需要单独更新。
  • 部署复杂:部分软件安装过程繁琐。

三、B/S 架构详解

1. 什么是 B/S 架构?

B/S 架构即浏览器 / 服务器架构,用户通过浏览器访问服务器资源。

2. 常见的 B/S 架构系统示例

  • 百度:http://www.baidu.com
  • 京东:http://www.jd.com
  • 网易邮箱:http://www.126.com

3. B/S 是否属于 C/S 的一种?

是的,B/S 是一种特殊的 C/S 架构。其中,“客户端”是一个通用浏览器。

4. B/S 架构的优缺点

✅ 优点:

  • 升级维护方便:只需更新服务器端即可。
  • 无需安装客户端:用户只需打开浏览器访问网址即可操作。

❌ 缺点:

  • 速度较慢:所有数据都需从服务器获取,依赖网络带宽。
  • 用户体验较差:受限于 HTML/CSS/JS,界面表现力有限。
  • 安全性较低:所有数据集中在服务器,一旦服务器出问题,数据可能全部丢失。

四、C/S 与 B/S 架构哪个更好?

这个问题没有绝对答案,取决于具体业务场景:

场景 推荐架构
娱乐类软件(如社交、游戏) C/S 架构
公司内部管理系统(如OA、ERP) B/S 架构

原因说明:

  • 公司内部系统要求:
    • 升级维护成本低
    • 不需要特别炫酷的界面
    • 主要用于数据维护和流程管理

五、开发 B/S 系统所需技术

开发一个 B/S 系统,其实就是开发一个 Web 系统,通常包括以下几个方面:

1. Web 前端技术(运行在浏览器中)

  • HTML
  • CSS
  • JavaScript

2. Web 后端技术(运行在服务器上)

可以使用多种语言开发,例如:

  • Java(JavaWEB 开发,核心规范为 Servlet)
  • C
  • C++
  • Python
  • PHP

六、JavaEE 是什么?

Java 分为三大方向:

1. JavaSE(Java 标准版)

  • 提供基础类库和 API。
  • 是学习 JavaEE 和 JavaME 的基础。

2. JavaEE(Java 企业版)

  • 专为企业级应用设计的一套类库。
  • 支持 Web 系统开发,包含 13 种规范,Servlet 是其中之一。
  • 是目前 Java 最热门的方向之一。

3. JavaME(Java 微型版)

  • 适用于嵌入式设备开发,如:
    • 机顶盒
    • 吸尘器
    • 电冰箱
    • 电饭煲等小型电子设备

总结

架构类型 特点 适用场景
C/S 快速、体验好、界面炫、安全 娱乐类、客户端需求高的应用
B/S 易维护、无需安装、跨平台 企业内部系统、Web 应用

提示:选择架构时应结合项目需求、团队能力及长期维护等因素综合考虑。

B/S 结构的系统通信原理(不涉及 Java 小程序)

一、WEB 系统的访问过程

  1. 打开浏览器
  2. 找到地址栏
  3. 输入一个合法的网址
  4. 按下回车键
  5. 浏览器展示响应结果

二、关于域名

  • 示例网址https://www.baidu.com/
    • www.baidu.com 是一个域名
  • 在浏览器中输入域名后:
    • 域名解析器会将域名解析为具体的 IP 地址和端口号
  • 解析示例
    • http://110.242.68.3:80/index.html

三、IP 地址的作用

  • IP 地址是计算机在网络中的“身份证号”
  • 在同一个网络中,IP 地址具有唯一性
  • 要实现两台计算机之间的通信,必须知道对方的 IP 地址

四、端口号的作用

  • 一个端口代表一个软件或服务
  • 每个软件启动后都会占用一个端口号
  • 同一台计算机上,端口号具有唯一性

五、WEB 系统的通信原理与步骤

  1. 用户在浏览器中输入网址(URL)
  2. 域名解析器进行解析,得到完整的 URL(如 http://110.242.68.3:80/index.html
  3. 浏览器根据 IP 地址在网络上寻找对应的主机(如 110.242.68.3
  4. 定位该主机上的服务器软件(通过端口号 80)
  5. 服务器得知用户请求的资源名称(如 index.html
  6. 服务器查找并读取该资源文件
  7. 服务器将文件内容发送给浏览器(响应数据)
  8. 浏览器接收 HTML/CSS/JS 数据
  9. 浏览器渲染页面,执行代码,最终展示网页效果

六、什么是 URL?

  • 定义:统一资源定位符(Uniform Resource Locator)
  • 示例http://www.baidu.com

七、请求与响应的概念

类型 数据流向 英文术语 中文解释
请求 浏览器 → 服务器 request Browser 向 Server 发送数据
响应 服务器 → 浏览器 response Server 向 Browser 返回数据

关于Web服务器软件

常见的Web服务器软件

以下是一些常见的Web服务器软件(均为已开发完成的成熟产品):

  • Tomcat:轻量级Web服务器
  • Jetty:嵌入式Web服务器
  • JBoss:应用服务器
  • WebLogic:企业级应用服务器
  • WebSphere:IBM出品的企业级应用服务器

应用服务器与Web服务器的关系

  • 应用服务器实现了JavaEE平台中的所有13个规范。
  • Web服务器仅实现了Servlet和JSP两个核心规范。
  • 由此可见,应用服务器包含Web服务器
  • 例如,JBoss中就内嵌了一个Tomcat服务器。

Tomcat概述

  • 官网地址:
  • 特点:
    • 开源免费
    • 轻量级Web服务器
    • 使用Java语言编写
    • 运行需要JRE环境支持
  • 别名:Catalina
    • 名字来源于美国的一个风景优美的岛屿,寓意Tomcat服务器小巧、高效。
  • Logo:一只公猫,象征其轻巧、快速的特性。

Tomcat配置Java运行环境

Tomcat是基于Java编写的,因此运行前必须安装JDK并配置Java运行环境变量:

1
2
JAVA_HOME=C:\Program Files\Java\jdk-17.0.1
PATH=%JAVA_HOME%\bin;%CATALINA_HOME%\bin

💡 思考问题:如果没有配置JAVA_HOME是否可行?
虽然在某些情况下可以运行,但为了稳定性和兼容性,建议始终正确配置。

Tomcat安装方式

Tomcat为绿色版本,无需复杂安装,只需解压ZIP包即可使用。

推荐安装路径(示例):

  • C:\dev\tomcat:将所有开发工具集中管理,便于维护。

启动Tomcat

进入bin目录,执行以下命令启动Tomcat:

  • Windows系统startup.bat
  • Linux系统startup.sh

批处理文件说明

  • .bat 文件:Windows下的批处理脚本,可批量执行DOS命令。
  • .sh 文件:Linux下的Shell脚本,用于执行Shell命令。
  • Tomcat提供了跨平台的支持,适配多种操作系统。

启动流程解析

  1. startup.bat 实际调用了 catalina.bat
  2. catalina.bat 中定义了主类:
    1
    
    MAINCLASS=org.apache.catalina.startup.Bootstrap
    
  3. 该类中包含 main() 方法,即Tomcat启动入口。

环境变量要求

启动Tomcat需要配置以下环境变量:

1
2
3
JAVA_HOME=JDK安装目录
CATALINA_HOME=Tomcat安装目录
PATH=%JAVA_HOME%\bin;%CATALINA_HOME%\bin

Tomcat目录结构详解

目录名 用途说明
bin/ 存放启动、关闭等命令脚本(如 startup.bat, shutdown.sh
conf/ 配置文件目录,如 server.xml 可修改端口号等设置
lib/ 核心类库目录,存放Tomcat运行所需的JAR文件
logs/ 日志文件目录,记录服务器运行日志
temp/ 临时文件存储目录
webapps/ Web应用部署目录,放置 .war 包或项目文件夹
work/ JSP编译后生成的Java文件和 .class 文件

Tomcat启动与关闭

  • 启动:执行 startup.batstartup.sh
  • 关闭:执行 shutdown.bat(建议重命名为 stop.bat,避免与系统关机命令冲突)

测试Tomcat是否启动成功

打开浏览器访问以下任一地址:

1
2
http://127.0.0.1:8080
http://localhost:8080

如果看到Tomcat欢迎页面,则表示服务器已成功启动。

实现一个最基本的 Web 应用(该应用中不包含 Java 小程序)

一、准备工作

在开始之前,请确保你已经安装并配置好了 Tomcat 服务器,并设置了 CATALINA_HOME 环境变量。


二、步骤详解

第一步:定位到 webapps 目录

  • 找到 Tomcat 的 webapps 目录:

    1
    
    CATALINA_HOME\webapps
    

⚠️ 注意:所有 Web 应用都必须放置在该目录下,这是 Tomcat 的规定。否则服务器将无法识别你的应用。


第二步:创建一个新的 Web 应用目录

  • webapps 目录下新建一个子目录,命名为 oa

📁 说明:目录名 oa 即为你的 Web 应用名称。


第三步:添加资源文件

  • oa 目录下创建资源文件,例如 index.html
  • 编写 HTML 文件内容以展示页面信息。

第四步:启动 Tomcat 服务器

  • 启动 Tomcat,等待服务器完成部署。

第五步:访问你的 Web 应用

  • 打开浏览器,在地址栏输入以下 URL:
1
http://127.0.0.1:8080/oa/index.html

三、关于 URL 访问的思考

浏览器直接输入 URL 与超链接的区别?

当你在浏览器地址栏输入一个 URL 并回车时,其本质与点击一个超链接是一样的。因此,我们可以在网页中使用超链接来实现相同的效果。

示例代码:

1
2
3
4
5
<!-- 绝对路径的写法(推荐),以 / 开头,包含项目名 -->
<a href="/oa/login.html">用户登录</a>

<!-- 多级目录访问也无问题 -->
<a href="/oa/test/debug/d.html">d 页面</a>

✅ 提示:前端路径建议统一使用绝对路径,格式为 /项目名/资源路径,无需添加协议和域名。


四、静态资源与动态资源的区别

静态资源示例:

1
http://127.0.0.1:8080/oa/userList.html
  • 当前这个页面是静态 HTML 文件,数据是“硬编码”写死在文件中的。
  • 这类资源被称为 静态资源

动态资源的需求:

  • 如果希望页面上的数据能够根据数据库中的内容变化而自动更新,则需要引入动态网页技术。
  • 动态网页不是指有动画效果的页面,而是指页面的数据是动态生成的。

如何实现动态网页?

  • 使用 JDBC 技术连接数据库。
  • 编写 Java 程序从数据库中读取数据。
  • 根据查询结果动态生成 HTML 页面内容。

💡 总结:动态网页的关键在于“数据可变”,通常依赖于后端逻辑和数据库交互。

动态 Web 应用中请求与响应过程涉及的角色与协议

BS结构系统的通信原理2

一、参与的角色

在一个典型的 B/S(Browser/Server)架构系统中,一个完整的请求与响应过程会涉及到多个角色的协同工作。具体包括以下几个开发团队及其所代表的技术角色:

1. 浏览器软件开发团队

  • 负责开发浏览器程序。
  • 常见浏览器包括:
    • Google Chrome
    • Mozilla Firefox
    • Microsoft Edge / Internet Explorer
    • Safari 等

2. Web Server 开发团队

  • 提供 Web 容器服务,负责接收 HTTP 请求并处理静态资源或转发给 Web 应用处理。
  • 常见 Web Server 包括:
    • Apache Tomcat
    • Jetty
    • Oracle WebLogic
    • JBoss
    • IBM WebSphere 等

3. DB Server 开发团队

  • 提供数据库管理系统,用于存储和管理数据。
  • 常见数据库系统包括:
    • Oracle
    • MySQL
    • PostgreSQL
    • SQL Server 等

4. Web 应用开发团队(Java Web 开发者)

  • 编写实际业务逻辑的 Web 应用程序(WebApp),部署在 Web Server 上运行。

二、角色之间的规范与协议

为了保证各个角色之间能够有效协作,需要遵循一系列标准和协议。

1. Web App 开发团队 与 Web Server 开发团队之间

  • 规范:Servlet 规范(属于 JavaEE 标准的一部分)
  • 作用
    • 实现 Web Server 与 Web 应用之间的解耦合。
    • 确保 Web 应用可以在不同的 Web Server 中兼容运行。

Servlet 规范包含哪些内容?

  • 规定了哪些接口和类必须存在。
  • 规定了 Web 应用应包含的配置文件:
    • 配置文件名称(如 web.xml
    • 存放路径(如 /WEB-INF/
    • 文件内容格式
  • 规定了 Web 应用的标准目录结构。
  • 其他与 Web 应用生命周期、请求处理相关的规范。

2. 浏览器(Browser) 与 Web Server 之间

  • 协议:HTTP 协议(HyperText Transfer Protocol)
  • 作用
    • 浏览器向服务器发起请求。
    • 服务器返回 HTML 页面或其他资源。
    • 是浏览器与服务器之间通信的基础协议。

3. Web App 开发团队 与 DB Server 开发团队之间

  • 规范:JDBC(Java Database Connectivity)
  • 作用
    • 提供统一的 API 接口,使 Java 程序可以访问各种数据库。
    • 实现数据库操作的标准化和可移植性。

BS结构系统的角色和协议


三、Servlet 规范详解

1. 什么是 Servlet 规范?

  • 是 JavaEE 标准中定义的一套编程规范。
  • 只要 Web 应用遵循该规范编写,就可以部署在任何支持该规范的 Web Server 上运行。

2. Servlet 规范的主要内容

内容类型 描述
接口与类 定义了 Servlet, HttpServlet, ServletRequest, ServletResponse 等核心接口和类
配置文件 指定 web.xml 的格式、位置和内容
目录结构 明确 Web 应用的标准目录结构(如 WEB-INF, classes, lib 等)
生命周期 规定 Servlet 的初始化、服务、销毁等生命周期方法
请求处理 规定如何处理 HTTP 请求(GET、POST 等)

开发一个带有 Servlet 的 Web 应用(重点)

一、开发步骤

第一步:创建 Web 应用的根目录

webapps 目录下新建一个项目文件夹,例如:

  • crm(客户关系管理系统)
  • bank(银行系统)
  • oa(办公自动化系统)

注意:该目录即为 Web 应用的“根目录”。


第二步:创建 WEB-INF 目录

在项目根目录下新建一个名为 WEB-INF 的子目录。

注意

  • 名称必须全大写,不能更改。
  • 该目录是 Servlet 规范中规定的标准目录。

第三步:创建 classes 子目录

WEB-INF 下创建 classes 目录。

注意

  • 名称必须小写。
  • 用于存放 Java 编译后的 .class 文件(字节码)。

第四步:创建 lib 子目录(可选)

WEB-INF 下创建 lib 目录。

注意

  • 名称必须小写。
  • 用于存放第三方 JAR 包(如数据库驱动等)。
  • 并非必须存在。

第五步:创建 web.xml 配置文件

WEB-INF 目录下创建 web.xml 文件。

注意

  • 必须存在。
  • 文件名必须为 web.xml
  • 用于配置请求路径与 Servlet 类之间的映射关系。
  • 推荐从已有项目复制模板,避免手动编写。

示例模板:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="true">
</web-app>

第六步:编写 Servlet 程序

创建一个 Java 类,实现 jakarta.servlet.Servlet 接口。

注意

  • 该接口属于 Jakarta EE 规范,不在 JDK 中。
  • Tomcat 提供了对应的 API(位于 servlet-api.jar 中)。
  • 从 Jakarta EE 9 开始,包名从 javax.servlet 变更为 jakarta.servlet
  • 源代码位置无特殊要求,只需确保编译后的 .class 文件放入 WEB-INF/classes 目录即可。

第七步:编译 Servlet

设置环境变量 CLASSPATH,以便编译时能找到 Servlet 接口。

1
CLASSPATH=.;C:\dev\apache-tomcat-10.0.12\lib\servlet-api.jar

注意

  • 该设置仅用于编译,不影响 Tomcat 运行时行为。

第八步:将编译后的 .class 文件拷贝到 WEB-INF/classes

HelloServlet.class 文件复制到 WEB-INF/classes 目录中。


第九步:在 web.xml 中注册 Servlet

web.xml 中添加 <servlet><servlet-mapping> 标签,完成 URL 路径与类的绑定。

示例配置:

1
2
3
4
5
6
7
8
9
<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.bjpowernode.servlet.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

说明

  • <servlet-name> 必须一致。
  • <url-pattern> 必须以 / 开头。

第十步:启动 Tomcat 服务器

运行 Tomcat 启动脚本,启动服务器。


第十一步:访问 Servlet

在浏览器中输入完整的 URL 地址访问:

1
http://127.0.0.1:8080/crm/hello

注意

  • 请求路径需包含项目名(如 /crm)。
  • 必须与 web.xml 中的 <url-pattern> 完全一致。

二、HTML 页面的放置位置

HTML、CSS、JS、图片等静态资源应放在 WEB-INF 外部。

原因

  • WEB-INF 目录受保护,外部无法直接访问其内容。

三、Web 应用目录结构示例

1
2
3
4
5
6
7
8
9
crm/
├── WEB-INF/
│   ├── classes/              # 存放编译后的 .class 文件
│   ├── lib/                  # 存放第三方 jar 包(可选)
│   └── web.xml               # 配置文件
├── index.html                # 静态页面
├── css/
├── js/
└── images/

四、Servlet 执行流程简述

  1. 用户在浏览器输入 URL:http://127.0.0.1:8080/crm/hello
  2. Tomcat 截取路径 /crm/hello,定位到项目 crm
  3. Tomcat 在 web.xml 中查找 /hello 对应的 Servlet 类
  4. Tomcat 通过反射机制创建 Servlet 实例
  5. Tomcat 调用该实例的 service() 方法处理请求

五、总结

  • Web 应用的结构需符合 Servlet 规范。
  • WEB-INF 是核心目录,包含配置文件和类文件。
  • 编写 Servlet 需要引入 servlet-api.jar
  • 不需要自己编写 main 方法,由 Tomcat 负责调用。
  • 请求路径必须与 web.xml 中的 <url-pattern> 保持一致。

如需进一步了解 Jakarta EE 规范和相关 XML Schema,请参考官方文档:

关于 JavaEE 的版本演进

一、JavaEE 的发展历史

  • 最高版本:JavaEE 的最终版本为 Java EE 8
  • 归属变更:Oracle 宣布将 JavaEE 规范捐献给 Apache 基金会。

二、名称变更与后续发展

  • 品牌迁移:Apache 接手后,不再使用 “JavaEE” 这一名字
  • 新名称:新的企业级 Java 规范被称为 Jakarta EE
  • 未来命名规则:从 Java EE 8 之后的版本开始,统一以 Jakarta EE + 版本号 形式命名。

三、版本对应关系与包名变化

JavaEE 版本 对应 Jakarta EE 版本 Servlet 包路径
Java EE 8 - javax.servlet.Servlet
- Jakarta EE 9 jakarta.servlet.Servlet

⚠️ 注意
Jakarta EE 9 及以后版本中,所有类的包名都由 javax 改为了 jakarta。这意味着:

  • 使用 javax.servlet.Servlet 的旧项目无法直接部署在支持 Jakarta EE 的服务器上(如 Tomcat 10+)。
  • 若需兼容性支持,请使用 Tomcat 9 或更早版本

四、部署建议

  • 如果你的项目仍在使用 javax 包:
    • 可部署环境:Tomcat 9 及其以下版本。
    • 不可部署环境:Tomcat 10 及以上版本。
  • 如需迁移到 Tomcat 10+,请将代码中的 javax 包替换为 jakarta

解决Tomcat服务器在DOS命令窗口中的乱码问题(控制台乱码)

将CATALINA_HOME/conf/logging.properties文件中的内容修改如下:

1
java.util.logging.ConsoleHandler.encoding = GBK

向浏览器响应一段HTML代码

1
2
3
4
5
public void service(ServletRequest request, ServletResponse response){
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.print("<h1>hello servlet!</h1>");
}

在Servlet中连接数据库,怎么做?

  • Servlet是Java程序,所以在Servlet中完全可以编写JDBC代码连接数据库。
  • 在一个webapp中去连接数据库,需要将驱动jar包放到WEB-INF/lib目录下。(com.mysql.cj.jdbc.Driver 这个类就在驱动jar包当中。)

在集成开发环境(IDE)中开发 Servlet 程序

一、常用的集成开发工具简介

目前主流的 Java 集成开发工具有两种:

1. IntelliJ IDEA

  • 使用人数较多,提示功能优于 Eclipse。
  • 更加智能,用户体验更佳。
  • 由 JetBrains 公司开发。
  • 商业软件(收费)

2. Eclipse

  • 使用人数相对较少,但仍有一定用户基础。
  • 正处于逐渐被替代的趋势。
  • 由 IBM 团队开发。
  • 名称寓意为“日食”,象征意图吞并 SUN 公司。
  • 实际上,SUN 公司于 2009 年被 Oracle 收购,IBM 并未实现并购目标。

二、使用 IntelliJ IDEA 开发 Servlet 的步骤

第一步:创建空项目(Empty Project)

  • 打开 IDEA → FileNewProject
  • 选择 Empty Project
  • 命名项目为:javaweb(非强制,建议与目录一致)
  • 可选操作:后续在此项目下添加模块(Module)

第二步:新建模块(Module)

  • FileNewModule
  • 创建一个普通的 JavaSE 模块(不要选 Java Enterprise)
  • 模块名建议为:servlet01

第三步:将模块转换为 Web Application

  • 右键点击模块 → Add Framework Support...
  • 选择 Web Application
  • IDEA 将自动生成符合 Servlet 规范的 webapp 目录结构
  • 注意:web 目录即为 webapp 的根目录

第四步(可选):删除默认生成的 index.jsp 文件

  • 根据需要删除模板生成的 index.jsp 文件

第五步:编写 Servlet 类

示例类名:StudentServlet

1
class StudentServlet implements Servlet
  • 如果出现找不到 Servlet.class 的错误:

    • 进入 FileProject StructureModules
    • 点击 + 添加 JAR 包
    • 添加 Tomcat 中的 servlet-api.jarjsp-api.jar 到 classpath
  • 实现 jakarta.servlet.Servlet 接口中的五个方法

第六步:在 service 方法中编写业务逻辑

  • 可以在这里添加数据库连接等具体业务代码

第七步:添加数据库驱动包

  • WEB-INF 下新建目录:lib(必须全小写)
  • 将数据库驱动 JAR 包放入此目录

第八步:配置 web.xml 注册 Servlet

文件路径:WEB-INF/web.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?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">

    <servlet>
        <servlet-name>studentServlet</servlet-name>
        <servlet-class>com.bjpowernode.javaweb.servlet.StudentServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>studentServlet</servlet-name>
        <url-pattern>/servlet/student</url-pattern>
    </servlet-mapping>

</web-app>

第九步:创建 HTML 页面用于测试

  • 文件名:student.html
  • 必须放在 WEB-INF 外部
  • 示例内容如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生页面</title>
</head>
<body>
    <!-- 注意:项目名为 /xmm,这里先写死 -->
    <a href="/xmm/servlet/student">查看学生列表</a>
</body>
</html>

第十步:配置 IDEA 关联 Tomcat 并部署项目

  • 点击右上角的绿色锤子旁的按钮:Add Configuration
  • 点击左上角的 +Tomcat ServerLocal
  • 设置服务器参数(一般无需修改)
  • 切换到 Deployment 选项卡
    • 点击 + 添加部署
    • 设置 Application context/xmm

第十一步:启动 Tomcat 服务器

  • 点击右上角绿色箭头或虫子图标(Debug 启动)
  • 推荐使用 Debug 模式启动 Tomcat,方便调试

第十二步:访问测试页面

  • 打开浏览器,输入地址:
1
http://localhost:8080/xmm/student.html

三、总结

通过上述步骤,我们完成了在 IntelliJ IDEA 中开发一个简单 Servlet 应用的全过程。包括项目创建、Servlet 编写、配置部署以及最终的测试运行。整个流程遵循标准的 Java Web 开发规范,适合初学者快速入门。

Servlet 对象的生命周期

一、什么是 Servlet 生命周期?

Servlet 的生命周期是指一个 Servlet 对象从创建到销毁的全过程。主要包括以下几个阶段:

  • 何时被创建?
  • 何时被销毁?
  • 创建了多少个实例?
  • 整个过程是怎样的?

二、Servlet 生命周期由谁管理?

  • Servlet 对象的创建、方法调用和销毁均由 Tomcat服务器(WEB Server) 全权负责。
  • Tomcat 又被称为 WEB 容器(Web Container)
  • 只有容器中管理的 Servlet 对象才能被正常调用。
  • 自己手动 new 出来的 Servlet 对象 不会被 WEB 容器管理

关键点:

  • Web 容器内部通常使用一个类似 HashMap 的集合来维护 Servlet 对象与请求路径之间的映射关系。
  • 示例图:WEB容器中的Map集合

三、默认情况下,服务器启动时是否创建 Servlet 对象?

  • 默认情况下,Servlet 在服务器启动时不会被实例化。
  • 构造方法不会执行。
  • 这种设计合理:避免浪费内存资源,未访问的 Servlet 不必提前创建。

四、如何让服务器在启动时创建 Servlet?

可以通过配置 web.xml 文件中的 <load-on-startup> 标签实现:

1
2
3
4
5
6
7
8
9
<servlet>
    <servlet-name>aservlet</servlet-name>
    <servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>aservlet</servlet-name>
    <url-pattern>/a</url-pattern>
</servlet-mapping>
  • 数值越小,优先级越高。
  • 此配置表示该 Servlet 应在服务器启动时加载并初始化。

五、Servlet 生命周期详解

1. 第一次请求发生时的行为

控制台输出如下:

1
2
3
AServlet无参数构造方法执行了
AServlet's init method execute!
AServlet's service method execute!

结论:

  • 用户第一次发送请求时,Servlet 被实例化(调用了无参构造方法)。
  • 实例化后立即调用 init() 方法。
  • 紧接着调用 service() 方法处理请求。

2. 后续请求行为

用户再次发送请求时,控制台输出如下:

1
AServlet's service method execute!

结论:

  • Servlet 是单例对象(但不是真正的单例模式)。
  • service() 方法每次请求都会被调用。
  • 构造方法init() 只会在第一次请求时执行一次。

3. 服务器关闭时的行为

控制台输出如下:

1
AServlet's destroy method execute!

结论:

  • destroy() 方法只执行一次,在服务器关闭前被调用。
  • 用于释放资源、保存数据等操作。
  • destroy() 执行完毕后,Servlet 对象才被销毁。

4. 生命周期类比人生阶段

方法 比喻
构造方法 出生
init() 接受教育
service() 工作服务
destroy() 临终

六、Servlet 方法执行次数总结

方法 执行次数
构造方法 1次(仅第一次请求)
init() 1次(仅第一次请求)
service() 每次请求都执行
destroy() 1次(服务器关闭时)

七、常见问题与注意事项

1. 如果自定义有参构造方法而没有无参构造方法会发生什么?

  • 报错:HTTP 500 错误(服务器内部错误)
  • 原因:Servlet 容器只能通过无参构造方法创建对象
  • 建议:不要在 Servlet 中定义构造方法

2. 能否用构造方法代替 init() 方法?

  • 不能。
  • 虽然两者都只执行一次,但构造方法可能影响 Servlet 实例化。
  • init() 更适合做初始化操作(如连接池、线程池初始化)

3. 哪些方法最常被使用?

  • service() 方法最重要,必须重写。
  • init()destroy() 方法较少使用,但可用于资源初始化和清理。

GenericServlet 概述

一、直接实现 Servlet 接口的缺点

当我们编写一个 Servlet 类直接实现 Servlet 接口时,存在以下问题:

  • 只关心 service() 方法,而其他方法(如 init()destroy() 等)在大多数情况下并不需要使用。
  • 导致代码冗余且不够优雅。

二、适配器设计模式(Adapter Pattern)

示例:充电器作为适配器

  • 手机无法直接连接 220V 电压,否则会损坏。
  • 使用一个充电器作为中间适配器:
    • 手机连接充电器;
    • 充电器连接电源;
    • 实现安全供电。

在 Servlet 中的应用

  • GenericServlet 是一个实现了 Servlet 接口的抽象类;
  • 它充当了一个“适配器”,屏蔽了不需要频繁重写的接口方法;
  • 用户只需要关注并重写 service() 方法即可。

三、GenericServlet 的作用与使用方式

1. GenericServlet 的特点

  • 是一个抽象类
  • 实现了 Servlet 接口;
  • 提供了一个抽象的 service() 方法
  • 其他方法(如 init()destroy())提供默认空实现。

2. 使用方式

  • 开发者只需继承 GenericServlet
  • 并重写其 service() 方法;
  • 无需处理不相关的生命周期方法。

四、关于 GenericServlet 的几个思考问题

1. init() 方法还会执行吗?

  • 是的,会执行。
  • 执行的是 GenericServlet 类中的 init() 方法。

2. init() 方法是谁调用的?

  • 是由 Tomcat 服务器自动调用的

3. ServletConfig 对象是谁创建并传入的?

  • 是由 Tomcat 服务器负责创建并传递init() 方法的。

五、Tomcat 服务器伪代码示例

以下是一个简化的 Tomcat 启动和调用 Servlet 的伪代码,帮助理解整个过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tomcat {
    public static void main(String[] args) {
        // Tomcat服务器伪代码

        // 加载LoginServlet类
        Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
        Object obj = clazz.newInstance();

        // 向下转型为Servlet类型
        Servlet servlet = (Servlet) obj;

        // 创建ServletConfig对象(由Tomcat内部实现)
        ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();

        // 调用init方法
        servlet.init(servletConfig);

        // 调用service方法
        // ...
    }
}

通过上述分析可以看出,GenericServlet 为我们简化了 Servlet 编程模型,提高了开发效率,同时遵循了“适配器”设计模式的思想。

ServletConfig 概述

1. 什么是 ServletConfig?

  • ServletConfig 是一个用于封装 Servlet 对象配置信息 的对象。
  • 它主要用来保存在 web.xml 文件中 <servlet> 标签内定义的配置信息。

2. 与 Servlet 的关系

  • 每一个 Servlet 都有唯一一个对应的 ServletConfig 对象
  • 该对象由 Tomcat(Web)服务器创建,默认情况下,在用户发送第一次请求时创建。

3. 初始化过程

  • Tomcat 在调用 Servletinit() 方法时,会将 ServletConfig 对象作为参数传递进去:

    1
    
    public void init(ServletConfig config) throws ServletException
    

4. 实现细节

  • ServletConfig 接口的具体实现类是由 Tomcat 服务器提供的
  • 因为 GenericServlet 类已经实现了 ServletConfig 接口,所以在自定义的 Servlet 中可以直接使用 this 来调用其方法。

5. 常用方法

以下是一些常用的 ServletConfig 接口方法:

方法名 描述
String getInitParameter(String name) 通过初始化参数的名称获取对应的值
Enumeration<String> getInitParameterNames() 获取所有初始化参数的名称集合
ServletContext getServletContext() 获取当前 Servlet 所属的 ServletContext 对象
String getServletName() 获取当前 Servlet 的名称(即 <servlet-name> 的值)

这些方法可以在 Servlet 类中直接使用,例如:

1
String value = this.getInitParameter("paramName");

ServletContext 详解

一、基本概念

1. 什么是 ServletContext?

  • 每个 Web 应用(webapp)只有一个 ServletContext 对象。
  • 所有该应用中的 Servlet 共享同一个 ServletContext
  • 它在服务器启动时创建,在服务器关闭时销毁,是应用级对象

2. 类比生活场景

  • 可将 ServletContext 比作一个“教室”:
    • 教室中有多个学生(Servlet);
    • 教室中有一个空调(共享资源),所有学生都可以使用;
    • 这个“教室”就是 ServletContext,它保存的是整个应用的共享环境与数据。

3. 接口实现

  • ServletContext 是一个接口;
  • Tomcat 等服务器对其进行了实现;
  • 启动 Web 应用时由服务器自动创建。

二、配置与初始化参数

1. 获取初始化参数

web.xml 中通过 <context-param> 标签配置全局参数,可通过以下方法获取:

1
2
public String getInitParameter(String name); // 根据参数名获取值
public Enumeration<String> getInitParameterNames(); // 获取所有参数名

示例配置:

1
2
3
4
5
6
7
8
<context-param>
    <param-name>pageSize</param-name>
    <param-value>10</param-value>
</context-param>
<context-param>
    <param-name>startIndex</param-name>
    <param-value>0</param-value>
</context-param>

⚠️ 注意:这些是应用级配置信息。若只想为某个 Servlet 配置参数,请使用 <servlet> 标签内的配置,并通过 ServletConfig 获取。


三、常用方法

1. 获取应用根路径

1
public String getContextPath();

用于动态获取当前 Web 应用的上下文路径,避免硬编码。

2. 获取文件真实路径

1
public String getRealPath(String path);

将虚拟路径转换为服务器上的绝对路径。

3. 记录日志

1
2
public void log(String message);
public void log(String message, Throwable t);

日志记录到如下位置(Tomcat):

  • logs/localhost.<date>.log:记录 ServletContext.log() 的输出;
  • logs/catalina.<date>.log:记录服务器控制台输出;
  • logs/localhost_access_log.<date>.txt:记录访问日志。

4. 应用域(Application Scope)

ServletContext 也称为“应用域”,适用于以下情况的数据存储:

  • 所有用户共享
  • 数据量小
  • 几乎不修改

存储与读取数据的方法:

1
2
3
setAttribute(String name, Object value); // 存数据(类似 map.put)
getAttribute(String name);             // 取数据(类似 map.get)
removeAttribute(String name);          // 删除数据(类似 map.remove)

✅ 优点:提高执行效率,减少数据库访问;
❌ 缺点:占用内存时间长,不适合大数据或频繁修改的数据。


四、Servlet 继承体系简介

我们通常不会直接继承 GenericServlet,而是使用其子类 HttpServlet 来处理 HTTP 请求。

继承结构如下:

1
2
3
jakarta.servlet.Servlet(接口)【爷爷】
└── jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
    └── jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】

使用建议:

  • 我们编写的 Servlet 类应继承 HttpServlet
  • 更适合处理基于 HTTP 协议的请求。

五、缓存机制回顾

缓存类型 描述
字符串常量池 存放 "abc" 等字符串,避免重复创建
整数型常量池 范围 [-128, 127] 的 Integer 对象
连接池(Connection Pool) 提前创建多个数据库连接对象,提升访问效率
线程池(Thread Pool) 提前创建多个线程,处理并发请求
Redis NoSQL 缓存数据库,适用于分布式系统
ServletContext 应用域 存放应用级共享数据,也是一种缓存机制

总结

  • ServletContext 是 Web 应用的核心对象之一;
  • 生命周期与应用一致;
  • 适合存放只读、小数据量、共享的信息;
  • 提供了丰富的 API 方法,如获取路径、记录日志、操作域属性等;
  • 在实际开发中,结合其他缓存机制可显著提升系统性能。

HTTP 协议详解

一、什么是协议?

  • 定义

    • 协议是由某些人或组织制定的一套规范,所有人都按照这套规范进行交流,以实现沟通无障碍。
    • 协议就是标准,是一种大家共同遵守的规则。
  • 举例说明

    • 比如我们使用的“普通话”,就是一种语言协议。我们都使用普通话交流,彼此就能听懂对方的话。

二、什么是 HTTP 协议?

1. 简介

  • HTTP 协议:是 W3C 制定的一种超文本传输协议。
  • 作用:用于浏览器(B)与服务器(S)之间的数据交互。
  • 特点
    • 支持传输普通文本、图片、音频、视频等流媒体信息。
    • 浏览器向服务器发送请求时遵循该协议,服务器返回响应也需遵循该协议。

2. W3C 组织简介

  • W3C
    • 全称:万维网联盟
    • 职责:制定 Web 相关标准(如 HTML、XML、HTTP、DOM 等)
    • 创始人:蒂姆·伯纳斯·李(Tim Berners-Lee)

3. 超文本(Hypertext)

  • 不只是普通的文字,还可以包含图像、声音、视频等多媒体内容。
  • HTTP 支持这些内容的传输。

4. 解耦合机制

  • 浏览器不依赖具体服务器品牌。
  • 服务器也不依赖具体浏览器品牌。
  • 实现了 B/S 架构的松耦合通信。

三、HTTP 请求协议(Browser → Server)

1. 结构组成

HTTP 请求由四部分组成:

部分 描述
请求行 包括请求方式、URI 和协议版本号
请求头 包含客户端的各种附加信息
空白行 分隔请求头与请求体
请求体 发送给服务器的数据(POST 特有)

2. 示例:GET 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/xhtml+xml,...
...
Referer: http://localhost:8080/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

[空行]

3. 示例:POST 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
POST /servlet05/postServlet HTTP/1.1
Host: localhost:8080
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 ...
Accept: text/html,...
...
Referer: http://localhost:8080/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

[空行]
username=lisi&userpwd=123

4. 各组成部分详解

(1)请求行(Request Line)

  • 包括三部分:
    • 请求方式(7种常见):
      • GET(常用)
      • POST(常用)
      • DELETEPUTHEADOPTIONSTRACE
    • URI(统一资源标识符):
      • 表示资源名称,但不能定位资源。
    • URL(统一资源定位符):
      • 包含 URI,同时可以定位资源。
      • 示例:
        • URL:http://localhost:8080/servlet05/index.html
        • URI:/servlet05/index.html
    • 协议版本号
      • HTTP/1.1

(2)请求头(Headers)

  • 包含客户端信息,例如:
    • 主机名和端口
    • 浏览器类型
    • 平台信息
    • Cookie 数据等

(3)空白行(Empty Line)

  • 用来分隔请求头和请求体。

(4)请求体(Body)

  • 只在 POST 请求中出现。
  • 存放要发送给服务器的具体数据。

四、HTTP 响应协议(Server → Browser)

1. 结构组成

HTTP 响应同样由四部分组成:

部分 描述
状态行 包括协议版本、状态码和描述
响应头 包含服务器返回的各种附加信息
空白行 分隔响应头与响应体
响应体 返回给浏览器的具体内容

2. 示例:响应报文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 160
Date: Mon, 08 Nov 2021 13:19:32 GMT
Keep-Alive: timeout=20
Connection: keep-alive

[空行]
<!doctype html>
<html>
<head><title>from get servlet</title></head>
<body><h1>from get servlet</h1></body>
</html>

3. 各组成部分详解

(1)状态行(Status Line)

  • 包括三部分:
    • 协议版本号
    • 状态码
      • 200:成功
      • 404:资源未找到(前端错误)
      • 405:请求方法不支持
      • 500:服务器内部错误
      • 4xx 开头:客户端错误
      • 5xx 开头:服务端错误
    • 状态描述
      • OKNot FoundInternal Server Error

(2)响应头(Headers)

  • 包括:
    • 内容类型(Content-Type)
    • 内容长度(Content-Length)
    • 响应时间(Date)
    • 连接方式(Connection)等

(3)空白行(Empty Line)

  • 分隔响应头和响应体。

(4)响应体(Body)

  • 是服务器返回的具体内容,如 HTML 页面、JSON 数据等。

五、如何查看 HTTP 协议内容?

  • 使用 Chrome 浏览器:
    • 按下 F12 打开开发者工具
    • 切换到 Network 标签页
    • 刷新页面即可查看每个请求的详细协议内容

六、GET 与 POST 请求的区别

对比项 GET 请求 POST 请求
数据位置 在请求行中(URI后) 在请求体中
安全性 安全(仅获取数据) 不安全(提交数据)
缓存支持 支持缓存 不支持缓存
数据长度限制 有限制(不同浏览器不同) 无限制
数据类型 只能发送普通字符串 可以发送任何类型数据(包括文件、图片等)
用途建议 获取服务器资源 向服务器提交数据
敏感信息暴露 会暴露在地址栏 不暴露

七、GET 和 POST 的使用场景

  • GET 请求适用场景

    • 获取数据
    • 不涉及敏感信息
    • 不需要修改服务器资源
    • 需要缓存结果
  • POST 请求适用场景

    • 提交表单数据
    • 上传文件
    • 修改服务器资源
    • 传递敏感信息(如密码)

八、请求数据格式统一

  • 不论是 GET 还是 POST 请求,数据格式都是统一的:
    1
    
    name=value&name=value&name=value
    
  • name:通常是表单字段的 name 属性
  • value:对应字段的输入值

总结

HTTP 是现代 Web 应用中最基础的通信协议,通过统一的请求和响应格式,实现了浏览器与服务器之间的高效、标准化通信。GET 和 POST 是最常用的两种请求方式,各有其适用场景。掌握它们的特点和区别,有助于更好地开发和调试 Web 应用。

模板方法设计模式

一、什么是设计模式?

  • 设计模式是针对某一类常见问题的标准化解决方案
  • 它具有可重用性、可维护性和可扩展性

二、常见的设计模式分类

1. GoF 设计模式(Gang of Four)

  • 定义:由四位软件工程专家提出的23种经典设计模式。
  • 常见模式包括
    • 单例模式
    • 工厂模式
    • 代理模式
    • 门面模式
    • 责任链模式
    • 观察者模式
    • 模板方法模式
    • ……(还有其他多种)

2. JavaEE 设计模式

  • 主要用于企业级开发中数据与业务逻辑的分离
  • 常见模式包括
    • DAO(Data Access Object)
    • DTO(Data Transfer Object)
    • VO(Value Object)
    • PO(Persistent Object)
    • POJO(Plain Old Java Object)
    • ……

3. 其他设计模式

  • 还有架构模式、行为模式等不同分类,适用于不同的场景。

三、模板方法设计模式详解

1. 定义

模板类的模板方法中定义核心算法骨架,而具体的实现步骤延迟到子类中完成。

2. 核心特点

  • 模板类通常为抽象类
  • 模板方法中定义了核心算法流程,该方法通常是 final 的(防止被重写)。
  • 抽象方法则代表不确定实现的具体步骤,需由子类根据需求实现。

3. 示例说明(简要)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
abstract class TemplateClass {
    // 模板方法,定义算法骨架
    public final void templateMethod() {
        stepOne();
        stepTwo();
        stepThree();
    }

    // 抽象方法,延迟到子类实现
    protected abstract void stepOne();
    protected abstract void stepTwo();
    protected abstract void stepThree();
}

四、总结

  • 模板方法模式通过封装算法流程,使得具体实现可灵活扩展
  • 是一种行为型设计模式,常用于框架设计中,提供统一接口的同时允许子类定制实现。

HttpServlet 源码分析

一、HttpServlet 简介

  • HttpServlet 是专为 HTTP 协议设计的 Servlet 类,相较于 GenericServlet 更适合 HTTP 协议下的开发。
  • 包路径
    • jakarta.servlet.http.HttpServlet

二、Servlet 规范中的主要接口与类

我们已接触的接口(jakarta.servlet.*)

类/接口 说明
Servlet 核心接口
ServletConfig 配置信息接口
ServletContext 上下文接口
ServletRequest 请求接口
ServletResponse 响应接口
ServletException 异常类
GenericServlet 通用抽象类

HTTP 包中的类与接口(jakarta.servlet.http.*)

类/接口 说明
HttpServlet HTTP 专用的抽象类
HttpServletRequest HTTP 请求对象
HttpServletResponse HTTP 响应对象

三、HttpServletRequest 和 HttpServletResponse

HttpServletRequest

  • 又称 request 对象
  • 封装了请求协议的所有内容
  • Tomcat 服务器将 HTTP 请求数据解析并封装到该对象中
  • 开发者可通过此对象获取请求信息

HttpServletResponse

  • 用于向浏览器发送响应数据
  • 主要处理 HTTP 响应逻辑

四、Servlet 生命周期回顾

  1. 用户第一次请求

    • Tomcat 通过反射创建 Servlet 实例(调用无参构造方法)
    • 调用 init(ServletConfig) 方法进行初始化
    • 调用 service() 处理请求
  2. 后续请求

    • 直接调用 service() 方法处理请求
  3. 服务器关闭

    • 调用 destroy() 方法执行销毁前的清理工作
    • 最终销毁 Servlet 对象

五、HttpServlet 源码剖析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class HelloServlet extends HttpServlet {
    public HelloServlet() {
        // 用户首次请求时会执行构造方法
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 GET 请求
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理 POST 请求
    }
}

HttpServlet 的核心方法:service

1. service(ServletRequest, ServletResponse)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Override
public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }

    service(request, response); // 调用重载方法
}

2. service(HttpServletRequest, HttpServletResponse)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        doGet(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
        doHead(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req, resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

总结service() 是一个模板方法,定义了处理请求的核心骨架,具体实现由子类完成。

默认的 doGet / doPost 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String msg = lStrings.getString("http.method_get_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String msg = lStrings.getString("http.method_post_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

如果未重写对应的方法,将会抛出 405 错误(HTTP 405 Method Not Allowed)。

六、常见问题解答

1. 如果前端请求方式和后端重写方法不匹配会发生什么?

  • 若前端发送的是 GET 请求,而后端只重写了 doPost,则会触发 405 错误。
  • 同理,若前端发送的是 POST 请求,而后端只重写了 doGet,也会触发 405 错误。

2. 如何避免 405 错误?

  • 后端重写 doGet,前端必须发送 GET 请求;
  • 后端重写 doPost,前端必须发送 POST 请求;
  • 请求方式由后端决定,前端需配合使用正确的请求方式。

建议:不要为了规避 405 错误而同时重写 doGetdoPost。如果确实需要统一处理,可直接重写 service() 方法。

3. 是否可以直接重写 HttpServlet 的 service() 方法?

  • 可以,但将失去对请求方式的自动判断,也无法享受 HTTP 专属功能。
  • 不推荐,除非有特殊需求。

七、Servlet 开发标准流程

  1. 第一步:编写 Servlet 类,继承 HttpServlet
  2. 第二步:根据业务需求重写 doGetdoPost 方法
  3. 第三步:在 web.xml 中配置该 Servlet 映射路径
  4. 第四步:准备前端页面(如 HTML 表单),指定对应的请求路径

💡 提示:理解 HttpServlet 的源码有助于掌握 Java Web 开发底层机制,合理利用框架提供的功能,提升开发效率和代码质量。

Web 站点的欢迎页面详解

一、什么是 Web 站点的欢迎页面?

  • 对于一个 Web 应用(WebApp)来说,我们可以设置它的欢迎页面
  • 设置了欢迎页面后,当用户访问该 WebApp 或站点时,没有指定具体资源路径,系统会默认跳转到欢迎页面。

示例说明:

  • 指定了资源路径:

    1
    
    http://localhost:8080/servlet06/login.html
    
  • 未指定资源路径(访问欢迎页):

    1
    
    http://localhost:8080/servlet06
    

此时,默认会访问你在配置中指定的欢迎页面。


二、如何设置欢迎页面?

步骤 1:在 web 目录下创建登录页面

例如,在 IDEA 的 web 目录下新建一个文件:

1
login.html

步骤 2:在 web.xml 中配置欢迎页面

1
2
3
<welcome-file-list>
    <welcome-file>login.html</welcome-file>
</welcome-file-list>

⚠️ 注意事项:

  • 路径不需要以 / 开头;
  • 默认从 WebApp 的根目录开始查找资源。

步骤 3:启动服务器并访问站点

访问地址如下:

1
http://localhost:8080/servlet07

此时将自动加载 login.html 页面。


三、如果欢迎页面在子目录中该如何设置?

场景描述:

  • 在 WebApp 根目录下新建目录结构:
    1
    
    /page1/page2/page.html
    

配置方式:

1
2
3
<welcome-file-list>
    <welcome-file>page1/page2/page.html</welcome-file>
</welcome-file-list>

⚠️ 同样地,路径不需要以 / 开头,查找仍从 WebApp 根目录开始。


四、一个 WebApp 可以设置多个欢迎页面吗?

是的!可以设置多个欢迎页面,并按优先级顺序排列:

1
2
3
4
<welcome-file-list>
    <welcome-file>page1/page2/page.html</welcome-file>
    <welcome-file>login.html</welcome-file>
</welcome-file-list>

⚠️ 优先级规则
越靠上的 <welcome-file> 优先级越高。如果第一个找不到,则继续向下查找。


五、为什么 index.html 不需要配置也能作为欢迎页面?

这是因为 Tomcat 服务器已经预设了全局的欢迎页面配置。

欢迎页面的配置位置有两个:

  • 局部配置:在你自己的 WebApp 的 WEB-INF/web.xml 文件中;
  • 全局配置:在 Tomcat 的 CATALINA_HOME/conf/web.xml 文件中。

Tomcat 默认的欢迎页面配置:

1
2
3
4
5
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

⚠️ 配置原则
遵循“局部优先”原则。如果你自己设置了欢迎页面,Tomcat 就不会使用默认的 index.html 等页面。


六、欢迎页面可以是一个 Servlet 吗?

当然可以!

欢迎页面本质上就是一个资源,既可以是静态资源(如 HTML),也可以是动态资源(如 Servlet)。

步骤 1:编写一个欢迎用的 Servlet

1
2
3
4
5
6
7
8
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("<h1>Welcome to bjpowernode!</h1>");
    }
}

步骤 2:在 web.xml 中注册该 Servlet

1
2
3
4
5
6
7
8
9
<servlet>
    <servlet-name>welcomeServlet</servlet-name>
    <servlet-class>com.bjpowernode.javaweb.servlet.WelcomeServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>welcomeServlet</servlet-name>
    <url-pattern>/fdsa/fds/a/fds/af/ds/af/dsafdsafdsa</url-pattern>
</servlet-mapping>

步骤 3:在 web.xml 中配置为欢迎页面

1
2
3
<welcome-file-list>
    <welcome-file>fdsa/fds/a/fds/af/ds/af/dsafdsafdsa</welcome-file>
</welcome-file-list>

这样,当你访问站点根路径时,就会触发这个 Servlet 并输出欢迎信息。


总结

类型 资源示例 是否需配置
静态资源 login.html, index.html 是(除 index.html 等默认值)
动态资源 WelcomeServlet

最佳实践建议

  • 使用 index.html 可省去配置;
  • 多个欢迎页面按优先级排列;
  • 欢迎页面可灵活选择静态或动态资源。

关于WEB-INF目录

  • 在WEB-INF目录下新建了一个文件:welcome.html
  • 打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html 出现了404错误。
  • 注意:放在WEB-INF目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像HTML、CSS、JS、image等静态资源一定要放到WEB-INF目录之外。

HttpServletRequest 接口详解

一、基本概述

1. 接口定义

  • 全限定名jakarta.servlet.http.HttpServletRequest
  • 所属规范:Servlet 规范的一员
  • 父接口ServletRequest
1
public interface HttpServletRequest extends ServletRequest {}

2. 实现与创建

  • 实现类:Tomcat 中为 org.apache.catalina.connector.RequestFacade
  • 对象创建者:Tomcat 服务器(即 Web 容器)
  • 说明
    • Tomcat 实现了 HttpServletRequest 接口,并负责创建其对象。
    • 开发者无需关心具体实现类,只需面向接口编程。

3. 封装内容

  • 封装信息:HTTP 请求协议中的所有信息
  • 数据来源:浏览器发送的 HTTP 请求
  • 处理流程
    1. 浏览器发送 HTTP 请求;
    2. Tomcat 解析请求;
    3. 将解析结果封装到 HttpServletRequest 对象中;
    4. 提供给 JavaWeb 程序员使用。

4. 生命周期

  • request 和 response 的生命周期
    • 仅在当前请求中有效;
    • 每次请求都会创建新的 request;
    • 多次请求对应多个独立的 request。

二、常用方法详解

1. 获取用户提交的数据

常用方法列表:

1
2
3
4
Map<String, String[]> getParameterMap()
Enumeration<String> getParameterNames()
String[] getParameterValues(String name)
String getParameter(String name) // 最常用

数据存储结构分析:

  • 表单示例:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt
  • 存储结构应为:Map<String, String[]>
    • 举例:
      1
      2
      3
      4
      5
      
      key         | value
      -------------------------------
      username    | ["abc"]
      userpwd     | ["111"]
      aihao       | ["s", "d", "tt"]
      

注意事项:

  • 所有前端提交的数据均为字符串类型;
  • 即使是数字(如 age=120),后端获取到的也是字符串 "120"

2. 请求域(Request Scope)

概述:

  • request 是一个“请求域”对象;
  • 用于在一次请求中共享数据;
  • 生命周期短于应用域(ServletContext)。

域操作方法:

1
2
3
void setAttribute(String name, Object obj); // 设置属性
Object getAttribute(String name);          // 获取属性
void removeAttribute(String name);        // 移除属性

与应用域对比:

类型 范围 生命周期 使用场景
RequestScope 当前请求 一次请求结束销毁 同一请求内多个组件共享
AppScope 整个 Web 应用 应用启动至关闭期间 所有用户共享数据

使用建议:

  • 优先使用范围小的域对象,减少资源占用。

3. 请求转发(Forward)

示例代码:

1
2
// 获取请求转发器并转发
request.getRequestDispatcher("/b").forward(request, response);

特点:

  • 一次请求;
  • 可以转发给任意合法资源(Servlet、HTML、JSP 等);
  • 路径写法:以 / 开头,不加项目名。

数据共享方式:

  • 在 AServlet 中设置属性,转发至 BServlet,可共享 request 域中的数据。

4. 参数获取与属性获取的区别

方法 来源 描述
getParameter() 用户提交的数据 获取请求参数(如表单、URL 参数)
getAttribute() 请求域中的属性 获取通过 setAttribute() 设置的值

三、其他常见方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 获取客户端 IP 地址
String remoteAddr = request.getRemoteAddr();

// 设置请求体字符集(解决 POST 请求乱码问题)
request.setCharacterEncoding("UTF-8");

// 获取请求方式(GET/POST)
String method = request.getMethod();

// 获取请求 URI(如:/aaa/testRequest)
String uri = request.getRequestURI();

// 获取 Servlet 路径(如:/testRequest)
String servletPath = request.getServletPath();

// 获取应用上下文路径(如:/myapp)
String contextPath = request.getContextPath();

四、乱码问题解决方案

请求方式 乱码原因 解决方案
GET 请求行中的参数编码 修改 server.xml 配置:URIEncoding="UTF-8"
POST 请求体中的编码 request.setCharacterEncoding("UTF-8")
Response 响应内容编码 response.setContentType("text/html;charset=UTF-8")

注意

  • Tomcat 10+ 默认使用 UTF-8 编码,不再需要手动设置;
  • 这体现了对中文支持的增强。

五、缓存技术简要介绍

技术名称 描述
字符串常量池 相同字符串复用,提高性能
整数常量池 [-128~127] 内整数复用
数据库连接池 预先创建连接,提升数据库访问效率
线程池 预先创建线程,提高并发处理能力
Redis / MongoDB 分布式缓存,适用于大规模系统

六、总结

  • HttpServletRequest 是 Servlet 规范的重要组成部分;
  • 封装了 HTTP 请求的所有信息;
  • 提供丰富的 API 用于获取请求参数、设置域属性、实现转发等功能;
  • 合理使用 request 域和应用域,可以提升系统性能;
  • 注意乱码问题的处理策略,尤其在不同 Tomcat 版本中的差异;
  • 掌握缓存思想,有助于构建高性能 Web 应用。

使用纯Servlet实现部门表的CRUD操作

一、项目概述

本项目使用纯Servlet技术完成对部门表(dept)的增删改查(CRUD)操作,采用B/S架构。


二、数据库准备

部门表结构及初始化脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-- 部门表
drop table if exists dept;
create table dept(
    deptno int primary key,
    dname varchar(255),
    loc varchar(255)
);

insert into dept(deptno, dname, loc) values(10, 'XiaoShouBu', 'BEIJING');
insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI');
insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU');
insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN');

commit;

select * from dept;

三、前端页面设计

使用 HBuilder 开发工具创建HTML页面,主要包括以下页面:

页面名称 描述
index.html 欢迎页面
list.html 部门列表页(核心页面)
add.html 新增部门页面
edit.html 修改部门页面
detail.html 查看部门详情页面

四、功能分析

每个连接数据库的操作即为一个独立功能。系统包括以下6个功能:

  1. 查看部门列表
  2. 新增部门
  3. 删除部门
  4. 查看部门详细信息
  5. 跳转到修改页面
  6. 修改部门

五、开发环境搭建(IDEA)

步骤如下:

  1. 创建Web应用项目。
  2. 添加依赖:
    • servlet-api.jar
    • jsp-api.jar
    • MySQL驱动包(放入 WEB-INF/lib 目录)
  3. 编写JDBC工具类用于数据库连接。
  4. 将HTML页面拷贝至web目录下。

六、功能实现流程详解

1. 查看部门列表

实现步骤:

  • 前端页面:

    1
    
    <a href="/oa/dept/list">查看部门列表</a>
    
  • 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    
    <servlet>
        <servlet-name>list</servlet-name>
        <servlet-class>com.bjpowernode.oa.web.action.DeptListServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>list</servlet-name>
        <url-pattern>/dept/list</url-pattern>
    </servlet-mapping>
    
  • 编写 DeptListServlet 类:

    继承 HttpServlet,重写 doGet() 方法,连接数据库查询数据并动态生成 HTML 表格。

    示例代码片段:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    while(rs.next()) {
        String deptno = rs.getString("deptno");
        String dname = rs.getString("dname");
        String loc = rs.getString("loc");
    
        out.print("<tr>");
        out.print("  <td>" + (++i) + "</td>");
        out.print("  <td>" + deptno + "</td>");
        out.print("  <td>" + dname + "</td>");
        out.print("  <td>");
        out.print("    <a href=''>删除</a>");
        out.print("    <a href='edit.html'>修改</a>");
        out.print("    <a href='detail.html'>详情</a>");
        out.print("  </td>");
        out.print("</tr>");
    }
    

2. 查看部门详情

实现步骤:

  • 前端链接添加参数:

    1
    
    out.print("<a href='" + contextPath + "/dept/detail?deptno=" + deptno + "'>详情</a>");
    
  • 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    
    <servlet>
        <servlet-name>detail</servlet-name>
        <servlet-class>com.bjpowernode.oa.web.action.DeptDetailServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>detail</servlet-name>
        <url-pattern>/dept/detail</url-pattern>
    </servlet-mapping>
    
  • 编写 DeptDetailServlet 类:

    doGet() 方法中获取 deptno 参数,查询数据库并展示详情页面。


3. 删除部门

实现步骤:

  • 前端 JS 提示确认删除:

    1
    2
    3
    4
    5
    6
    7
    8
    
    <a href="javascript:void(0)" onclick="del(30)">删除</a>
    <script type="text/javascript">
    function del(dno){
        if(window.confirm("亲,删了不可恢复哦!")){
            document.location.href = "/oa/dept/delete?deptno=" + dno;
        }
    }
    </script>
    
  • 配置 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    
    <servlet>
        <servlet-name>delete</servlet-name>
        <servlet-class>com.bjpowernode.oa.web.action.DeptDelServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>delete</servlet-name>
        <url-pattern>/dept/delete</url-pattern>
    </servlet-mapping>
    
  • 编写 DeptDelServlet 类:

    根据 deptno 删除记录,并根据结果转发到对应页面(成功跳转列表页,失败跳转错误页)。

    1
    2
    3
    4
    5
    
    if (count == 1) {
        request.getRequestDispatcher("/dept/list").forward(request, response);
    } else {
        request.getRequestDispatcher("/error.html").forward(request, response);
    }
    

4. 新增部门

注意事项:

  • 若使用 POST 请求提交新增数据,转发到 /dept/list 时可能出现 405 错误
  • 原因:转发请求仍保持原方法(POST),而目标 Servlet 只有 doGet()。
  • 解决方案:
    • 方案一: 在目标 Servlet 中添加 doPost() 并调用 doGet()
    • 方案二: 使用重定向代替转发。

5. 跳转到修改页面

  • 用户点击“修改”按钮后,应跳转至 edit.html 页面。
  • 页面需携带当前部门编号参数,供后续修改使用。

6. 修改部门

  • 类似于新增功能,但需要先从数据库加载当前部门信息填充表单。
  • 提交时更新数据库,并重定向或转发回列表页。

七、总结

通过以上步骤,可以使用纯Servlet技术完成对部门表的完整 CRUD 操作。虽然代码较为繁琐,但有助于深入理解 Web 应用底层工作原理。

在一个 Web 应用中如何完成资源跳转

在 Web 应用中,实现资源跳转主要有两种方式:

  1. 转发(Forward)
  2. 重定向(Redirect)

一、转发 vs 重定向

对比项 转发(Forward) 重定向(Redirect)
请求次数 一次请求 两次请求
地址栏变化 不变 改变为最终目标地址
控制者 服务器(Tomcat) 浏览器
是否共享 request 域数据

1. 代码上的区别

✅ 转发示例(Java)

1
2
3
4
5
6
// 获取请求转发器对象并转发
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
dispatcher.forward(request, response);

// 简化写法
request.getRequestDispatcher("/dept/list").forward(request, response);
  • 说明:转发过程中始终使用同一个 requestresponse,即所有转发都在一次请求中完成。
  • 特点:AServlet → BServlet → CServlet → DServlet,都共用同一个请求对象。

✅ 重定向示例(Java)

1
2
// 重定向到指定路径
response.sendRedirect("/oa/dept/list");
  • 说明:浏览器会根据返回的状态码和位置头,重新发起一个新的请求。
  • 注意:路径需要加上项目名,因为是浏览器主动发送新请求。

2. 形式上的区别

📌 转发(一次请求)

  • 地址栏显示的是最初请求的 URL。
  • 示例:
    • 初始请求:http://localhost:8080/servlet10/a
    • 最终地址不变。

📌 重定向(两次请求)

  • 地址栏最终显示的是重定向后的 URL。
  • 示例:
    • 初始请求:http://localhost:8080/servlet10/a
    • 最终地址:http://localhost:8080/servlet10/b

3. 本质区别

  • 转发:由服务器控制,Tomcat 内部跳转。客户端无感知,只看到一次请求。
  • 重定向:由浏览器执行,相当于重新发起一个全新的请求。

4. 生活化的例子解释

🧾 转发(一次请求)—— 类似“中间人帮忙借钱”

  • 杜老师找张三借钱,张三没有但去找李四借了钱再给杜老师。
  • 杜老师以为钱是张三给的,整个过程只有一次请求行为。

🧾 重定向(两次请求)—— 类似“直接找别人借钱”

  • 杜老师找张三借钱,张三没有,但告诉杜老师李四可以借。
  • 杜老师自己去找李四借钱,这是两个独立的行为。

二、转发和重定向该如何选择?

使用场景 推荐方式
需要传递 request 域中的数据(如属性值) 转发(Forward)
页面跳转后希望刷新页面不重复提交 重定向(Redirect)
普通的页面跳转(如登录成功后跳转首页) 重定向(Redirect)

建议:除非需要保留 request 域数据,否则一律使用重定向。


三、跳转的目标资源必须是 Servlet 吗?

不是!
转发或重定向的目标资源只要是服务器内部合法的资源即可,包括:

  • Servlet
  • JSP
  • HTML 文件
  • 图片、CSS、JS 等静态资源

四、注意事项

⚠️ 转发存在的问题:浏览器刷新问题

  • 如果用户刷新页面,可能会导致重复处理逻辑(如重复插入数据库),因为请求仍是原始请求。
  • 所以,对于涉及业务操作的请求,建议使用重定向,避免刷新造成副作用。

总结

特性 转发 重定向
请求次数 1次 2次
地址栏变化
request 数据是否共享
安全性 较低(暴露服务端结构) 较高
适用场景 数据共享、内部跳转 表单提交后跳转、防止重复提交

💡 一句话总结转发是服务器内部的事,重定向是让浏览器重新做事。

将oa项目中的资源跳转修改为合适的跳转方式

  • 删除之后,重定向
  • 修改之后,重定向
  • 保存之后,重定向
  • 重定向:
    • 成功
    • 失败

Servlet注解,简化配置

  • 分析oa项目中的web.xml文件

    • 现在只是一个单标的CRUD,没有复杂的业务逻辑,很简单的一丢丢功能。web.xml文件中就有如此多的配置信息。如果采用这种方式,对于一个大的项目来说,这样的话web.xml文件会非常庞大,有可能最终会达到几十兆。
    • 在web.xml文件中进行servlet信息的配置,显然开发效率比较低,每一个都需要配置一下。
    • 而且在web.xml文件中的配置是很少被修改的,所以这种配置信息能不能直接写到java类当中呢?可以的。
  • Servlet3.0版本之后,推出了各种Servlet基于注解式开发。优点是什么?

    • 开发效率高,不需要编写大量的配置信息。直接在java类上使用注解进行标注。
    • web.xml文件体积变小了。
  • 并不是说注解有了之后,web.xml文件就不需要了:

    • 有一些需要变化的信息,还是要配置到web.xml文件中。一般都是 注解+配置文件 的开发模式。
    • 一些不会经常变化修改的配置建议使用注解。一些可能会被修改的建议写到配置文件中。
  • 我们的第一个注解:

    • 1
      
      jakarta.servlet.annotation.WebServlet
      
    • 在Servlet类上使用:@WebServlet,WebServlet注解中有哪些属性呢?

      • name属性:用来指定Servlet的名字。等同于:<servlet-name>
      • urlPatterns属性:用来指定Servlet的映射路径。可以指定多个字符串。<url-pattern>
      • loadOnStartUp属性:用来指定在服务器启动阶段是否加载该Servlet。等同于:<load-on-startup>
      • value属性:当注解的属性名是value的时候,使用注解的时候,value属性名是可以省略的。
      • 注意:不是必须将所有属性都写上,只需要提供需要的。(需要什么用什么。)
      • 注意:属性是一个数组,如果数组中只有一个元素,使用该注解的时候,属性值的大括号可以省略。
  • 注解对象的使用格式:

    • @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值….)

使用模板方法设计模式优化oa项目

  • 上面的注解解决了配置文件的问题。但是现在的oa项目仍然存在一个比较臃肿的问题。
    • 一个单标的CRUD,就写了6个Servlet。如果一个复杂的业务系统,这种开发方式,显然会导致类爆炸。(类的数量太大。)
    • 怎么解决这个类爆炸问题?可以使用模板方法设计模式。
  • 怎么解决类爆炸问题?
    • 以前的设计是一个请求一个Servlet类。1000个请求对应1000个Servlet类。导致类爆炸。
    • 可以这样做:一个请求对应一个方法。一个业务对应一个Servlet类。
    • 处理部门相关业务的对应一个DeptServlet。处理用户相关业务的对应一个UserServlet。处理银行卡卡片业务对应一个CardServlet。

分析使用纯粹Servlet开发web应用的缺陷

  • 在Servlet当中编写HTML/CSS/JavaScript等前端代码。存在什么问题?
    • java程序中编写前端代码,编写难度大。麻烦。
    • java程序中编写前端代码,显然程序的耦合度非常高。
    • java程序中编写前端代码,代码非常不美观。
    • java程序中编写前端代码,维护成本太高。(非常难于维护)
      • 修改小小的一个前端代码,只要有改动,就需要重新编译java代码,生成新的class文件,打一个新的war包,重新发布。
  • 思考一下,如果是你的话,你准备怎么解决这个问题?
    • 思路很重要。使用什么样的思路去做、去解决这个问题
      • 上面的那个Servlet(Java程序)能不能不写了,让机器自动生成。我们程序员只需要写这个Servlet程序中的“前端的那段代码”,然后让机器将我们写的“前端代码”自动翻译生成“Servlet这种java程序”。然后机器再自动将“java”程序编译生成"class"文件。然后再使用JVM调用这个class中的方法。

B/S结构系统中的会话机制(Session机制)

在现代Web开发中,会话机制(Session) 是实现用户状态管理的重要手段。本文将详细介绍 Session 的概念、原理、应用场景以及其在 Java Web 中的具体实现方式。


一、什么是会话?

1. 基本定义

  • 会话(Session):指用户从打开浏览器开始操作,到关闭浏览器结束的一系列交互过程。
  • 在这个过程中,用户可能会发起多次请求,这些请求都属于同一次会话。

2. 会话对象(Session Object)

  • 在服务器端,每个用户的会话都会被封装成一个 Java 对象:HttpSession
  • 类路径:jakarta.servlet.http.HttpSession(原为 javax.servlet.http.HttpSession,Java EE 9 后改为 jakarta)。

3. 请求(Request)

  • 用户点击页面触发一次请求,服务器通过 HttpServletRequest 对象来处理该请求。
  • 一次会话包含多个请求。

二、为什么需要 Session?

1. HTTP 协议是无状态的

  • HTTP 是一种无连接、无状态的协议。
  • 每次请求完成后,客户端与服务器断开连接,服务器无法直接知道用户是否继续访问。

2. 状态保持的需求

  • 用户登录后,如何在整个会话期间保留登录状态?
  • 如何识别不同用户的不同会话?
  • 这些问题都需要通过 Session 来解决。

三、Session 的工作原理

1. 获取 Session 对象

1
HttpSession session = request.getSession();
  • 当调用此方法时,服务器会根据当前请求判断是否存在对应的 Session。
  • 如果不存在,则创建一个新的 Session 并返回。
  • 如果存在,则返回已有的 Session。

2. Session ID 的作用

  • 每个 Session 都有一个唯一的标识符:JSESSIONID
  • JSESSIONID 以 Cookie 形式保存在浏览器内存中:
    • 浏览器关闭后,Cookie 被清除,Session ID 失效。
    • 下次访问时,服务器会生成新的 Session。

3. Session 列表的存储结构

  • 服务器内部维护一个 Map 结构:
    1
    
    Map<String sessionId, HttpSession session>
    
  • 每次请求携带的 Session ID 用于查找对应的 Session 对象。

四、Session 与其他域对象对比

域对象 对应类名 生命周期 作用范围 典型用途
Request HttpServletRequest 一次请求 请求级别 页面跳转传值
Session HttpSession 一次会话 用户级别 登录状态、用户信息
Application ServletContext 应用启动到关闭 应用级别 全局配置、缓存数据

1. 域对象大小关系

1
request < session < application

2. 域对象常用方法

所有域对象都支持以下方法:

  • setAttribute(String name, Object value):设置属性
  • getAttribute(String name):获取属性
  • removeAttribute(String name):删除属性

3. 使用原则

  • 尽量使用小范围的域对象,避免资源浪费和并发冲突。

五、Session 的实现细节

1. Session ID 的传输方式

  • 默认通过 Cookie 传递 Session ID(JSESSIONID=xxxxxx)。
  • 如果浏览器禁用了 Cookie,Session ID 可以通过 URL 重写 方式传递:
    1
    
    http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16
    

2. URL 重写机制

  • 开发者需手动拼接 Session ID 到 URL 中。
  • 成本较高,因此大多数网站要求用户启用 Cookie。

3. Session 的销毁

  • 手动销毁:
    1
    
    session.invalidate(); // 销毁当前会话
    
  • 自动销毁:
    • 默认超时时间(如 Tomcat 默认为 30 分钟),可在 web.xml 中配置。

六、Session 的典型应用场景

1. 用户登录认证

  • 登录成功后,将用户信息存入 Session:
    1
    
    session.setAttribute("user", user);
    
  • 后续请求中检查 Session 是否存在用户信息,决定是否放行或跳转至登录页。

2. 购物车功能

  • 临时保存用户选中的商品信息,直到用户提交订单。

3. 防止重复提交

  • 在提交操作前检查 Session 中是否已有标记,防止用户重复提交。

七、Session 的优缺点分析

优点 缺点
实现简单,易于使用 存储在服务器,占用内存资源
支持跨页面数据共享 Session 丢失可能导致状态异常
提供了安全的数据存储机制 不适合存储大量数据

八、Session 与 Token 的比较(扩展知识)

随着前后端分离和移动端的发展,越来越多项目采用 Token(如 JWT) 替代传统的 Session:

对比项 Session Token
存储位置 服务器 客户端(如 LocalStorage)
可扩展性 较差(依赖服务器内存) 更好(无状态,便于分布式部署)
安全性 依赖 Cookie 可加密签名
性能影响 有状态,每次请求需查 Session 无状态,性能更高

建议:对于大型分布式系统,推荐使用 Token;而对于传统单体 Web 应用,Session 仍是实用选择。


九、总结

Session 是 B/S 架构中实现用户状态管理的核心机制之一,具有以下特点:

  • 基于 HTTP 无状态协议之上,提供状态保持能力;
  • 通过 Session ID 识别用户,服务器维护 Session 数据;
  • 适用于用户登录、购物车、权限控制等场景;
  • 有生命周期限制,可手动或自动销毁;
  • 与 Cookie 紧密相关,但也可通过 URL 重写实现;
  • 在 Java Web 中由 HttpSession 接口实现;
  • 开发中应合理使用 Session,优先考虑较小的域对象。

如需进一步深入 Session 相关内容,可以研究如下方向:

  • Session 的集群同步机制(如 Redis 存储 Session)
  • Session 的持久化(如数据库存储)
  • Spring Session 的使用
  • OAuth2 与 Session 的结合使用

Cookie 与 Session 原理详解

一、Cookie 简介

  • Cookie 是客户端(浏览器)用于存储少量数据的一种机制。
  • 数据以 name=value 的形式保存在浏览器中。
  • 每次向服务器发送请求时,符合条件的 Cookie 会自动随请求一起发送给服务器。
  • 生成方式:
    • 由服务器通过 HTTP 响应头中的 Set-Cookie 字段设置。
    • 浏览器接收到后解析并存储。
  • 存储位置:
    • 运行内存(临时 Cookie):
      • 关闭浏览器即失效。
      • 未设置过期时间时默认如此。
    • 硬盘文件(持久化 Cookie):
      • 设置了过期时间后,浏览器将其写入磁盘。
      • 即使关闭浏览器也不会丢失。
  • 维持用户状态: 在无状态的 HTTP 协议中,通过 Cookie 实现用户登录状态、购物车信息等的保持。
  • 记录用户偏好: 如语言选择、主题风格等。
  • 跨域通信辅助: 配合其他机制实现跨域单点登录(SSO)等功能。

2.1 Session 的实现原理

  • 每个用户的会话信息被服务器端封装为一个 session 对象。
  • 每个 session 对象都有一个唯一标识符:session ID
    • 示例:JSESSIONID=41C481F0224664BDB28E95081D23D5B8
  • 这个 session ID 通常通过 Cookie 发送给浏览器,称为 会话 Cookie
  • 浏览器在后续请求中将该 Cookie 自动回传给服务器,从而实现会话跟踪。

注意:

  • Session 是服务器端机制,而 Cookie 是客户端机制。
  • Session 的 session ID 依赖于 Cookie 来实现“记忆”功能。
  • 如果浏览器禁用了 Cookie,则需要 URL 重写来传递 session ID。

三、Cookie 的使用场景与经典案例

3.1 购物车功能(如京东商城)

  • 用户未登录状态下添加商品到购物车。
  • 即使关闭浏览器,再次访问网站时购物车内容仍存在。
  • 实现原理:
    • 将商品编号(如 productIds=1001,1002,1003)存入 Cookie。
    • Cookie 设置为持久化(保存在硬盘),有效期较长。
    • 下次访问时读取 Cookie,还原购物车内容。

3.2 十天内免登录(如 126 邮箱)

  • 用户勾选“十天内免登录”,登录成功后:
    • 服务器生成包含用户名、密码等信息的 Cookie。
    • Cookie 存储在硬盘上,有效期为 10 天。
  • 用户下次访问时,浏览器自动发送该 Cookie。
  • 服务器验证信息后完成自动登录。

🚫 Cookie 失效条件:

  • 10 天后自动过期
  • 用户修改密码
  • 用户清除浏览器 Cookie

4.1 Java Servlet 提供的类与方法

  • 类: jakarta.servlet.http.Cookie
  • 创建 Cookie:
    1
    
    Cookie cookie = new Cookie("username", "zhangsan");
    
  • 设置最大生存时间(单位:秒):
    1
    
    cookie.setMaxAge(60 * 60 * 24 * 10); // 10天
    
  • 设置路径(path):
    1
    
    cookie.setPath("/myapp"); // 表示该路径及其子路径下有效
    
  • 添加到响应对象中:
    1
    
    response.addCookie(cookie);
    
1
2
3
4
5
6
7
8
Cookie[] cookies = request.getCookies();
if (cookies != null) {
    for (Cookie cookie : cookies) {
        String name = cookie.getName();
        String value = cookie.getValue();
        // 处理业务逻辑
    }
}

五、Cookie 的生命周期与作用范围

5.1 生命周期控制

设置值 效果
未设置 setMaxAge() 默认存储在运行内存中,浏览器关闭即失效
setMaxAge(0) 删除同名 Cookie
setMaxAge(n > 0) 持久化保存,n 秒后失效
setMaxAge(n < 0) 同未设置,存储在运行内存中
  • 默认路径:
    • 与当前请求路径相同目录及其子路径有效。
  • 自定义路径:
    • 例如:cookie.setPath("/project") 表示 /project 及其子路径都可访问该 Cookie。

6.1 功能流程

  1. 用户访问登录页面,输入用户名、密码,并选择“十天内免登录”选项。
  2. 登录成功后,服务器判断是否勾选免登录:
    • 若勾选,则创建包含用户名、密码的 Cookie,并设置有效期为 10 天。
  3. 用户再次访问首页时,检查是否存在对应 Cookie:
    • 若存在且合法,则跳转至登录后的页面;
    • 否则,跳转至登录页。

6.2 核心代码片段

登录处理逻辑(Servlet)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean autoLogin = "on".equals(request.getParameter("remember"));

// 假设登录验证成功
if ("admin".equals(username) && "123456".equals(password)) {
    if (autoLogin) {
        Cookie userCookie = new Cookie("username", username);
        Cookie pwdCookie = new Cookie("password", password);
        int expireTime = 60 * 60 * 24 * 10; // 10天
        userCookie.setMaxAge(expireTime);
        pwdCookie.setMaxAge(expireTime);
        userCookie.setPath("/");
        pwdCookie.setPath("/");
        response.addCookie(userCookie);
        response.addCookie(pwdCookie);
    }

    response.sendRedirect("welcome.jsp");
} else {
    response.sendRedirect("login_failed.jsp");
}

页面自动登录检测逻辑(Filter 或 JSP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Cookie[] cookies = request.getCookies();
String username = null;
String password = null;

if (cookies != null) {
    for (Cookie c : cookies) {
        if ("username".equals(c.getName())) {
            username = c.getValue();
        } else if ("password".equals(c.getName())) {
            password = c.getValue();
        }
    }
}

if (username != null && password != null) {
    // 验证用户名密码合法性,合法则自动登录
    request.getSession().setAttribute("user", username);
    response.sendRedirect("home.jsp");
} else {
    response.sendRedirect("login.jsp");
}

七、总结与扩展思考

7.1 总结

特性 Cookie Session
存储位置 客户端(浏览器) 服务端
安全性 相对较低(可能被篡改) 较高(服务器管理)
生命周期 可控(持久化或临时) 依赖 Cookie 或 URL 重写
用途 记录用户信息、偏好设置等 保存敏感会话数据

7.2 扩展建议

  • 安全性增强:
    • 不应在 Cookie 中明文存储密码,应加密或使用 Token 替代。
  • 现代替代方案:
    • 使用 JWT(JSON Web Token)代替传统 Cookie + Session 机制。
    • 使用 localStorage / sessionStorage 配合前端框架(如 Vue/React)实现本地状态管理。
  • 移动端适配:
    • 移动 App 中通常使用 SharedPreferences / Keychain / Secure Storage 替代 Cookie。

✨ JSP(JavaServer Pages)详解

一、初识 JSP

1. 第一个 JSP 程序

  • 在 Web 应用的 WEB-INF 目录之外创建一个 index.jsp 文件。
  • 部署项目后启动服务器,在浏览器中访问:
    1
    
    http://localhost:8080/jsp/index.jsp
    
  • 页面显示为空白,但底层执行的是:index_jsp.class 这个 Java 类。

🔍 原理说明:

  • Tomcat 会将 index.jsp 翻译为 index_jsp.java
  • 再将其 编译为 index_jsp.class
  • 访问该页面时,实际上是调用了这个类的 service() 方法。

二、JSP 的本质与生命周期

1. JSP 是 Servlet 吗?

  • 是的。JSP 本质上就是一个 Servlet!
  • JSP 被翻译成 Java 类后,继承自 HttpJspBase,而 HttpJspBase 又继承自 HttpServlet
  • 所以,index_jsp 类是一个标准的 Servlet。

2. 生命周期

  • JSP 的生命周期与 Servlet 完全一致:
    1. jspInit()
    2. jspService()
    3. jspDestroy()

3. 单例特性

  • JSP 和 Servlet 一样,都是单例的(严格来说是“伪单例”),即每个请求共享同一个实例。
  • 因此,不建议在 JSP 中使用成员变量或静态变量,避免线程安全问题。

三、JSP 的运行机制与性能优化

1. 为什么第一次访问 JSP 比较慢?

  • 第一次访问需要经历以下过程:
    1. JSP 被翻译成 .java 文件
    2. 编译为 .class 字节码
    3. 创建 Servlet 实例
    4. 调用 init() 初始化
    5. 最后才调用 service() 方法处理请求

2. 为什么第二次访问就快了?

  • 因为已经生成 .class 文件并创建了 Servlet 实例。
  • 后续请求直接复用该实例,仅调用 service() 方法即可。

🧩 运维建议:

在正式演示前,提前访问所有 JSP 页面,完成预热,提升用户体验。


四、JSP 的基础语法

1. JSP 是什么?

  • JSP 是一种 动态网页技术,基于 Java 实现。
  • 全称:JavaServer Pages
  • 是 Java EE 规范的一部分。
  • 所有 Web 容器(如 Tomcat)都内置 JSP 引擎,负责翻译和执行。

2. JSP 基础语法结构

语法 说明
<% java代码 %> 插入 Java 代码,翻译到 service() 方法内部
<%= 表达式 %> 输出表达式的值,翻译为 out.print()
<%! java代码 %> 插入 Java 代码,翻译到 service() 方法外部(不推荐使用)
<%-- 注释 --%> JSP 注释,不会出现在生成的 Java 代码中
<!-- HTML 注释 --> HTML 注释,会被翻译进 Java 代码中

3. page 指令

用于控制 JSP 的全局行为:

1
<%@ page contentType="text/html;charset=UTF-8" %>

常用属性:

属性 说明
contentType 设置响应内容类型及字符集
pageEncoding 设置当前 JSP 文件的编码格式
import 导包
session 是否启用 session 对象,默认为 true
errorPage 指定错误页面
isErrorPage 当前页面是否作为错误页面使用

五、JSP 与 Servlet 的区别与协作

功能 Servlet JSP
主要职责 数据收集、业务逻辑处理 数据展示、页面渲染
优点 控制能力强、适合处理复杂逻辑 易于编写 HTML、适合前端展示
缺点 不适合写大量 HTML 不适合处理复杂业务逻辑

最佳实践:

  • 使用 Servlet 处理业务逻辑、连接数据库、获取数据
  • 使用 JSP 展示数据、渲染页面
  • 两者配合,发挥各自优势,提高开发效率和系统可维护性。

六、JSP 的作用域对象

JSP 支持四种作用域对象,按作用范围从小到大排列如下:

作用域 类型 生命周期
pageContext PageContext 页面内有效
request HttpServletRequest 请求范围内有效
session HttpSession 用户会话期间有效
application ServletContext 整个应用生命周期内有效

📌 使用原则:

  • 尽量使用最小的作用域,减少资源占用。
  • 避免滥用 session 或 application 存储大量数据。

七、JSP 的九大内置对象

对象名 类型 用途
pageContext PageContext 页面上下文,管理其他作用域
request HttpServletRequest 获取请求信息
response HttpServletResponse 设置响应内容
session HttpSession 管理会话状态
application ServletContext 应用上下文
config ServletConfig 获取配置信息
exception Throwable 错误信息处理(需设置 isErrorPage=true
page Object 当前 JSP 对应的 Servlet 实例
out JspWriter 向浏览器输出内容

八、常见问题与技巧

1. 如何调试 JSP?

  • 查看对应的 .java 文件(位于 work/Catalina/... 目录下)
  • 分析翻译后的 Java 代码,有助于理解执行流程和排查错误

2. JSP 文件扩展名可以修改吗?

  • 可以通过修改 web.xml 文件中的 <servlet-mapping> 来更改默认扩展名:
1
2
3
4
5
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

⚠️ 注意:修改后需重启服务器生效。

3. JSP 可以单独开发 Web 应用吗?

  • 可以,因为 JSP 本质就是 Servlet,可以在 <% %> 中写 JDBC、业务逻辑等。
  • 但不推荐,会导致代码混乱、难以维护。

九、实战案例:登录功能实现

1. 需求背景

  • 系统需要登录验证,防止未授权用户访问敏感操作。

2. 实现步骤

  1. 创建用户表 t_user

    1
    2
    3
    4
    5
    
    CREATE TABLE t_user (
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(50),
        password VARCHAR(50)
    );
    
  2. 创建登录页面 login.jsp

  3. 创建登录处理 Servlet

    • 成功跳转至部门列表页
    • 失败跳转至失败提示页
  4. 添加拦截机制(后续章节重点)


十、JSP 指令详解

1. 指令的作用

指导 JSP 引擎如何翻译当前文件。

2. 常见指令

指令 说明
<%@ include file="xxx.jsp" %> 静态包含另一个 JSP 文件
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 引入标签库(如 JSTL)
<%@ page ... %> 页面相关配置(最常用)

十一、JSP 开发建议

建议 说明
尽量少写 Java 代码 JSP 更适合做视图层,逻辑应由 Servlet 或后端服务处理
统一编码格式 使用 pageEncodingcontentType 统一设置编码,避免乱码
合理使用作用域 根据需求选择合适的存储空间,避免内存浪费
分清职责边界 JSP 负责展示,Servlet 负责处理,保持代码整洁
调试时查看翻译后的 Java 文件 有助于理解执行流程,排查问题

十二、拓展知识:JavaBean

什么是 JavaBean?

  • 符合一定规范的 Java 类,常用于封装数据。
  • 特征包括:
    • 提供无参构造方法
    • 属性私有化
    • 提供公开的 getter/setter 方法
    • 实现 Serializable 接口
    • 可重写 toString()hashCode()equals() 方法

📦 JavaBean 是实体类的标准形式,广泛应用于 MVC 架构的数据传递。


总结

JSP 是 Java Web 开发中非常重要的组成部分,虽然现在被 Thymeleaf、Freemarker 等模板引擎部分替代,但在传统项目中仍广泛应用。掌握其工作原理、语法结构、与 Servlet 的协同关系,是构建高质量 Web 应用的基础。

EL 表达式详解

一、什么是 EL 表达式?

  • 全称:Expression Language(表达式语言)
  • 作用
    • 替代 JSP 中的 Java 脚本代码(如 <% %><%= %>),使页面更加整洁、易于维护。
    • 是 JSP 技术的一部分,主要用于简化从作用域中获取数据并输出到浏览器的过程。
  • 出现背景
    • 在早期 JSP 页面中夹杂大量 Java 代码,导致页面混乱,难以阅读与维护。
    • 为了解决这个问题,引入了 EL 表达式。

二、EL 表达式的核心功能

EL 表达式主要有三大核心功能:

  1. 从某个作用域中取数据

    • 支持四个作用域(范围从小到大):
      • pageContext(当前页面)
      • request(一次请求)
      • session(一次会话)
      • application(整个应用)
    • 取值时,优先从最小的作用域中查找。
  2. 将取出的数据转换为字符串

    • 如果是对象,自动调用其 toString() 方法进行转换。
  3. 将字符串输出到浏览器

    • 等同于 <%= %> 的效果。

三、基本语法格式

1
${表达式}

示例:

1
${userObj} 

等价于:

1
<%= request.getAttribute("userObj") %>

四、使用场景与实例解析

4.1 数据存入作用域

在 JSP 中,必须先将数据放入某个作用域中,EL 才能访问:

1
2
3
4
5
6
7
8
<%
    User user = new User();
    user.setUsername("jackson");
    user.setPassword("1234");
    user.setAge(50);
    
    request.setAttribute("userObj", user); // 存入 request 作用域
%>

4.2 使用 EL 获取数据

1
2
${userObj} <!-- 输出对象 toString() 的结果 -->
${userObj.username} <!-- 输出 username 属性,前提是 User 类有 getUsername() 方法 -->

⚠️ 注意:如果没有对应的 getXxx() 方法,会抛出异常(500 错误)。

4.3 面试题解析

${abc} vs ${"abc"}

表达式 含义
${abc} 从作用域中取出名为 "abc" 的属性值
${"abc"} 直接输出字符串 "abc",不从作用域中取值

五、EL 表达式的隐式对象

EL 提供了一些常用的内置对象,方便开发者直接访问上下文信息:

对象名 说明
pageContext 当前页面上下文对象
param 获取请求参数(单个值)
paramValues 获取请求参数(数组形式)
initParam 获取 web.xml 中的初始化参数
pageScope 访问 page 作用域中的数据
requestScope 访问 request 作用域中的数据
sessionScope 访问 session 作用域中的数据
applicationScope 访问 application 作用域中的数据

六、EL 表达式对特殊字符的支持

如果 key 名中包含特殊字符(如 .),建议使用 [ ] 操作符:

1
request.setAttribute("abc.def", "zhangsan");

错误写法:

1
${requestScope.abc.def} <!-- 无法正确解析 -->

正确写法:

1
${requestScope["abc.def"]}

七、EL 表达式操作集合与数组

7.1 Map 集合

1
${map.keyName}

7.2 数组 / List 集合

1
2
${array[0]}
${list[1]}

八、控制 EL 表达式是否启用

可以通过 JSP 的 page 指令全局控制 EL 表达式是否启用:

1
<%@ page isELIgnored="true" %>
  • isELIgnored="true":忽略所有 EL 表达式
  • isELIgnored="false":启用 EL 表达式(默认)

也可以通过 \ 符号局部禁用 EL:

1
\${username} <!-- 不会被解析为 EL 表达式 -->

九、常用 EL 表达式技巧

9.1 获取项目根路径

1
${pageContext.request.contextPath}

等价于:

1
request.getContextPath()

9.2 运算符支持

类型 示例
算术运算 +, -, *, /, %
关系运算 == (eq), !=, >, >=, <, <=
逻辑运算 not, and, or
条件运算 ${a ? b : c}
判空运算 empty / not empty

示例:

1
${empty param.username} <!-- 判断是否为空 -->

十、EL 表达式的优势与适用场景

优势 说明
简洁易读 替代复杂 Java 脚本,提升可读性
自动类型转换 自动调用 toString() 方法
支持嵌套对象访问 ${user.address.zipCode}
安全处理 null null 被转换为空字符串,避免报错
支持多种集合类型 Map、List、Array 等

✅ 推荐使用场景:JSP 页面中展示数据时,替代 <%= %><% %>,保持页面干净整洁。


十一、总结

EL 表达式是 JSP 开发中非常重要的工具,能够显著提高页面的可读性和可维护性。掌握其基本语法、作用域机制、隐式对象及常见操作,有助于构建更优雅的 Web 应用程序。


如果你正在开发传统的 JSP 项目,强烈建议多使用 EL 表达式,减少 Java 脚本的使用。同时,也可以配合 JSTL 标签库一起使用,实现更强大的页面逻辑控制。

如需进一步学习,推荐搭配 JSTL 核心标签库 一起使用,提升开发效率。

JSTL 标签库(Java Standard Tag Library)

JSTL 是 Java Web 开发中用于简化 JSP 页面逻辑处理的标准标签库。它与 EL 表达式配合使用,旨在减少或替代 JSP 中的 Java 脚本代码,提升代码可读性和维护性。


一、什么是 JSTL 标签库?

  • 全称: Java Standard Tag Lib
  • 作用:
    • 替代 JSP 中 <% %> 的 Java 代码。
    • 提供结构化、语义化的标签语法。
    • 支持条件判断、循环、变量赋值等常见逻辑控制。
  • 实现原理:
    • JSTL 标签在 JSP 中被解析为对应的 Java 类(封装在 jar 包中)。
    • 实际上,每个标签背后都对应一个实现了特定功能的 Java 类。

💡 小贴士:
虽然 JSTL 可以简化页面逻辑,但复杂的业务逻辑仍建议放在后端控制器中处理,保持视图层简洁。


二、使用 JSTL 标签库的步骤

1. 引入 JSTL 相关依赖包

  • Tomcat 10+ 所需的 jar 包:

    • jakarta.servlet.jsp.jstl-api-2.0.0.jar(接口)
    • jakarta.servlet.jsp.jstl-2.0.0.jar(具体实现类)
  • IDEA 配置方法:

    • 将上述两个 jar 文件复制到项目的 /WEB-INF/lib/ 目录下。
    • 右键 → Add as Library... 添加为项目库。
    • 确保部署时这些 jar 包会被打包进 WAR 文件。

⚠️ 注意:所有需要随项目部署的 jar 包都应放入 /WEB-INF/lib 目录中,这样 Tomcat 在运行时才能正确加载它们。


2. 在 JSP 页面中引入标签库

使用 <%@ taglib %> 指令引入 JSTL 标签库,例如核心标签库:

1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  • prefix="c":给标签库起个别名,通常用 c 表示 core。
  • uri:指定标签库的唯一标识符,指向对应的 .tld 配置文件。

3. 使用 JSTL 标签

在 JSP 页面中使用类似如下语法调用标签:

1
2
3
<c:if test="${user.age > 18}">
    成年用户
</c:if>

表面上是标签,实际上底层会调用相应的 Java 类来执行逻辑。


三、JSTL 标签的工作原理

1. TLD 文件的作用

  • TLD(Tag Library Descriptor)是一个 XML 文件,描述了标签库的元信息。

  • 它定义了:

    • 标签名称
    • 对应的 Java 类
    • 标签体内容类型
    • 属性及其特性(是否必需、是否支持 EL)
  • 位置:

    • jakarta.servlet.jsp.jstl-2.0.0.jarMETA-INF/c.tld 文件中。

2. 示例 TLD 片段解析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<tag>
    <name>catch</name>
    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
    <body-content>JSP</body-content>
    <attribute>
        <name>var</name>
        <required>false</required>
        <rtexprvalue>false</rtexprvalue>
    </attribute>
</tag>
  • tag-class:标签对应的 Java 类。
  • body-content:标签体内允许的内容类型。
  • attribute:定义标签属性行为。

四、JSTL 核心标签库常用标签详解

以下是 JSTL Core 标签库中最常用的几个标签及其用法说明:

1. <c:if>

用于条件判断,相当于 Java 中的 if

1
2
3
<c:if test="${user.age >= 18}">
    已成年
</c:if>
  • test 属性接受布尔表达式,支持 EL 表达式。

2. <c:forEach>

用于遍历集合或进行计数循环。

遍历集合:

1
2
3
<c:forEach items="${users}" var="user">
    ${user.name}
</c:forEach>
  • items:要遍历的数据源(如 List、Map)。
  • var:每次迭代的元素变量名。
  • varStatus(可选):提供索引、计数等状态信息。

计数循环:

1
2
3
<c:forEach var="i" begin="1" end="10" step="2">
    ${i}
</c:forEach>
  • beginendstep 控制循环范围和步长。

3. <c:choose>, <c:when>, <c:otherwise>

组合使用实现多条件分支判断,类似于 Java 的 switch-case 或多个 if-else

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<c:choose>
    <c:when test="${param.age < 18}">
        青少年
    </c:when>
    <c:when test="${param.age < 35}">
        青年
    </c:when>
    <c:when test="${param.age < 55}">
        中年
    </c:when>
    <c:otherwise>
        老年
    </c:otherwise>
</c:choose>
  • <c:choose> 是外层容器。
  • <c:when> 是条件分支。
  • <c:otherwise> 是默认情况(可选)。

五、补充建议与最佳实践

  1. 避免过度使用 JSTL:

    • JSTL 不适合处理复杂业务逻辑,建议将数据处理放在 Controller 层,JSP 只负责展示。
  2. EL 表达式搭配使用:

    • JSTL 和 EL 表达式是“黄金搭档”,能有效减少脚本嵌套,提高可读性。
  3. 版本兼容性注意:

    • JSTL 1.x 适用于 Servlet 2.x / JSP 2.x。
    • JSTL 2.x 适用于 Servlet 3.x / JSP 2.3,推荐用于现代项目。
  4. 推荐使用 Spring MVC + Thymeleaf:

    • 如果你正在开发新项目,考虑使用 Spring Boot + Thymeleaf 模板引擎,其功能更强大且更现代化。

六、总结

内容 说明
目标 替代 JSP 中的 Java 脚本代码,提升可读性
核心组件 JSTL Core 标签库(c:if, c:forEach, c:choose 等)
关键配置 引入 JAR 包、在 JSP 中使用 <%@ taglib %> 声明
底层机制 TLD 描述标签与 Java 类之间的映射关系
最佳实践 仅用于视图逻辑,避免复杂运算;推荐结合 EL 使用

改造OA

  • 使用什么技术改造呢?

    • Servlet + JSP + EL表达式 + JSTL标签。进行改造。
  • 在前端HTML代码中,有一个标签,叫做base标签,这个标签可以设置整个网页的基础路径。

    • 这是Java的语法,也不是JSP的语法。是HTML中的一个语法。HTML中的一个标签。通常出现在head标签中。

    • < base href=“http://localhost:8080/oa/">

    • 在当前页面中,凡是路径没有以“/”开始的,都会自动将base中的路径添加到这些路径之前。

      • < a href=“ab/def”></ a>
      • 等同于:< a href=“http://localhost:8080/oa/ab/def”></ a>
    • 需要注意:在JS代码中的路径,保险起见,最好不要依赖base标签。JS代码中的路径最好写上全路径。

    • 1
      
      <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
      

Filter 过滤器详解

一、问题背景:OA项目中重复代码的问题

在 OA 项目开发中,我们通常会遇到多个 Servlet(如 DeptServletEmpServletOrderServlet)都需要执行一些公共操作的情况。例如:

  • 用户登录验证:每个 Servlet 执行前都要判断用户是否已登录。
  • 中文乱码处理:每次请求都需要设置编码格式。

这些问题导致了大量重复代码,降低了代码复用性和可维护性。

解决方案:

使用 Servlet 规范中的 Filter(过滤器)机制 来集中处理这些公共逻辑。


二、Filter 是什么?有什么作用?

1. 定义

Filter(过滤器)是 Servlet 规范的一部分,用于在请求到达目标 Servlet 前或响应返回客户端前进行预处理或后处理。

2. 主要用途

  • 统一处理请求(如权限校验、日志记录)
  • 统一处理响应(如压缩输出、添加响应头)
  • 避免代码冗余,提高模块化和可维护性

3. 执行原理

Filter 的执行流程如下:

1
客户端请求 → Filter1 → Filter2 → ... → 目标Servlet → FilterN → FilterN-1 → ... → 客户端响应

Filter 可以在请求处理的前后插入逻辑,类似于 AOP(面向切面编程)的思想。


三、如何编写一个 Filter?

步骤一:创建 Filter 类

实现 javax.servlet.Filter 接口,并重写三个核心方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyFilter 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.setContentType("text/html;charset=UTF-8");

        // 放行请求,继续执行下一个 Filter 或目标 Servlet
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 销毁 Filter 前调用一次,用于释放资源
    }
}

步骤二:配置 Filter

方式一:web.xml 配置

1
2
3
4
5
6
7
8
9
<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>com.example.filter.MyFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

方式二:注解方式(推荐)

1
2
@WebFilter("*.do")
public class MyFilter implements Filter { ... }

四、Filter 的生命周期

Filter 生命周期与 Servlet 类似,但有以下区别:

对象类型 默认实例化时间 是否单例
Filter 服务器启动时自动实例化 ✅ 是
Servlet 第一次请求时才实例化 ✅ 是

五、Filter 的匹配规则

Filter 的 URL 匹配方式非常灵活,常见有以下几种:

匹配方式 示例 说明
精确匹配 /login.do 仅匹配指定路径
后缀匹配 *.do 匹配所有以 .do 结尾的请求
路径前缀匹配 /dept/* 匹配 /dept/add/dept/edit
通配符匹配 /* 匹配所有请求

六、Filter 的执行顺序

Filter 的执行顺序取决于配置方式:

1. web.xml 中的顺序

Filter 的执行顺序<filter-mapping> 标签的书写顺序有关,越靠上优先级越高。

示例:

1
2
<filter-mapping><filter-name>A</filter-name></filter-mapping>
<filter-mapping><filter-name>B</filter-name></filter-mapping>

执行顺序为:A → B → Servlet → B → A

2. 使用 @WebFilter 注解

Filter 的执行顺序默认按照类名排序决定:

  • FilterA 会比 FilterB 更早执行。
  • Filter1 会比 Filter2 更早执行。

七、责任链设计模式(Chain of Responsibility Pattern)

Filter 的最大优势在于其支持 责任链设计模式,即:

  • 在运行阶段动态组合 Filter 的执行顺序。
  • 不依赖编译时的硬编码,而是通过配置文件控制流程。

核心思想:

将多个对象连接成一条链,请求在这条链上传递,直到有一个对象处理它为止。

应用场景:

  • 登录验证 → 日志记录 → 编码处理
  • 多层权限拦截
  • 请求缓存控制

八、Filter 和 Servlet 的关系

特点 Filter Servlet
是否自动初始化 ✅ 是 ❌ 否(首次请求时初始化)
是否单例 ✅ 是 ✅ 是
是否处理请求 ✅ 是(前置/后置) ✅ 是(处理业务逻辑)
是否决定执行下一级 ✅ 是(通过 chain.doFilter() ❌ 否

九、实战应用:使用 Filter 改造 OA 项目

场景需求:

  • 所有以 .do 结尾的请求都必须先登录。
  • 所有请求都需统一设置编码。

实现步骤:

1. 创建登录验证 Filter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@WebFilter("*.do")
public class LoginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");

        if (user == null) {
            res.sendRedirect(req.getContextPath() + "/login.jsp");
        } else {
            chain.doFilter(request, response);
        }
    }
}

2. 创建编码处理 Filter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@WebFilter("*.do")
public class EncodingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        chain.doFilter(request, response);
    }
}

3. 效果

  • 用户未登录时,会被跳转到登录页面。
  • 所有请求无需手动设置编码,避免中文乱码。

十、总结与建议

优点总结:

  • 减少重复代码,提高代码复用率
  • 提升系统的可维护性和扩展性
  • 支持责任链设计模式,灵活控制执行流程

使用建议:

  • 将公共逻辑提取到 Filter 中(如登录验证、编码设置、日志记录等)
  • 合理配置 Filter 的匹配路径,避免影响非目标资源(如静态资源)
  • 注意 Filter 的执行顺序,合理安排逻辑顺序

Listener 监听器(监听器机制)

一、什么是监听器?

监听器是 Servlet 规范 中提供的一种回调机制,用于在特定事件发生时自动执行预定义的代码逻辑。

  • 类似于 Filter 过滤器,都是 Servlet 规范的一部分。
  • 所有监听器接口名称都以 Listener 结尾,例如 ServletContextListenerHttpSessionListener 等。

二、监听器的作用

监听器允许我们在 Web 应用生命周期中的关键节点域对象状态变化时 插入自定义逻辑。

  • 举例:
    • 应用启动或关闭时初始化资源或释放资源;
    • 用户登录/退出时更新在线人数;
    • Session 创建或销毁时记录日志等。

三、Servlet 规范中常见的监听器分类

1. jakarta.servlet 包下的通用监听器

监听器名称 功能描述
ServletContextListener 监听整个 Web 应用的上下文(ServletContext)的创建与销毁
ServletContextAttributeListener 监听 ServletContext 域中属性的添加、替换、移除
ServletRequestListener 监听每次请求的创建与销毁
ServletRequestAttributeListener 监听 ServletRequest 域中属性的变化

2. jakarta.servlet.http 包下的 HTTP 专用监听器

监听器名称 功能描述
HttpSessionListener 监听 HttpSession 的创建与销毁
HttpSessionAttributeListener 监听 HttpSession 域中属性的添加、替换、移除
HttpSessionBindingListener 对象绑定或解绑到 session 时触发(无需配置)
HttpSessionIdListener session ID 改变时触发
HttpSessionActivationListener 监听 session 的钝化(序列化到磁盘)与活化(从磁盘恢复)

小贴士:

  • HttpSessionBindingListener 是唯一一个不需要使用 @WebListener 注解或在 web.xml 中注册的监听器。
  • 只要某个类实现了该接口,当它被加入或移出 session 时,会自动触发相应方法。

四、实现监听器的基本步骤(以 ServletContextListener 为例)

步骤一:编写监听器类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@WebListener // 使用注解方式注册监听器
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("应用启动,执行初始化操作...");
        // 可以在这里加载数据库连接池、读取配置文件等
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("应用关闭,执行清理工作...");
        // 可以在这里关闭数据库连接池等
    }
}

步骤二:注册监听器(两种方式)

方式一:使用 @WebListener 注解(推荐)

只需在类上加上 @WebListener 即可自动注册。

方式二:在 web.xml 中手动配置

1
2
3
<listener>
    <listener-class>com.example.MyServletContextListener</listener-class>
</listener>

五、监听器方法的调用时机

所有监听器中的方法都不是由程序员主动调用的,而是由 Web 容器(如 Tomcat)根据事件自动触发的

  • 例如:
    • 当 Web 应用启动时,自动调用 contextInitialized()
    • 当用户访问页面时,自动调用 requestInitialized()
    • 当 session 被创建或销毁时,自动调用 sessionCreated()sessionDestroyed()

六、典型业务场景分析

场景一:统计当前网站的在线用户数

思路:

  • 每当一个新的 HttpSession 被创建时,表示有一个新用户访问;
  • 每当一个 HttpSession 被销毁时,表示用户离开;
  • 利用 HttpSessionListener 实现计数器逻辑。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@WebListener
public class OnlineUserCounter implements HttpSessionListener {

    private static int onlineCount = 0;

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        onlineCount++;
        se.getSession().getServletContext().setAttribute("onlineUsers", onlineCount);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        onlineCount--;
        se.getSession().getServletContext().setAttribute("onlineUsers", onlineCount);
    }
}

📌 注意:此方案统计的是所有会话,包括未登录用户。


场景二:仅统计已登录用户的在线数量

问题背景:

  • 需要区分“访客”和“登录用户”;
  • 登录标志通常是将用户信息存入 session,如 session.setAttribute("user", userObj)
  • 用户登出则是 session.removeAttribute("user") 或 session 超时。

解决方案:

使用 HttpSessionBindingListener 接口,让 User 类实现该接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class User implements HttpSessionBindingListener {

    private String username;

    public User(String username) {
        this.username = username;
    }

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        // 用户登录时触发
        Integer count = (Integer) event.getSession().getServletContext().getAttribute("loginUsers");
        if (count == null) count = 0;
        count++;
        event.getSession().getServletContext().setAttribute("loginUsers", count);
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        // 用户退出时触发
        Integer count = (Integer) event.getSession().getServletContext().getAttribute("loginUsers");
        if (count == null || count <= 0) count = 0;
        else count--;
        event.getSession().getServletContext().setAttribute("loginUsers", count);
    }
}

✅ 优势:

  • 更加精确地统计“登录用户”的在线情况;
  • 不依赖 session 的创建/销毁,只关注用户是否真正登录。

七、实际项目案例:OA 系统中统计登录用户数

1. 登录标识判断

  • 用户登录成功后,通常会执行如下代码:

    1
    
    session.setAttribute("user", user); // 表示用户已登录
    
  • 因此可以利用 HttpSessionBindingListener 来监听 user 对象的绑定和解绑。

2. 用户退出标识

  • 用户登出时执行:

    1
    
    session.removeAttribute("user"); // 用户主动登出
    
  • 或者 session 超时自动销毁,也会触发 valueUnbound() 方法。


八、总结

功能 使用的监听器
应用启动/关闭 ServletContextListener
请求开始/结束 ServletRequestListener
Session 创建/销毁 HttpSessionListener
Session 属性变化 HttpSessionAttributeListener
用户登录/退出(精准) HttpSessionBindingListener
Session ID 变化 HttpSessionIdListener
Session 钝化/活化 HttpSessionActivationListener

九、最佳实践建议

  1. 合理选择监听器类型:根据需求选择最合适的监听器,避免过度监听。
  2. 注意线程安全问题:多个用户并发访问时,共享变量(如在线人数)需考虑同步控制。
  3. 不要在监听器中执行耗时操作:如数据库查询、网络请求等,会影响系统性能。
  4. 使用注解简化配置:优先使用 @WebListener 注解而非 XML 配置。