JavaWeb
概念
静态Web: 数据不会改变,已经很少使用
动态Web:数据动态改变,几乎所有现代网站都是动态的,需要Servlet/Jsp、Asp、PHP等技术支持
JavaWeb是动态Web开发技术的统称
Web应用由html、css、js、jsp/servlet、java程序、jar/war包、配置文件等组成,由服务器统一管理
静态Web
文件类型
.html、.htm
特点
页面内容固定不变
无法与数据库交互(数据无法持久化)
可以通过 JavaScript 实现伪动态效果
动态Web
技术支撑
Servlet、JSP、PHP、ASP、Node.js 等
特点
页面内容可根据用户请求动态变化
支持与数据库交互,实现数据持久化
后台更新后需重新部署
支持复杂功能(登录、权限控制、支付等)
常见Web技术
ASP | 微软 | 微软开发、在HTML脚本中嵌入VB脚本,代码量庞大,页面混乱,难以维护 |
PHP | 开源 | 快速开发、语法灵活、跨平台,适合中小型网站、无法承载大流量访问 |
JSP/Servlet | Sun / Oracle | 基于 Java,支持高并发、可扩展性强,适合大型项目 |
常见Web服务器
IIS | 微软 | 支持 ASP,Windows 系统常用 |
Apache HTTP Server | Apache 基金会 | 主要用于静态资源服务,常配合 Tomcat 使用 |
Tomcat | Apache 基金会 | 轻量级 Servlet 容器,免费开源,适合中小型 JavaWeb 项目 |
Jetty | Eclipse 基金会 | 更轻量,嵌入式部署友好 |
WebLogic | Oracle | 重量级 Java EE 服务器,企业级应用 |
Nginx | 开源 | 高性能反向代理服务器,常用于负载均衡和静态资源服务 |
服务器是被动操作的,处理用户请求并返回响应信息
Web架构
C/S 架构 | Client/Server | 客户端+服务器,需要安装客户端软件(如 QQ、微信) |
B/S 架构 | Browser/Server | 浏览器+服务器,无需安装客户端,通过浏览器访问(如淘宝、百度) |
B/S 架构是当前主流 Web 应用架构
网络协议
HTTP
超文本传输协议,是一个简单的请求响应型应用层协议,基于TCP(传输控制协议),数据明文传输,默认端口为80
HTTPS
HTTPS 是 HTTP 的安全版本,通过 SSL(Secure Sockets Layer)或 TLS(Transport Layer Security)加密技术为网络通信提供安全保障,默认端口为443
HTTP/1.0
短连接:每个HTTP请求都需要单独建立TCP连接,完成之后立即关闭连接。这种方式增加了网络延迟,因为每次请求都需要经历三次握手过程。
HTTP/1.1
长连接:引入了持久连接(Persistent Connection),允许在一个TCP连接上发送多个请求和接收多个响应,从而减少了重复建立连接的时间消耗。 管道化:支持请求管道化,即客户端可以在不等待前一个响应的情况下连续发送多个请求,不过服务器仍然按顺序响应这些请求。
HTTP请求
请求行 | 包括请求方法(如GET, POST)、请求URI、HTTP版本 | GET /index.html HTTP/1.1 |
请求头 | 提供关于请求的元数据,如客户端类型、可接受的内容类型等 | Host: www.example.com , User-Agent: Mozilla/5.0 等 |
空行 | 标识头部结束,消息体开始 | (\r\n ) |
消息体 | 可选,通常用于POST或PUT请求中携带的数据 | 表单数据、JSON、XML等 |
请求头
Accept |
客户端可处理的内容类型(如 text/html , application/json )。 |
Accept-Charset |
客户端接受的字符集编码(如 utf-8 , iso-8859-1 )。 |
Accept-Encoding |
指定客户端能够理解的内容编码方式(如 gzip , deflate )。 |
Accept-Language |
客户端希望接收的语言版本(如 en-US , zh-CN )。 |
Authorization |
用于身份验证的信息(通常配合服务端的认证机制使用)。 |
Cache-Control |
控制缓存的行为(如 no-cache , max-age=3600 )。 |
Connection |
控制网络连接的行为(如 keep-alive , close )。 |
Content-Length |
请求体的长度(字节)。 |
Content-Type |
请求体的数据类型(如 application/x-www-form-urlencoded , multipart/form-data )。 |
Cookie |
存储在客户端并随请求发送到服务器的cookie数据。 |
Host |
目标主机和端口号。 |
If-Modified-Since |
只有当资源自指定日期以来被修改时才返回资源内容。 |
Referer |
发出请求的页面URL。 |
User-Agent |
发送请求的客户端信息(包括浏览器类型、操作系统等)。 |
HTTP响应
状态行 | 包括HTTP版本、状态码及描述 | HTTP/1.1 200 OK |
响应头 | 提供关于响应的元数据,如内容类型、长度、缓存控制等 | Content-Type: text/html; charset=UTF-8 , Content-Length: 1024等 |
空行 | 标识头部结束,消息体开始 | (\r\n ) |
消息体 | 可选,通常包含返回给客户端的实际内容 | HTML文档、图片、JSON数据等 |
响应头
Content-Length |
响应体的大小(字节)。 |
Content-Type |
响应体的数据类型(如 text/html; charset=UTF-8 , application/json )。 |
Content-Encoding |
内容编码格式(如 gzip , deflate )。 |
Content-Disposition |
提供有关如何处理响应内容的建议(如 attachment; filename="filename.jpg" )。 |
Cache-Control |
指示是否可以缓存响应及如何缓存(如 public , private , no-store )。 |
Expires |
设置响应被认为过期的时间点。 |
Set-Cookie |
向客户端设置一个cookie。 |
Access-Control-Allow-Origin |
指定哪些域可以访问资源(CORS机制的一部分)。 |
Age |
资源在代理缓存中存在的时间(秒)。 |
Content-Language |
内容的语言类型(如 en , zh )。 |
Date |
响应生成的日期和时间。 |
ETag |
标识特定版本的资源,以便检测更新或避免重复获取相同内容。 |
Last-Modified |
所请求资源最后一次修改的时间。 |
Location |
重定向的目标地址(通常用于 3xx 状态码)。 |
Server |
发送响应的服务器软件信息。 |
HTTP状态码
信息性响应 (100–199)
100 | Continue | 客户端应继续其请求。 |
101 | Switching Protocols | 服务器理解并正在切换到客户端请求的新协议。 |
102 | Processing | 请求将被处理,但需要一些时间完成。 |
103 | Early Hints | 提供一些关于最终响应的信息提示。 |
成功响应 (200–299)
200 | OK | 请求成功。 |
201 | Created | 请求成功并且服务器创建了新的资源。 |
202 | Accepted | 已接受请求,但尚未处理完成。 |
203 | Non-Authoritative Information | 返回的信息不是原始服务器确定的有效表示。 |
204 | No Content | 请求成功,但没有返回任何内容。 |
205 | Reset Content | 请求成功,但用户代理应重置视图。 |
206 | Partial Content | 服务器正在发送部分数据。 |
207 | Multi-Status | 可能包含多个单独响应的代码。 |
208 | Already Reported | DASL中定义,指示绑定已经报告过。 |
226 | IM Used | 服务器已完成对资源的请求,并且响应是对所请求实例的表示。 |
重定向消息 (300–399)
300 | Multiple Choices | 针对请求存在多个可能的响应。 |
301 | Moved Permanently | 所请求的页面已经永久移动到新位置。 |
302 | Found | 所请求的页面临时移到另一个位置。 |
303 | See Other | 应该通过GET方法来定位资源。 |
304 | Not Modified | 资源未被修改,可以使用缓存。 |
305 | Use Proxy | 请求的资源必须通过指定的代理访问。 |
307 | Temporary Redirect | 页面临时移到另一个位置,不改变请求方法。 |
308 | Permanent Redirect | 页面永久移到另一个位置,不改变请求方法。 |
客户端错误响应 (400–499)
400 | Bad Request | 因为语法无效,服务器无法理解此请求。 |
401 | Unauthorized | 当前请求需要用户验证。 |
402 | Payment Required | 此响应代码保留以备将来使用。 |
403 | Forbidden | 服务器拒绝执行所请求的操作。 |
404 | Not Found | 服务器找不到请求的网页。 |
405 | Method Not Allowed | 禁用请求中指定的方法。 |
406 | Not Acceptable | 服务器只能生成不被请求头接受的响应。 |
407 | Proxy Authentication Required | 类似于401,但要求先通过代理服务器的身份验证。 |
408 | Request Timeout | 服务器等待客户端发送请求的时间过长。 |
409 | Conflict | 由于与资源当前状态冲突,请求无法完成。 |
410 | Gone | 所请求的资源不再可用且无更多信息。 |
411 | Length Required | 服务器不接受不含实体长度的有效请求。 |
412 | Precondition Failed | 前提条件失败导致请求失败。 |
413 | Payload Too Large | 请求实体过大,超出了服务器处理能力。 |
414 | URI Too Long | 请求URI过长,服务器无法处理。 |
415 | Unsupported Media Type | 不支持的媒体类型。 |
416 | Range Not Satisfiable | 客户端请求的部分范围不在有效范围内。 |
417 | Expectation Failed | 服务器无法满足Expect请求头字段的要求。 |
418 | I'm a teapot | 这是一个愚人节笑话RFC中的状态码。 |
421 | Misdirected Request | 服务器无法产生响应。 |
422 | Unprocessable Entity | 请求格式正确,但由于含有语义错误而无法响应。 |
424 | Failed Dependency | 由于之前的请求失败,导致当前请求失败。 |
425 | Too Early | 表示服务器不愿意冒险处理一个可能被重放的请求。 |
426 | Upgrade Required | 客户端应当切换到TLS/1.0。 |
428 | Precondition Required | 原始服务器要求请求是条件性的。 |
429 | Too Many Requests | 用户在给定时间内发送了太多请求。 |
431 | Request Header Fields Too Large | 请求头字段太大。 |
451 | Unavailable For Legal Reasons | 由于法律原因不可用。 |
服务器错误响应 (500–599)
500 | Internal Server Error | 服务器遇到未知情况,阻止其完成请求。 |
501 | Not Implemented | 服务器不具备完成请求的功能。 |
502 | Bad Gateway | 作为网关或代理服务器,从上游服务器收到无效响应。 |
503 | Service Unavailable | 服务器暂时无法使用(可能是过载或维护)。 |
504 | Gateway Timeout | 作为网关或代理服务器,未能及时收到上游服务器的响应。 |
505 | HTTP Version Not Supported | 服务器不支持请求中使用的HTTP版本。 |
506 | Variant Also Negotiates | 内部配置错误。 |
507 | Insufficient Storage | 服务器无法存储完成请求所需的内容。 |
508 | Loop Detected | 服务器检测到请求处理中出现无限循环。 |
510 | Not Extended | 需要进一步扩展才能完成请求。 |
511 | Network Authentication Required | 客户端需要进行身份验证才能获得网络访问权限。 |
HTTP方法
GET | 获取资源,参数在 URL 中可见 |
POST | 提交数据,参数在请求体中,更安全 |
PUT | 更新资源(整个替换) |
PATCH | 更新资源(局部更新) |
DELETE | 删除资源 |
HEAD | 获取资源头部信息 |
OPTIONS | 获取服务器支持的方法 |
当用户在浏览器输入域名并回车后发生了什么?
首先,浏览器会检查其内部的DNS缓存(如果有的话)以寻找之前访问该域名时存储的IP地址。
如果未找到,系统还会检查本机的hosts文件(Windows路径为C:\Windows\System32\drivers\etc\hosts,Linux/macOS路径为/etc/hosts)。如果在此文件中找到了对应的域名和IP地址映射,则直接使用该IP地址进行连接。
如果未找到,操作系统将查询自身的DNS缓存。这包括了最近解析过的域名及其对应的IP地址记录。
如果未找到,计算机会联系预设的DNS服务器(通常是您的ISP提供的,也可以是自定义的如Google的8.8.8.8或Cloudflare的1.1.1.1等公共DNS服务)。这个DNS服务器可能会执行递归查询,这意味着它会代替客户端向其他DNS服务器询问直到找到正确的答案。 DNS递归查询: DNS查询可能涉及多个步骤,包括查询根DNS服务器、顶级域(TLD)DNS服务器(例如.com、.org等),以及最终的目标域名的权威DNS服务器。 一旦找到匹配的IP地址,这个信息会被返回给用户的计算机,并且通常也会被缓存起来以便未来的请求使用。
如果以上步骤均未获得IP地址,浏览器会返回错误
浏览器根据获得的IP地址尝试与目标服务器建立TCP连接(如果是HTTPS还需完成SSL/TLS握手)。 建立连接成功后,浏览器发送HTTP请求(对于现代网站,通常是HTTPS请求),请求页面的内容。
服务器处理请求并返回HTTP响应,包含HTML文档、CSS样式表、JavaScript脚本等资源。
浏览器开始解析这些资源,并逐步构建DOM树、加载图片、执行脚本等,直至网页完全呈现给用户。
为什么还要学习 Servlet/Jsp ?
学习基本的 Servlet 有助于理解 Spring MVC(Spring MVC 的核心是 DispatcherServlet) 等高级Java框架的底层原理,进而掌握高级框架的使用方法。
此外其也有助于掌握 Tomcat 的使用方法(部署、调优等)。
不必在 Servlet/Jsp 中太过深入学习,理解其总体原理即可,毕竟现在主流是 前后端分离 + 模板引擎
Servlet 生命周期(init/doGet/doPost/destroy) | ✅ 必须掌握 |
请求和响应对象(HttpServletRequest / HttpServletResponse) | ✅ 必须掌握 |
过滤器(Filter) | ✅ 掌握基本用法(如编码处理、权限验证) |
监听器(Listener) | ✅ 了解监听事件类型 |
JSP 的执行过程和 EL 表达式 | ✅ 了解即可 |
Session 和 Cookie | ✅ 必须掌握,尤其是 Session 机制 |
Session 机制不可不品,它解决了 HTTP 协议无状态的问题,实现了用户身份的识别和数据的持久化。
Tomcat
下载
Apache Tomcat® - Apache Tomcat 9 Software Downloads
安装
解压,将文件夹移动到自己想要的路径
环境变量配置(不是必须的,配置只是为了命令行可以直接执行startup.bat等脚本)
环境变量->系统变量->新建
变量名CATALINA_HOME 变量值path\to\apache-tomcat-9.0.100
Path变量新增项:%CATALINA_HOME%\bin
Tomcat配置
conf/server.xml
<!-- 端口配置(默认为8080)、协议、超时时长、重定向端口、最大参数数量 -->
<Connector port="8099" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
<!-- 默认绑定主机名、应用所在目录、是否解包war、是否自动部署 -->
<!-- 此处的主机名需要与主机中定义的主机名一致,例如windows下 C:\Windows\System32\drivers\etc\hosts 文件中设置的主机名 -->
<!-- 修改如下 -->
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
127.0.0.1 ssydx
<!-- 此时该处的name也应设置为ssydx -->
<Host name="ssydx" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- 最终本地访问为ssydx:8099 -->
webapps/ROOT/index.jsp
<!-- 未添加应用是默认访问ROOT应用、index.jsp就是其主页 -->
发布应用
复制一份ROOT修改名称,删除除WEB-INF之外的内容
/webapps/webappname/WEB-INF/web.xml
<!-- 里面可进行应用的设置 -->
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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"
metadata-complete="true">
</web-app>
新建/webapps/webappname/index.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SSYDX</title>
</head>
<body>
<h1>这是首页</h1>
<p>Hello world</p>
<a href="./other.html">去另一页</a>
</body>
</html>
新建/webapps/webappname/other.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SSYDX</title>
</head>
<body>
<h1>这是另一页</h1>
<p>Hello ssydx</p>
<a href="./index.html">去首页</a>
</body>
</html>
启动Tomcat
双击执行/bin/startup.bat或/bin/startup.sh
在浏览器输入localhost:8099会访问ROOT应用
在浏览器输入localhost:8099/webappname会访问webappname应用
默认显示的是index页面
其他文件夹
examples | 一些应用样例 |
host-manager | 主机管理,提供可视化界面进行设置,需要设置用户信息,在/conf/tomcat-users.xml文件中 |
manager | 应用管理,提供可视化界面进行设置,需要设置用户信息,在/conf/tomcat-users.xml文件中 |
docs | tomcat的帮助文档 |
Maven
项目管理工具
下载
安装
解压,将文件夹移动到自己想要的路径
环境变量配置
环境变量->系统变量->新建
变量名MAVEN_HOME 变量值path\to\apache-maven-3.9.9
变量名(该环境变量是为了兼容旧版MAVEN) M2_HOME 变量值path\to\apache-maven-3.9.9\bin
Path变量新增项:%MAVEN_HOME%\bin
配置
path\to\apache-maven-3.9.9\conf\settings.xml
镜像仓库
阿里云官方指南: 仓库服务
<!-- 配置阿里云镜像仓库,推荐选项2 -->
<!-- 选项1,只映射maven中央仓库 -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<!-- 选项2,除maven中央仓库外还包含其他仓库的聚合仓库,排除了jeecg、jeecg-snapshots这两个特定版本 -->
<!-- jeecg是企业级快速开发仓库、jeecg-snapshots则是对应的快照版本,根据具体情况可排除自己不需要的仓库 -->
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
<name>Nexus aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
本地仓库
在path\to\apache-maven-3.9.9\创建一个文件夹,maven-repo
<!-- 配置本地仓库,指向刚创建的文件夹 -->
<localRepository>D:\Program\Environment\apache-maven-3.9.9\maven-repo</localRepository>
IDEA
下载
安装
自选安装路径,其余默认即可
配置
设置->构建、执行、部署->构建工具->Maven
含义 |
Maven的安装路径 | Maven的配置文件 | 本地仓库的位置 | 使用项目中进行的Maven设置 |
内容 |
自填 | 自填 | 自填 | 取消勾选 |
示例 |
path/to/apache-maven-3.9.9 | path/to/apache-maven-3.9.9\conf.xml | path/to/apache-maven-3.9.9\maven-repo | 取消勾选 |
注意要在打开具体项目之前配置,否则不生效(有时候会因项目失效,注意检查)
避免MAVEN资源导出失败
有时候非java文件也可能在java目录下,例如mybatis的xml文件,此时构建打包后,这类文件可能不被导出,pom添加以下配置可解决
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
前期准备
JDK1.8安装
Tomcat9安装
IDEA2025Ultra安装
Maven配置
项目创建
Idea->新建->项目->Java
含义 |
项目名称,即工件ID | 保存路径 | 构建项目的方式 | JDK版本 | 项目所属组织 |
内容 |
自填 | 自选 | Maven | 1.8 | 自填 |
示例 |
JavaWeb | D:\Code\Java | Maven | jdk1.8.0_202 | xyz.ssydx |
删除src目录
Servlet
Servlet是Sun公司开发的一种动态Web技术
Servlet是一系列API中的一个,实现该接口并部署到Web服务器即可
实现了Servlet接口的程序叫Servlet应用
模块创建
Idea->新建->模块->MavenArchetype
含义 |
项目名称,即工件ID | 保存路径 | JDK版本 | 模块所属项目 | 模板位置 | 模板 |
内容 |
自填 | 自选 | 1.8 | JavaWeb | 内部 | maven-archetype-webapp |
示例 |
servlet | D:\Code\Java | jdk1.8.0_202 | 之前创建的项目 | 内部 | maven-archetype-webapp |
载入依赖
serlet模块下的pom.xml文件(载入后记得右键->Maven->同步项目),后续操作均在模块内
<!-- servlet 依赖库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<!--
tomcat 等 servlet 容器已经内置了该依赖库
因此设置 provided, 声明其由容器提供
-->
<scope>provided</scope>
</dependency>
新建Java目录
JavaWeb/servlet/src/main->右键->新建目录java
如果其不是java源代码根目录则右键该目录->将目录标记为->源代码根目录
运行调试配置->编辑配置->Tomcat 服务器 -> 本地
含义 |
配置名称 | Tomcat服务器 | 设置控制台输出编码格式,避免乱码 | 启动Tomcat时要部署的模块 |
内容 |
自填 | 配置->新建->配置可百度 | -Dfile.encoding=UTF-8 | servlet |
示例 |
servlet | Tomcat 9.0.100 | -Dfile.encoding=UTF-8 | 之前创建的模块 |
非Ultra版限制较多(建议试用Ultra,可试用1个月),不支持Tomcat配置,需安装SmartTomcat插件
入门
demo01
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Demo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// req.getRequestURL().toString() 用于获取请求的URL路径的字符串
System.out.println("demo01: " + "这是第一个 Servlet, 证明你正式开始学习! " + "当前访问路径为: " + req.getRequestURL().toString());
}
}
web.xml
<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"
metadata-complete="true">
<!-- demo01 -->
<!-- Servlet 注册 -->
<servlet>
<servlet-name>demo01</servlet-name>
<servlet-class>xyz.ssydx.servlet.Demo01</servlet-class>
</servlet>
<!--
Servlet 映射
映射是自上而下匹配的,一旦找到匹配项就不再继续进行下去
-->
<!-- 将 Servlet 和单个路径映射 -->
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>/demo01</url-pattern>
</servlet-mapping>
<!-- 将 Servlet 和多个路径映射 -->
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>/demo01_1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>/demo01_2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>/demo01_3</url-pattern>
</servlet-mapping>
<!-- 将 Servlet 和通用路径映射 -->
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>/demo01/*</url-pattern>
</servlet-mapping>
<!--
将 Servlet 和指定后缀路径映射
不能*suffix
不能 /*.suffix这种形式
也不能/your_path/*.suffix这种形式
-->
<servlet-mapping>
<servlet-name>demo01</servlet-name>
<url-pattern>*.demo01</url-pattern>
</servlet-mapping>
<!--
将 Servlet 和所有路径映射, 基本不会使用,会覆盖所有路径
初期可以用来把所有未映射路径统一处理映射到指定的 Servlet 上, 避免404错误
注意: 如果采用此策略必须确保该映射位于末尾
-->
<!-- <servlet-mapping>-->
<!-- <servlet-name>demo01</servlet-name>-->
<!-- <url-pattern>/*</url-pattern>-->
<!-- </servlet-mapping>-->
</web-app>
访问验证
# 启动应用
# 在浏览器中依次访问:
http://localhost:your_port/app_name/demo01
http://localhost:your_port/app_name/demo01_1
http://localhost:your_port/app_name/demo01/01
http://localhost:your_port/app_name/01.demo01
# IDE终端依次输出
demo01: 这是第一个 Servlet, 证明你正式开始学习! 当前访问路径为: http://localhost:your_port/app_name/demo01
demo01: 这是第一个 Servlet, 证明你正式开始学习! 当前访问路径为: http://localhost:your_port/app_name/demo01_1
demo01: 这是第一个 Servlet, 证明你正式开始学习! 当前访问路径为: http://localhost:your_port/app_name/demo01/01
demo01: 这是第一个 Servlet, 证明你正式开始学习! 当前访问路径为: http://localhost:your_port/app_name/01.demo01
映射总结
精确路径 | /demo01 |
/demo01 |
只要能匹配就立即使用 |
多路径绑定 | /demo01_1 |
/demo01_1 |
实际仍是精确匹配 |
通配符前缀 | /demo01/* |
/demo01/01 , /demo01/abc |
路径前缀匹配 |
后缀匹配 | *.demo01 |
01.demo01 , hello.world.demo01 |
后缀匹配 |
任意路径 | /* |
所有路径 | 最宽泛的匹配,不推荐使用 |
ServletContext
初始化参数
demo02
package xyz.ssydx.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Demo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 ServletContext 对象, 这是当前 Web 应用的上下文对象
ServletContext context = this.getServletContext();
// 也可以通过代码设置应用的初始化参数, 但通常不会生效, Servlet 容器会阻止该操作
// context.setInitParameter("datasource_url", "jdbc:mysql:/localhost:3306/java_web");
// 获取指定的初始化参数
String datasourceUrl = context.getInitParameter("datasource_url");
System.out.println("demo02: " + "datasource_url: " + datasourceUrl);
// 获取 ServletConfig 对象, 这是当前 Servlet 的配置对象
ServletConfig config = this.getServletConfig();
// 不支持代码设置
// 获取指定的初始化参数, 此处 redisUrl 为 null, 因为当前 Servlet 并未定义这个初始化参数
String redisUrl = config.getInitParameter("redis_url");
System.out.println("demo02: " + "redis_url: " + redisUrl);
}
}
demo03
package xyz.ssydx.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Demo03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取 ServletContext 对象, 这是当前 Web 应用的上下文对象
ServletContext context = this.getServletContext();
// 也可以通过代码设置应用的初始化参数, 但通常不会生效, Servlet 容器会阻止该操作
// context.setInitParameter("datasource_url", "jdbc:mysql:/localhost:3306/java_web");
// 获取指定的初始化参数
String datasourceUrl = context.getInitParameter("datasource_url");
System.out.println("demo03: " + "datasource_url: " + datasourceUrl);
// 获取 ServletConfig 对象, 这是当前 Servlet 的配置对象
ServletConfig config = this.getServletConfig();
// 不支持代码设置
// 获取指定的初始化参数, 此处 redisUrl 为 null, 因为当前 Servlet 并未定义这个初始化参数
String redisUrl = config.getInitParameter("redis_url");
System.out.println("demo03: " + "redis_url: " + redisUrl);
}
}
web.xml
后续若无特殊情况,web.xml中的注册及映射不再单独列出
<!-- demo02 demo03 -->
<!-- 设置当前 Web 应用的初始化参数, 应用内任何 Servlet 均可通过 ServletContext 进行访问 -->
<context-param>
<param-name>datasource_url</param-name>
<param-value>jdbc:mysql:/localhost:3306/java_web</param-value>
</context-param>
<!-- demo02 -->
<servlet>
<servlet-name>demo02</servlet-name>
<servlet-class>xyz.ssydx.servlet.Demo02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo02</servlet-name>
<url-pattern>/demo02</url-pattern>
</servlet-mapping>
<!-- demo03 -->
<servlet>
<servlet-name>demo03</servlet-name>
<servlet-class>xyz.ssydx.servlet.Demo03</servlet-class>
<!-- 设置 demo03 这个 Servlet 的初始化参数, 只有 demo03 可通过 ServletConfig 进行访问 -->
<init-param>
<param-name>redis_url</param-name>
<param-value>localhost:6379</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>demo03</servlet-name>
<url-pattern>/demo03</url-pattern>
</servlet-mapping>
访问验证
# 启动应用
# 在浏览器中依次访问:
http://localhost:your_port/app_name/demo02
http://localhost:your_port/app_name/demo03
# IDE终端依次输出
demo02: datasource_url: jdbc:mysql:/localhost:3306/java_web
demo02: redis_url: null
demo03: datasource_url: jdbc:mysql:/localhost:3306/java_web
demo03: redis_url: localhost:6379
初始化参数总结
应用级初始化参数 | <context-param> |
ServletContext |
getInitParameter() |
整个 Web 应用 | 是 |
Servlet 级初始化参数 | <init-param> |
ServletConfig |
getInitParameter() |
当前 Servlet | 否 |
请求转发
demo04
package xyz.ssydx.servlet;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Demo04 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 获取请求转发对象, 参数为要转发到的路径
RequestDispatcher dispatcher = context.getRequestDispatcher("/demo01");
// 进行转发
dispatcher.forward(req, resp);
// 拓展知识
ServletConfig config = this.getServletConfig();
String servletName = config.getServletName();
System.out.println("demo04: " + "servletName: " + servletName);
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo04
# IDE终端输出
demo01: 这是第一个 Servlet, 证明你正式开始学习! 当前访问路径为: http://localhost:8099/servlet_war/demo01
demo04: servletName: demo04
重定向见后文
资源读取
demo05
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class Demo05 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 测试1
try (
// 获取资源的字节输入流, 资源虽然被放到src/resources目录下, 但构建后会放到/WEB-INF/classes下
InputStream is = context.getResourceAsStream("/WEB-INF/classes/test.txt");
// 以 UTF-8 格式转为字符输入流
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
// 创建缓冲区字符输入流以提供读取效率(此处也可不考虑, 毕竟内容很少)
BufferedReader br = new BufferedReader(isr)
) {
// 按行读取
String line;
while ((line = br.readLine()) != null) {
System.out.println("demo05: " + line);
}
}
// 测试2
try (InputStream is = context.getResourceAsStream("/WEB-INF/classes/datasource.properties")) {
// 创建属性对象
Properties properties = new Properties();
// 从指定输入流中载入属性
properties.load(is);
// 获取属性, 第二个参数可指定属性不存在的默认值
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String url = properties.getProperty("url", "jdbc:mysql://localhost:3306/java_web");
System.out.println("demo05: " + "username: " + username);
System.out.println("demo05: " + "password: " + password);
System.out.println("demo05: " + "url: " + url);
}
}
}
test.txt
放到src/resources目录
hello 😀
该文档仅用于资源载入的测试!
datasource.properties
放到src/resources目录
username=ssydx
password=123456
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo05
# IDE终端输出
demo05: hello 😀
demo05: 该文档仅用于资源载入的测试!
demo05: username: ssydx
demo05: password: 123456
demo05: url: jdbc:mysql://localhost:3306/java_web
数据共享
demo06
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class Demo06 extends HttpServlet {
// 内部类
public class People {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + "]";
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 设置属性
context.setAttribute("msg", "Hello World");
List<String> list = new ArrayList<String>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
context.setAttribute("list", list);
context.setAttribute("people", new People("ssydx", 18));
System.out.println("demo06: " + "属性设置成功");
}
}
demo07
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
public class Demo07 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 获取属性
String msg = (String)context.getAttribute("msg");
System.out.println("demo07: " + msg);
List<String> list = (List<String>)context.getAttribute("list");
System.out.println("demo07: " + list);
Demo06.People people = (Demo06.People)context.getAttribute("people");
System.out.println("demo07: " + people);
}
}
访问验证
# 启动应用
# 在浏览器中依次访问:
http://localhost:your_port/app_name/demo06
http://localhost:your_port/app_name/demo07
# IDE终端依次输出
demo06: 属性设置成功
demo07: Hello World
demo07: [zhangsan, lisi, wangwu]
demo07: People [name=ssydx, age=18]
HttpServletResponse
编解码设置
demo08
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class Demo08 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 仅指定响应的输出流的编码方式是不够的, 虽然编码正确但客户端(浏览器)不知道如何解码, 也不知道如何显示
// resp.setCharacterEncoding("UTF-8");
/*
仅指定响应的内容类型(显示为 plain 文档, 按 UTF-8 进行解码)是可行的
虽然未指定响应输出流的编码方式, 但 Servlet 容器(Tomcat)会隐式根据响应内容类型中的解码进行响应输出流的编码
*/
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().write("你好 🌏");
System.out.println("demo08: " + "编解码设置成功");
}
}
访问验证
后续若无特殊情况,访问验证还需关注浏览器渲染结果(注意观察标头),不再单独强调
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo08
# 浏览器页面显示
你好 🌏
# IDE终端输出
demo08: 编解码设置成功
重定向
demo09
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Demo09 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 进行重定向
resp.sendRedirect("demo08");
System.out.println("demo09: " + "重定向成功");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo09
# IDE终端输出
demo09: 重定向成功
demo08: 编解码设置成功
请求转发见前文
文件下载1
demo10
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Demo10 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 设置响应头,强制浏览器下载该文件
resp.setHeader("Content-Disposition", "attachment; filename=test.txt");
// 获取资源的输入流和响应的输出流
try (
InputStream is = context.getResourceAsStream("/WEB-INF/classes/test.txt");
OutputStream os = resp.getOutputStream()
) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
System.out.println("demo10: " + "文件下载成功");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo10
# IDE终端输出
demo10: 文件下载成功
test.txt文件见前文
文件下载2
demo11
package xyz.ssydx.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
public class Demo11 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
// 设置响应头,强制浏览器下载该文件
resp.setHeader(
"Content-Disposition",
// 中文字符无法直接出现在响应头中, 必须进行编码处理
"attachment; filename=" + URLEncoder.encode("测试.png", "UTF-8")
);
// 获取资源的输入流和响应的输出流
try (
InputStream is = context.getResourceAsStream("/WEB-INF/classes/测试.png");
OutputStream os = resp.getOutputStream()
) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
System.out.println("demo11: " + "文件下载成功");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo11
# IDE终端输出
demo11: 文件下载成功
测试.png自行准备并放到src/resouces目录下
验证码
demo12
package xyz.ssydx.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Random;
public class Demo12 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/jpeg");
// 设置刷新间隔, 每30秒自动刷新一次
resp.setHeader("refresh", "30");
// 不缓存响应结果, HTTP/1.1
resp.setHeader("Cache-Control", "no-cache");
// 不缓存响应结果, HTTP/1.0
// resp.setDateHeader("expires", -1);
// resp.setHeader("Pragma", "no-cache");
// 生成验证码图片
BufferedImage bufferedImage = new BufferedImage(80,20, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D)bufferedImage.getGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0,0,80,20);
g2d.setColor(Color.BLACK);
g2d.setFont(new Font(null, Font.BOLD, 20));
String captcha = getRand();
g2d.drawString(captcha, 5,20);
// 写入响应的输出流
ImageIO.write(bufferedImage, "jpg", resp.getOutputStream());
System.out.println("demo12: " + "验证码内容: " + captcha + " 获取时间为: " + new Date(System.currentTimeMillis()));
}
/**
* 本方法用于生成随机的6位数字字符串
* @return 返回值为6位的数字字符串
*/
private String getRand() {
Random random = new Random();
String str = random.nextInt(999999) + "";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 6 - str.length(); i++) {
stringBuilder.append("0");
}
str += stringBuilder.toString();
return str;
}
}
访问验证
# 启动应用
# 在浏览器中访问并等待30秒以上:
http://localhost:your_port/app_name/demo12
# 浏览器页面显示
验证码图片, 每隔30秒自动刷新一次
# IDE终端输出(仅作示例)
demo12: 验证码内容: 998899 获取时间为: Sun May 25 13:35:02 CST 2025
demo12: 验证码内容: 863927 获取时间为: Sun May 25 13:35:32 CST 2025
HttpServletRequest
获取参数
demo13
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
public class Demo13 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
req.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbies");
System.out.println("demo13: " + "username=" + username + ", password=" + password + ", hobbies=" + Arrays.toString(hobbies));
// 写入响应的输入流
resp.setContentType("text/plain;charset=utf-8");
resp.getWriter().write("username=" + username + ", password=" + password + ", hobbies=" + Arrays.toString(hobbies) + "\n");
// 拓展(请求包含)
this.getServletContext().getRequestDispatcher("/demo08").include(req, resp);
}
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="demo13" method="get">
用户名:<input type="text" name="username" id="username"> <br>
密码:<input type="password" name="password" id="password"> <br>
爱好:
<input type="checkbox" name="hobbies" value="小说">小说
<input type="checkbox" name="hobbies" value="电影">电影
<input type="checkbox" name="hobbies" value="manga">漫画
<input type="checkbox" name="hobbies" value="music">音乐
<br>
<button type="submit">提交</button>
</form>
</body>
</html>
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/login.html
# 填写表单并提交
# 浏览器跳转到 http://localhost:your_port/app_name/demo13?username=ssydx&password=123456&hobbies=电影&hobbies=manga
# 浏览器页面显示:
username=ssydx, password=123456, hobbies=[电影, manga]
你好 🌏
# IDE终端输出(仅作示例)
demo13: username=ssydx, password=123456, hobbies=[电影, manga]
demo08: 编解码设置成功
demo08 见前文
获取Cookie
demo14
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Demo14 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/plain;charset=utf-8");
PrintWriter writer = resp.getWriter();
// 获取 cookie
Cookie[] cookies = req.getCookies();
Long lastTime = null;
// 遍历是否有指定 cookie
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("lastTime")) {
lastTime = Long.parseLong(cookie.getValue());
break;
}
}
}
// 无则认为第一次访问当前页面, 否则返回 cookie 值
if (lastTime == null) {
System.out.println("demo14: " + "尚未设置 lastTime");
writer.write("首次访问");
} else {
System.out.println("demo14: " + "上次设置 lastTime: " + lastTime);
writer.write("lastTime=" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(lastTime)));
}
// 设置 cookie
Cookie newLastTime = new Cookie("lastTime", System.currentTimeMillis() + "");
// 设置 30 秒后自动过期
newLastTime.setMaxAge(30);
resp.addCookie(newLastTime);
System.out.println("demo14: " + "设置 cookie 成功");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo14
# 浏览器页面显示:
username=ssydx, password=123456, hobbies=[电影, manga]
首次访问
# 立刻刷新页面:
lastTime=2025-05-26 05:28:51
# 等待30秒以上再次刷新页面:
首次访问
# IDE终端依次输出(仅作示例)
demo14: 尚未设置 lastTime
demo14: 设置 cookie 成功
demo14: 上次设置 lastTime: 1748208531322
demo14: 设置 cookie 成功
demo14: 尚未设置 lastTime
demo14: 设置 cookie 成功
获取Session
web.xml
<!-- demo15 demo16 -->
<!-- 设置 session 的过期时间为1分钟 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
<!-- demo15 -->
<servlet>
<servlet-name>demo15</servlet-name>
<servlet-class>xyz.ssydx.servlet.Demo15</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo15</servlet-name>
<url-pattern>/demo15</url-pattern>
</servlet-mapping>
<!-- demo16 -->
<servlet>
<servlet-name>demo16</servlet-name>
<servlet-class>xyz.ssydx.servlet.Demo16</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo16</servlet-name>
<url-pattern>/demo16</url-pattern>
</servlet-mapping>
demo15
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo15 extends HttpServlet {
// 内部类
public class People {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + "]";
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/plain;charset=utf-8");
// 获取 session, 策略是无则新建
HttpSession session = req.getSession();
// 在 session 中存入数据
session.setAttribute("name", "ssydx");
session.setAttribute("people", new People("zhangsan", 18));
String id = session.getId();
// 如果是新创建的 session, 输出新建, 否则输出已有
if (session.isNew()) {
System.out.println("demo15: " + "新建session: " + id);
} else {
System.out.println("demo15: " + "已有session: " + id);
}
}
}
demo16
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class Demo16 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/plain;charset=utf-8");
// 获取session并获得其中的值, 无也不新建
HttpSession session = req.getSession(false);
if (session != null) {
String name = (String)session.getAttribute("name");
Demo15.People people = (Demo15.People)session.getAttribute("people");
// 强制使 session 非法(过期)
session.invalidate();
System.out.println("demo16: " + "name=" + name + ", people=" + people);
} else {
System.out.println("demo16: session 不存在");
}
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo15
# 立刻刷新页面
# 随即访问:
http://localhost:your_port/app_name/demo15
# 立刻刷新页面
# IDE终端依次输出(仅作示例)
demo15: 新建session: 5FC33AA983AFB0C18E7B964EA4C2ADAD
demo15: 已有session: 5FC33AA983AFB0C18E7B964EA4C2ADAD
demo16: name=ssydx, people=People [name=zhangsan, age=18]
demo16: session 不存在
# 不妨尝试等待1分钟以上再进行访问, 不难发现 session 也会新建(demo15)或不存在(demo16)
如果启动应用后先访问了调用过 getSession() 的 Servlet(包括任意的jsp页面,例如index.jsp),此时访问demo15会发现session已经存在,因为getSession方法在无参或参数为true时会无则新建,而jsp页面均隐式调用了该方法
不难发现在浏览器的cookie列表中往往存在一个名为JSESSIONID的cookie,这就是维持会话的标识,它跟会话是同步新建(JSESSIONID本质是session的id)
WebServlet
后续若无特殊情况,Servlet 均采用注解形式进行注册映射
web.xml
<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"
metadata-complete="false">
<!-- metadata-complete="false" 开启注解扫描 -->
</web-app>
demo17
package xyz.ssydx.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
// 注册并映射, 配置方式类同web.xml
@WebServlet(name = "demo17", urlPatterns = {"/demo17"})
public class Demo17 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig config = this.getServletConfig();
String servletName = config.getServletName();
System.out.println("demo17: " + "servletName: " + servletName);
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo17
# IDE终端输出
demo17: servletName: demo17
Filter
过滤器常用于对 Servlet 进行统一处理,例如:编码设置、权限拦截
web.xml
<!-- filter01 -->
<filter>
<filter-name>filter01</filter-name>
<filter-class>xyz.ssydx.filter.Filter01</filter-class>
</filter>
<filter-mapping>
<filter-name>filter01</filter-name>
<url-pattern>/demo18</url-pattern>
</filter-mapping>
filter01
package xyz.ssydx.filter;
import javax.servlet.*;
import java.io.IOException;
public class Filter01 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 应用启动时
System.out.println("Filter01 init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 设置请求的编码格式和响应的内容类型
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/plain;charset=utf-8");
System.out.println("Filter01 doFilter begin");
// 继续执行其他过滤器
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter01 doFilter finish");
}
@Override
public void destroy() {
// 应用关闭时
System.out.println("Filter01 destroy");
}
}
demo18
package xyz.ssydx.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 注册并映射, 配置方式类同web.xml
@WebServlet(name = "demo18", urlPatterns = {"/demo18", "/demo/demo18"})
public class Demo18 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("Hello World 😀");
System.out.println("demo18: " + "响应完成");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo18
# 浏览器页面显示:
Hello World 😀
# IDE终端输出
Filter01 doFilter begin
demo18: 响应完成
Filter01 doFilter finish
# 在浏览器中访问:
http://localhost:your_port/app_name/demo/demo18
# 浏览器页面显示:
Hello World ?
# IDE终端输出
demo18: 响应完成
单个过滤器按照filter-mapping映射顺序进行映射过滤
多个过滤器同样按照filter-mapping映射顺序进行过滤(不是过滤器注册顺序,而是过滤器映射顺序)
如果同时存在 web.xml注册映射的过滤器 和 注解配置的过滤器,前者总是优先于后者执行
WebFilter
同样支持注解配置
不支持顺序指定,即两个注解配置的过滤器的执行顺序是未知的,如需要按既定顺序执行过滤器则还是采用web.xml进行配置
filter02
package xyz.ssydx.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
// 注解配置, 名称为filter02, 按 Servlet 的名称过滤(也可以按路径)
@WebFilter(filterName = "filter02", servletNames = "demo19")
public class Filter02 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 应用启动时
System.out.println("Filter02 init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter02 doFilter begin");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter02 doFilter finish");
}
@Override
public void destroy() {
// 应用关闭时
System.out.println("Filter02 destroy");
}
}
demo19
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 注册并映射, 配置方式类同web.xml
@WebServlet(name = "demo19", urlPatterns = "/demo19")
public class Demo19 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("demo19: " + "响应完成");
}
}
访问验证
# 启动应用
# 在浏览器中访问:
http://localhost:your_port/app_name/demo19
# IDE终端输出
Filter02 doFilter begin
demo19: 响应完成
Filter02 doFilter finish
Listener
此处以HttpSessionListener为例
事件监听器的种类非常多,例如:ServletContextListener,ServletRequestListener,HttpSessionAttributeListener等
web.xml
<!-- listener01 -->
<!-- <listener>-->
<!-- <listener-class>xyz.ssydx.listener.Listener01</listener-class>-->
<!-- </listener>-->
listener01
package xyz.ssydx.listener;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
// 也可在web.xml中进行注册
@WebListener
public class Listener01 implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("sessionCreated");
ServletContext context = se.getSession().getServletContext();
Integer loginCount = (Integer)context.getAttribute("loginCount");
if (loginCount == null) {
loginCount = 1;
} else {
loginCount++;
}
context.setAttribute("loginCount", loginCount);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("sessionDestroyed");
ServletContext context = se.getSession().getServletContext();
Integer loginCount = (Integer)context.getAttribute("loginCount");
if (loginCount == null) {
loginCount = 0;
} else {
loginCount--;
}
context.setAttribute("loginCount", loginCount);
}
}
demo20
package xyz.ssydx.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "demo20", urlPatterns = "/demo20")
public class Demo20 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 触发一次 session 创建
req.getSession();
Integer loginCount = (Integer)this.getServletContext().getAttribute("loginCount");
resp.getWriter().println(loginCount);
System.out.println("demo20: " + "loginCount = " + loginCount);
}
}
访问验证
# 启动应用
# 在浏览器中访问并等待1分钟以上:
http://localhost:your_port/app_name/demo20
# IDE终端输出
sessionCreated
demo20: loginCount = 1
sessionDestroyed
访问demo20会触发session监听器的创建事件,输出结果是1,但由于session有效期在web.xml设置为1分钟(见前文),因此1分钟后会自动触发session监听器的销毁事件
如果想使结果为2,请新建无痕模式窗口(或使用另一浏览器)访问demo20
web.xml其他配置
错误处理
针对所有页面
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
JSP
Java Server Pages:java服务端页面,同 Servlet 一样用于动态Web技术
写Jsp就像写HTML,可以嵌入Java代码以提供动态服务
Jsp本质依然是Servlet,Tomcat等Servlet容器会在这些jsp文件首次被访问时自动转换为java文件(借助Jasper)并编译为class文件
访问 jsp 页面时注意添加 .jsp 后缀名
模块创建
新建模块
参见前文
载入依赖
<!-- servlet 依赖库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- jsp 依赖库 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- jstl 标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
新建Java目录
参见前文
Tomcat配置
参见前文
Jsp语法及源码解析
demo01.jsp
<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>JSP</h1>
<!-- 这是 HTML 注释,会出现在 HTML 页面 -->
<%-- 这是 JSP 注释,和 HTML 注释不同,不会出现在 HTML 页面中 --%>
<%-- jsp 表达式 --%>
<%= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) %>
<%-- 等价于 --%>
<%-- <% --%>
<%-- String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()); --%>
<%-- out.write(date); --%>
<%-- %> --%>
<%-- jsp 脚本片段1 --%>
<%
int sum = 0;
for (int i = 1; i <= 5; i++) {
out.print("<p>" + "当前循环数值为: " + i + "</p>");
sum += i;
}
out.print("<p>" + "求和结果为: " + sum + "</p>");
%>
<%-- jsp 脚本片段2, 嵌套 HTML --%>
<% int n = 6; %>
<% if (n > 5) { %>
<p>数字大于5</p>
<% } else { %>
<p>数字小于等于5</p>
<% } %>
<%-- jsp 声明, 在 jsp 编译后会作为 Servlet 类的静态内部块、成员属性、成员方法 --%>
<%!
static {
System.out.println("Loading Servlet!");
}
private int globalvar = 10;
public void meth1() {
System.out.println("这是自定义方法");
}
%>
<%-- 直接使用 --%>
<%= globalvar %>
<%-- 直接使用 --%>
<%
meth1();
%>
</body>
</html>
demo01_jsp.java
该文件取自idea_cache\system\tomcat\randomdirname\work\Catalina\localhost\projectname_war\org\apache\jsp目录
idea_cache是Idea缓存所在的路径,视idea版本和个人配置而定
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Date;
import java.text.SimpleDateFormat;
// HttpJspBase 继承自 HttpServlet
public final class demo01_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
// jsp 声明 编译后在这里
static {
System.out.println("Loading Servlet!");
}
private int globalvar = 10;
public void meth1() {
System.out.println("这是自定义方法");
}
// 省略
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
// 省略
try {
// jsp 编译后会默认设置响应的内容类型为该值
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
// 页面上下文对象, jsp 特有对象
_jspx_page_context = pageContext;
// 本质是容器级上下文对象
application = pageContext.getServletContext();
// 本质是Servlet级配置对象
config = pageContext.getServletConfig();
// 这里调用了 getSession(), 意味着当访问该页面时如果不存在 session 会自动创建
session = pageContext.getSession();
// 本质是 getWriter() 获得的 PrintWriter 对象
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write(" <title>Title</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write(" <h1>JSP</h1>\r\n");
// html 注释 编译后在这里
out.write(" <!-- 这是 HTML 注释,会出现在 HTML 页面 -->\r\n");
// jsp 注释 编译后被去除
out.write(" ");
out.write("\r\n");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
// jsp 表达式 编译后在这里
out.print( new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) );
out.write("\r\n");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
// jsp 脚本片段1 编译后在这里
int sum = 0;
for (int i = 1; i <= 5; i++) {
out.print("<p>" + "当前循环数值为: " + i + "</p>");
sum += i;
}
out.print("<p>" + "求和结果为: " + sum + "</p>");
out.write("\r\n");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
// jsp 脚本片段2, 嵌套 HTML 编译后在这里
int n = 6;
out.write("\r\n");
out.write(" ");
if (n > 5) {
out.write("\r\n");
out.write(" <p>数字大于5</p>\r\n");
out.write(" ");
} else {
out.write("\r\n");
out.write(" <p>数字小于等于5</p>\r\n");
out.write(" ");
}
out.write("\r\n");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
// 通过 jsp 表达式 使用 jsp 声明 的变量
out.print( globalvar );
out.write("\r\n");
out.write(" ");
out.write("\r\n");
out.write(" ");
// 通过 jsp 代码片段 使用 jsp 声明 的方法
meth1();
out.write("\r\n");
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>\r\n");
} catch (java.lang.Throwable t) {
// 省略
} finally {
// 省略
}
}
}
Jsp指令
概览
page | <%@ page ... %> |
控制整个 JSP 页面的行为,比如编码方式、内容类型、是否启用 session 等。 |
include | <%@ include file="..." %> |
在 JSP 编译阶段静态包含另一个文件的内容(如 HTML、JSP 片段)。 |
taglib | <%@ taglib uri="..." prefix="..." %> |
声明并引入一个自定义标签库或标准标签库(如 JSTL)。 |
<%@ page ...args %>
args
language |
java |
指定 JSP 页面使用的脚本语言(目前只支持 Java) |
extends |
- | 指定生成的 Servlet 所继承的父类(一般不建议修改) |
import |
- | 导入 Java 包或类,多个用逗号分隔,如:import="java.util.*, java.sql.* |
session |
true |
是否允许使用 session 对象,设为 false 表示禁用 Session |
isThreadSafe |
true |
控制生成的 Servlet 是否实现 SingleThreadModel 接口,false 时容器会确保线程安全处理(已过时) |
errorPage |
- | 指定当前页面出错后跳转的错误处理页面,例如:errorPage="error.jsp" |
isErrorPage |
false |
表示该页面是否是一个错误处理页面(可访问 exception 内置对象) |
contentType |
text/html |
设置响应的 MIME 类型和字符编码,常用:contentType="text/html;charset=UTF-8" |
pageEncoding |
服务器默认编码(通常是 ISO-8859-1) | 设置当前 JSP 文件的字符编码(推荐设置为 UTF-8) |
isELIgnored |
false |
是否忽略 EL 表达式(${...} ,设为 true 后 EL 不会被解析 |
buffer |
8kb |
设置 JSP 输出流(out )的缓存大小,如:buffer="16kb" |
autoFlush |
true |
缓冲区满时是否自动刷新输出,若为 false ,缓冲区满时抛出异常 |
trimDirectiveWhitespaces |
false |
是否移除 JSP 标签之间的空白字符(优化 HTML 输出) |
demo02.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 指定当前页面出错后显示的错误页面 --%>
<%@ page errorPage="error/500.jsp" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%-- 例如出现了0除错误 --%>
<%
int x = 1 / 0;
%>
</body>
</html>
500.jsp
在webapp/error目录下创建
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>自定义的错误页面500</h1>
</body>
</html>
<%@ include file="path/to/your_jsp.jsp" %>
静态包含,不推荐使用
demo03.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%@ include file="commons/header.jsp" %>
<%
int x = 20;
%>
<h1>DEMO03</h1>
<%-- 载入公共的脚本HTML片段 --%>
<%-- 编译为Servlet时会直接把该片段编译进当前页面 --%>
<%-- 由于当前页面和载入的 footer 片段页面均定义了x,会产生错误(变量重复声明定义) --%>
<%--<%@ include file="commons/footer.jsp"%>--%>
<%-- 解决办法是使用jsp标签(推荐用法),这样不会直接编译进当前页面,而是作为外部引用 --%>
<jsp:include page="commons/footer.jsp" />
</body>
</html>
header.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>这是HTML头部片段</h1>
footer.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>这是脚部HTML片段</h1>
<%
int x = 10;
%>
<%@ taglib ...args %>
用于引用外部标签库,例如:jstl标准标签库,见后文
内置对象
九大对象
pageContext |
javax.servlet.jsp.PageContext |
提供对页面所有其他内置对象的访问,支持属性管理(page/request/session/application)和包含/转发操作 |
request |
javax.servlet.http.HttpServletRequest |
表示客户端的 HTTP 请求,用于获取请求参数、请求头、设置/获取属性等 |
response |
javax.servlet.http.HttpServletResponse |
用于向客户端发送响应数据,如设置状态码、响应头、重定向等 |
session |
javax.servlet.http.HttpSession |
用于在多个请求之间保存用户信息,比如登录状态、用户偏好等 |
application |
javax.servlet.ServletContext |
表示整个 Web 应用上下文,用于共享全局数据、获取配置参数等 |
config |
javax.servlet.ServletConfig |
表示当前 JSP 页面的配置信息,用于获取初始化参数 |
out |
javax.servlet.jsp.JspWriter |
用于向浏览器输出内容(相当于 PrintWriter ),常用于 <%= %> 输出 |
page |
java.lang.Object (实际是 this ) |
表示当前 JSP 页面本身(即生成的 Servlet 实例),类似 Java 中的 this |
exception |
java.lang.Throwable |
表示运行时异常或错误,仅在设置了 isErrorPage="true" 的页面中可用 |
四大对象作用域
pageContext |
页面作用域 | 当前页面创建到处理完成 | 仅当前页面有效 | 存储页面级别的临时数据,如页面内共享变量 | 数据只在当前页面有效,离开即失效 |
request |
请求作用域 | 请求开始到响应结束 | 同一个请求链(包括转发) | 在多个 Servlet/JSP 之间传递数据 | 支持请求转发时共享,重定向后数据丢失 |
session |
会话作用域 | 用户首次访问到会话超时或销毁 | 整个用户会话期间 | 存储用户登录状态、购物车等需要跨请求保持的数据 | 可跨多个页面和请求,适合跟踪用户行为 |
application |
应用作用域 | Web应用启动到停止或重新部署 | 整个Web应用的所有用户 | 存放全局配置、公共资源(如数据库连接池、全局计数器等) | 所有用户共享,修改需谨慎,避免并发问题 |
demo04
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 放在html标签开始之前,请求转发 --%>
<%--<% pageContext.forward("index.jsp"); %>--%>
<%-- 等价于 --%>
<%--<% request.getRequestDispatcher("hello.jsp").forward(request, response); %>--%>
<html>
<head>
<title>Title</title>
</head>
<body>
<%-- 动态包含 --%>
<%--<% pageContext.include("commons/header.jsp"); %>--%>
<%
pageContext.setAttribute("name1", "ssydxp_1");
request.setAttribute("name1", "ssydxr_1");
request.setAttribute("name2", "ssydxr_2");
session.setAttribute("name1", "ssydxs_1");
session.setAttribute("name2", "ssydxs_2");
session.setAttribute("name3", "ssydxs_3");
application.setAttribute("name1", "ssydxa_1");
application.setAttribute("name2", "ssydxa_2");
application.setAttribute("name3", "ssydxa_3");
application.setAttribute("name4", "ssydxa_4");
%>
<%-- 按照 pageContext > request > session > application 顺序依次查找指定属性名,如果最终不存在则返回 null --%>
<%-- 此过程就类似类加载的双亲委派机制 --%>
<h1><%= pageContext.findAttribute("name1")%></h1>
<h1><%= pageContext.findAttribute("name2")%></h1>
<h1><%= pageContext.findAttribute("name3")%></h1>
<h1><%= pageContext.findAttribute("name4")%></h1>
<h1><%= pageContext.findAttribute("name5")%></h1>
<%-- 同上, 但查找不到返回空(底层就是调用了 pageContext.findAttribute, 但对 null 进行了处理)--%>
<%-- 即 ${} (EL表达式) 取的是四大对象内的属性而不是变量 --%>
<h1>${name1}</h1>
<h1>${name2}</h1>
<h1>${name3}</h1>
<h1>${name4}</h1>
<h1>${name5}</h1>
</body>
</html>
特性 ${name}
<%= pageContext.findAttribute("name") %>
查找机制 完全相同 完全相同 返回 null 时的行为 不输出任何内容(视觉上“消失”) 输出字符串 "null"
HTML 转义 自动进行 不自动进行 推荐使用场景 JSP 页面展示数据 需要 Java 控制逻辑时 可读性 更好 较差 是否适合 MVC 推荐 不推荐用于视图展示
拓展
双亲委派机制:一个类加载器收到加载请求时优先委托给其父类加载器(此过程递归向上到顶级加载器),如果父类加载器无法完成加载请求(即其搜索范围内没有所需的类)则通知子加载器(递归向下到发出委托的子加载器为止)由子加载器进行加载
此机制有效避免了同名类导致的问题,即自己定义的类覆盖系统类
jsp标签
标签一览表
<jsp:include> |
包含另一个页面的内容到当前页面 | 动态包含其他 JSP 页面或资源,适用于需要在运行时合并多个页面的情况 |
<jsp:forward> |
将请求转发到另一个页面 | 当前页面处理完后将请求转发给另一个页面,常用于页面导航 |
<jsp:param> |
作为子元素传递参数给 <jsp:include> 或 <jsp:forward> |
需要向被包含或转发的页面传递参数时使用 |
<jsp:useBean> |
在指定的作用域中查找或创建一个 JavaBean 实例 | 使用 JavaBean 组件管理数据状态 |
<jsp:setProperty> |
设置 JavaBean 的属性值 | 初始化或更新 JavaBean 的属性 |
<jsp:getProperty> |
获取 JavaBean 的属性值并输出 | 显示 JavaBean 中的数据 |
<jsp:text> |
输出纯文本内容 | 当你需要确保某些文本不被解释为HTML或XML时 |
<jsp:element> |
动态定义XML元素 | 在JSP页面中动态生成XML内容 |
<jsp:body> |
定义自定义标签体的内容 | 自定义标签开发时定义标签体 |
<jsp:attribute> |
定义自定义标签库中的标签的属性 | 自定义标签开发时定义标签属性 |
<jsp:plugin> |
生成客户端浏览器中使用的插件(如 Java Applet)所需的 HTML 标记 | 已很少使用,主要用于早期支持嵌入式Java应用程序 |
demo05
<%@ page import="xyz.ssydx.entity.People" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--<jsp:forward page="hello.jsp" />--%>
<%--<jsp:forward page="hello.jsp">--%>
<%-- <jsp:param name="name" value="value"/>--%>
<%--</jsp:forward>--%>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--<jsp:include page="commons/footer.jsp">--%>
<%-- <jsp:param name="name" value="value"/>--%>
<%--</jsp:include>--%>
<%--<%--%>
<%-- People people = new People();--%>
<%-- people.setName("zhangsan");--%>
<%-- people.setAge(24);--%>
<%-- pageContext.setAttribute("people", people);--%>
<%--%>--%>
<%--${people.name}--%>
<%--${people.age}--%>
<%-- 等价 --%>
<jsp:useBean id="people" class="xyz.ssydx.entity.People" scope="page" />
<jsp:setProperty name="people" property="name" value="zhangsan" />
<jsp:setProperty name="people" property="age" value="24" />
<jsp:getProperty name="people" property="name"/>
<jsp:getProperty name="people" property="age"/>
</body>
</html>
People.java
package xyz.ssydx.entity;
public class People {
private Integer id;
private String name;
private Integer age;
public People(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public People() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
EL表达式
隐式对象
pageScope | page 作用域 |
requestScope | request 作用域 |
sessionScope | session 作用域 |
applicationScope | application 作用域 |
param | Request 对象的参数,字符串 |
paramValues | Request对象的参数,字符串集合 |
header | HTTP 信息头,字符串 |
headerValues | HTTP 信息头,字符串集合 |
initParam | 上下文初始化参数 |
cookie | Cookie值 |
pageContext | 当前页面的pageContext |
demo06
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page import="xyz.ssydx.entity.People" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 引入标准函数标签库 --%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
request.setAttribute("list", list);
String[] strings = {"zs", "ls", "ww"};
request.setAttribute("strings", strings);
People people = new People(1, "ssydx", 35);
request.setAttribute("people", people);
session.setAttribute("name", "ssydx");
session.setAttribute("age", 25);
%>
<%-- EL表达式应该专注于获取数据而不是执行写入操作(很多涉及写入的操作(put方法, set方法等)都是非法的) --%>
<h1>支持大多数的Java运算符</h1>
<p>计算3+5:${3+5}</p>
<p>判断3!=5:${3 != 5}</p>
<p>判断列表是否为空:${empty list}</p>
<p>获取列表第一个值:${list.get(1)}</p>
<p>获取字符串数组第二个元素:${strings[2]}</p>
<p>获取年龄:${people.age}</p>
<p>获取姓名的长度:${fn:length(people.name)}</p>
<h1>登录表单</h1>
<%-- 提交到当前页 --%>
<form action="demo06.jsp" method="get">
<input type="text" name="username" value="user" /> <br />
<input type="checkbox" name="hobbies" value="小说" checked>小说
<input type="checkbox" name="hobbies" value="电影">电影
<input type="checkbox" name="hobbies" value="manga" checked>漫画
<input type="checkbox" name="hobbies" value="music">音乐 <br />
<input type="submit" value="确定">
</form>
<%-- 请求中的请求参数 --%>
<p>用户姓名:${param.username}</p>
<p>用户爱好:${paramValues.hobbies[0]}</p>
<%-- 请求中的cookie --%>
<p>获取JSESSIONID的值:${cookie.get("JSESSIONID").value}</p>
<%-- 作用域对象, 类同四大对象作用域 --%>
<p>获取会话作用域的属性name的值:${sessionScope["name"]}</p>
<p>获取会话作用域的属性age的值:${sessionScope.age}</p>
<%-- tomcat9可以生效, 但这是非标准写法, 不推荐 --%>
${pageContext.include("commons/footer.jsp")}
</body>
</html>
jstl标签
核心标签
核心标签一览表
<c:out> |
用于在 JSP 中显示数据,类似于 <%= ... %> |
<c:if> |
条件判断标签,与一般程序中使用的 if 类似 |
<c:choose> |
作为 <c:when> 和 <c:otherwise> 的父标签,用于组合多条件判断 |
<c:when> |
<c:choose> 的子标签,用于判断某个条件是否成立 |
<c:otherwise> |
<c:choose> 的子标签,当所有 <c:when> 都为 false 时执行 |
<c:forEach> |
基础迭代标签,支持多种集合类型(如 List、Map、数组等) |
<c:set> |
用于保存数据(设置变量) |
<c:remove> |
用于删除某个作用域中的变量 |
<c:catch> |
用来处理产生错误的异常状况,并且将错误信息储存起来 |
<c:import> |
检索一个绝对或相对 URL,并将其内容暴露给当前页面 |
<c:forTokens> |
根据指定的分隔符分割字符串并进行迭代输出 |
<c:param> |
用于给包含(include)或重定向(redirect)的页面传递参数 |
<c:redirect> |
将用户请求重定向到一个新的 URL |
<c:url> |
创建一个带有可选查询参数的 URL(可用于防止 URL 重写问题) |
demo07.jsp
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 引入标准核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录表单</h1>
<%-- 提交到当前页 --%>
<form action="demo07.jsp" method="get">
<input type="text" name="username" value="user" /> <br />
<input type="number" name="score" value="5" max="10" min="0" /> <br />
<input type="submit" value="确定">
</form>
<h1>条件语句if</h1>
<%-- var存储判断结果 --%>
<c:if test="${param.username == 'admin'}" var="isAdmin">
<p><c:out value="欢迎登录" /></p>
</c:if>
<p>是否为admin? <c:out value="${isAdmin}" /></p>
<h1>条件语句choose</h1>
<p>
<c:choose>
<c:when test="${param.score >= 5}">
好
</c:when>
<c:when test="${param.score >= 3}">
中
</c:when>
<c:otherwise>
坏
</c:otherwise>
</c:choose>
</p>
<h1>循环语句forEach1</h1>
<c:forEach var="i" begin="1" end="5" step="1">
<p><c:out value="${i}" /></p>
</c:forEach>
<h1>循环语句forEach2</h1>
<%
List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
request.setAttribute("list", list);
%>
<c:forEach var="str" items="${list}">
<p><c:out value="${str}" /></p>
</c:forEach>
</body>
</html>
web.xml其他配置
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
Cookie和Session
Session 的本质是基于 Cookie 的
Session 是 Web 应用中用于维持用户状态的一种机制。其底层实现通常依赖于 Cookie 技术,通过 JSESSIONID
来标识用户的会话信息。
首次请求时:
当用户首次访问服务器时,Servlet 容器会检查 HTTP 请求中是否包含名为 JSESSIONID
的 Cookie,并查找是否存在与该 ID 对应的 HttpSession
对象。
- 如果是首次访问,且程序中没有调用
getSession()
或getSession(true)
,则不会创建 Session; - 只有当代码中显式调用
getSession()
或getSession(true)
时,容器才会:- 创建一个新的
HttpSession
; - 生成唯一的
JSESSIONID
; - 将该 ID 写入响应头中的
Set-Cookie: JSESSIONID=xxx
,返回给浏览器;
- 创建一个新的
- 浏览器保存该 Cookie 后,在后续请求中将自动携带
JSESSIONID
,用于识别用户会话。
后续请求时:
当用户再次发起请求时,浏览器会自动携带之前保存的 JSESSIONID
(通过 Cookie)发送到服务器。
- Servlet 容器检测到请求中包含
JSESSIONID
,并能查找到对应的HttpSession
; - 此时调用
getSession()
或getSession(true)
会直接返回已有的 Session(不会重新创建新的 Session),除非当前 Session 已失效或过期。
当 Session 被强制失效或自然过期后:
如果 Session 被手动调用 invalidate()
方法销毁,或者因超时而自然过期:
- 用户再次发起请求时,虽然请求中仍携带了
JSESSIONID
; - 但服务器已找不到对应的 Session;
- 此时若调用
getSession()
或getSession(true)
,容器将重新创建一个新的 Session; - 并生成新的
JSESSIONID
返回给客户端,继续后续会话。
特别说明:关于 getSession(false)
getSession(false)
行为不同于其他两个方法;- 如果当前请求没有关联有效的 Session(如首次访问或 Session 已过期),它将返回
null
; - 不会强制创建新的 Session;
- 常用于判断当前用户是否已有有效会话,适用于登录状态检测等场景。
Cookie和Session的区别
存储位置 | 客户端(如浏览器) | 服务器端 |
安全性 | 较低,因为数据存储在客户端,可能被用户篡改或窃取。可以通过设置 HttpOnly 和 Secure 标志提高安全性。 |
较高,因为敏感信息存储在服务器端,不会暴露给客户端。 |
存储容量 | 有限制,通常每个域名下最大4KB左右。 | 可以存储大量数据,受限于服务器的内存或配置的持久化存储空间。 |
传输 | 每次请求都会自动携带相关的Cookie到服务器。 | 只需传递一个短小的 JSESSIONID 到客户端,并通过该ID来关联服务器端的会话数据。 |
生命周期 | 可以通过设置 Max-Age 或 Expires 来控制其生命周期,可以是临时性的也可以是持久化的。 |
生命周期由服务器控制,通常通过设置超时时间来决定会话的有效期。当会话过期或调用 invalidate() 方法时,会话结束。 |
适用场景 | 适合存储少量不敏感的数据,如用户的偏好设置、跟踪访问者等。 | 适合存储需要保护的信息,如登录状态、购物车内容等。 |
隐私问题 | 存储在用户设备上,可能会引起隐私担忧。 | 数据存储在服务器端,减少了对用户隐私的直接暴露。 |
跨域限制 | 受同源策略限制,不同域名下的Cookie不能互相访问。 | 同一应用服务器上的所有Web应用都可以共享同一个Session,不受同源策略限制。 |
性能影响 | 每次请求都会发送Cookie,增加了网络流量和请求大小。 | 减少了每次请求的数据量,但增加了服务器资源的使用。 |
管理复杂度 | 相对简单,主要涉及创建、读取、更新和删除操作。 | 管理相对复杂,涉及到会话的创建、销毁、持久化以及分布式环境下的同步等问题。 |
请求转发和重定向
类型 | 客户端重定向 | 服务器端转发 |
请求次数 | 2次(第一次返回302,第二次访问新URL) | 1次(浏览器不知道) |
地址栏变化 | 变成新URL | 保持原URL |
路径范围 | 可以跳转到外部网站 | 只能在当前Web应用内 |
使用场景 | 登录成功跳转、防止重复提交等 | 跳转Jsp或另一个Servlet |
拓展知识
会话持久化
持久化到本地
path\to\apache-tomcat-9.0.100\conf\context.xml
<!-- 持久化到本地文件系统 -->
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true">
<Store className="org.apache.catalina.session.FileStore" directory="D:/Program/Environment/apache-tomcat-9.0.100/global_sessions"/>
</Manager>
持久化到数据库
驱动下载安装
操作系统 选择 平台独立,下载zip或tar.gz均可,解压复制jar包到path\to\apache-tomcat-9.0.100\lib目录下即可
path\to\apache-tomcat-9.0.100\conf\context.xml
<!-- 持久化到mysql数据库 -->
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true">
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/javaweb?user=ssydx"
sessionTable="tomcat_sessions"
sessionIdCol="session_id"
sessionDataCol="session_data"
sessionValidCol="valid_session"
sessionLastAccessedCol="last_access"
sessionMaxInactiveCol="max_inactive"
sessionAppCol="app_name"/>
</Manager>
mysql
CREATE DATABASE javaweb;
CREATE TABLE tomcat_sessions (
session_id VARCHAR(100) NOT NULL PRIMARY KEY,
valid_session CHAR(1) NOT NULL,
max_inactive INT NOT NULL,
last_access BIGINT NOT NULL,
app_name VARCHAR(255),
session_data MEDIUMBLOB,
KEY kapp_name(app_name)
);
持久化到redis数据库
TODO
#java##javaweb#本专栏包含Java、MySQL、JavaWeb、Spring、Redis、Docker等等,作为个人学习记录及知识总结,将长期进行更新! 如果浏览、点赞、收藏、订阅过少,考虑停更或删除了(😄有点打击积极性)