Servlet是什么
Servlet
是一种实现动态页面的技术
.
是一组
Tomcat
提供给程序猿的
API,
帮助程序猿简单高效的开发一个 web app。
第一个Servlet程序
1.创建项目
菜单 -> 文件 -> 新建项目 ->选择Maven
2.引入依赖
Maven
项目创建完毕后
,
会自动生成一个
pom.xml
文件。
我们需要在
pom.xml
中引入
Servlet API
依赖的
jar
包。

2.选择版本,一般选用3.1.0
Servlet的版本要和Tomcat的版本匹配。
具体版本对应在https://tomcat.apache.org/whichversion.html 可以查到。
3.把中央仓库中的xml复制到项目中的pom.xml。
其中<dependencies>标签内部放置项目依赖的jar包,maven会自动下载依赖到本地。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>message_wall</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</project>
3.创建目录
1)创建webapp目录
在
main
目录下
,
和
java
目录并列
,
创建一个
webapp
目录
(
注意
,
不是
webapps)。
2)创建web.xml
然后在
webapp
目录内部创建一个
WEB
-
INF
目录
,
并创建一个
web.xml
文件。
3)编写web.xml
往
web.xml
中拷贝以下代码
.
具体细节内容我们暂时不关注
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
webapp 目录就是未来部署到 Tomcat 中的一个重要的目录. 当前我们可以往 webapp 中放一些静
态资源, 比如 html , css 等。
在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动
态资源。
4.编写代码
在
java
目录中创建一个类
HelloServlet,
代码如下
:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("hello");
resp.getWriter().write("hello");
}
}
当满足以下三个条件时,Tomcat就会找到这个类,在合适的时候进行调用。
a)创建的类要继承自HttpServlet。
b)这个类需要使用@WebServlet注解,关联上一个HTTP路径。
c)这个类需要实现doXXX方法。
5.打包程序
使用
maven
进行打包
.
打开
maven
窗口
(
一般在
IDEA
右侧就可以看到
Maven
窗口
,
如果看不到的话
,可以通过 菜单 -> View -> Tool Window -> Maven
打开
)
然后展开
Lifecycle ,
双击
package
即可进行打包。
打包成功以后,可以看到在target目录下,生成了一个jar包。
这样的
jar
包并不是我们需要的
, Tomcat
需要识别的是另外一种
war
包格式
.
另外这个
jar
包的名字太复杂了
, 我们也希望这个名字能更简单一点.


在
pom.xml
中新增一个
packing
标签
,
表示打包的方式是打一个
war
包
.
<packaging>war</packaging>
在
pom.xml
中再新增一个
build
标签
,
内置一个
finalName
标签
,
表示打出的
war
包的名字是
HelloServlet。
<build>
<finalName>ServletHelloWorld</finalName>
</build>
完整的pom.xml形如
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Servlet</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<finalName>java1091</finalName>
</build>
</project>
重新使用
maven
打包
,
可以看到生成的新的
war
包的结果。
6.部署程序
把
war
包拷贝到
Tomcat
的
webapps
目录下
.
7.验证程序
此时通过浏览器访问
http://127.0.0.1:8080/ServletHelloWorld/hello
就可以看到结果了
注意
:
URL
中的
PATH
分成两个部分
,
其中
HelloServlet
为
Context Path,
hello
为
Servlet Path
更方便的部署方法
手动拷贝
war
包到
Tomcat
的过程比较麻烦
.
我们还有更方便的办法
.
此处我们使用
IDEA
中的
Smart Tomcat
插件完成这个工作

配置好了以后,点击绿色三角运行即可。
Servlet运行原理
在
Servlet
的代码中我们并没有写
main
方法
,
那么对应的
doGet
代码是如何被调用的呢
?
响应又是如何返回给浏览器的?
1.
接收请求
:
-
用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求 .
-
这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit 流 , 最终通过物理层的硬件设备转换成光信号/ 电信号传输出去 .
-
这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达目标主机 ( 这个过程也需要网络层和数据链路层参与).
-
服务器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成HTTP 请求 . 并交给 Tomcat 进程进行处理 ( 根据端口号确定进程 )
-
Tomcat 通过 Socket 读取到这个请求 ( 一个字符串 ), 并按照 HTTP 请求的格式来解析这个请求 , 根据请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类 . 再根据当前请求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法 . 此时我们的代码中的doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息 .
2.根据请求计算响应
-
在我们的 doGet / doPost 方法中 , 就执行到了我们自己的代码 . 我们自己的代码会根据请求中的一些信息, 来给 HttpServletResponse 对象设置一些属性 . 例如状态码 , header, body 等 .
3.返回响应
-
我们的 doGet / doPost 执行完毕后 , Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串 , 通过 Socket 把这个响应发送出去 .
-
此时响应数据在服务器的主机上通过网络协议栈层层 封装 , 最终又得到一个二进制的 bit 流 , 通过物理层硬件设备转换成光信号/ 电信号传输出去
-
这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达浏览器所在的主机 ( 这个过程也需要网络层和数据链路层参与)
-
浏览器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成HTTP 响应 , 并交给浏览器处理
-
浏览器也通过 Socket 读到这个响应 ( 一个字符串 ), 按照 HTTP 响应的格式来解析这个响应 . 并且把body 中的数据按照一定的格式显示在浏览器的界面上 .
Tomcat的伪代码如下:
1.Tomcat初始化流程
class Tomcat {
// 用来存储所有的 Servlet 对象
private List<Servlet> instanceList = new ArrayList<>();
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
Class<Servlet>[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class<Servlet> cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
Servlet ins = cls.newInstance();
instanceList.add(ins);
}
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.init();
}
// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
2.Tomcat处理请求流程
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内
容
// 直接使用我们学习过的 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
// 最终找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
3.Servlet的service方法的实现
class Servlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
......
}
}
Servlet API
HttpServlet
方法名称
|
调用时机
|
init
|
在
HttpServlet
实例化之后被调用一次
|
destroy |
在
HttpServlet
实例不再使用的时候调用一次
|
service
|
收到
HTTP
请求的时候调用
|
doGet
|
收到
GET
请求的时候调用
(
由
service
方法调用
)
|
doPost
|
收到
POST
请求的时候调用
(
由
service
方法调用
)
|
doPut/doDelete/doOptions/...
|
收到其他请求的时候调用
(
由
service
方法调用
)
|
Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过程).
注意
:
HttpServlet
的实例只是在程序启动时创建一次
.
而不是每次收到
HTTP
请求都重新创建实例
.
HttpServletRequest
当
Tomcat
通过
Socket API
读取
HTTP
请求
(
字符串
),
并且按照
HTTP
协议的格式把字符串解析成
HttpServletRequest
对象
.
方法 | 描述 |
String getProtocol()
|
返回请求协议的名称和版本。
|
String getMethod()
|
返回请求的
HTTP
方法的名称,例如,
GET
、
POST
或
PUT
。
|
String getRequestURI()
|
从协议名称直到
HTTP
请求的第一行的查询字符串中,返回该请求的 URL
的一部分。
|
String getContextPath()
|
返回指示请求上下文的请求
URI
部分。
|
String getQueryString()
|
返回包含在路径后的请求
URL
中的查询字符串。
|
Enumeration getParameterNames()
|
返回一个
String
对象的枚举,包含在该请求中包含的参数的名称。
|
String getParameter(String name)
|
以字符串形式返回请求参数的值,或者如果参数不存在则返回null。
|
String[] getParameterValues(
String name)
|
返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回null
|
Enumeration getHeaderNames()
|
返回一个枚举,包含在该请求中包含的所有的头名。
|
String getHeader(String name)
|
以字符串形式返回指定的请求头的值。
|
String getCharacterEncoding()
|
返回请求主体中使用的字符编码的名称。
|
String getContentType()
|
返回请求主体的
MIME
类型,如果不知道类型则返回
null
。
|
int getContentLength()
|
以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1
。
|
InputStream getInputStream()
|
用于读取请求的
body
内容
.
返回一个
InputStream
对象
.
|
注意: 请求对象是服务器收到的内容, 不应该修改. 因此上面的方法也都只是 "读" 方法, 而不是 "写"
方法.
HttpServletResponse
Servlet
中的
doXXX
方法的目的就是根据请求计算得到相应
,
然后把响应的数据设置到 HttpServletResponse 对象中
.
然后
Tomcat
就会把这个
HttpServletResponse
对象按照
HTTP
协议的格式
,
转成一个字符串
,
并通过Socket 写回给浏览器
.
方法
|
描述
|
void setStatus(int sc)
|
为该响应设置状态码
|
void setHeader(String name, String value)
|
设置一个带有给定的名称和值的
header.
如果
name
已经存在,
则覆盖旧的值
|
void addHeader(String
name, String value)
|
添加一个带有给定的名称和值的
header.
如果
name
已经存在
, 不覆盖旧的值,
并列添加新的键值对
|
void setContentType(String
type)
|
设置被发送到客户端的响应的内容类型。
|
void setCharacterEncoding(String
charset)
|
设置被发送到客户端的响应的字符编码(
MIME
字符集)例如,UTF-8。
|
void sendRedirect(String
location)
|
使用指定的重定向位置
URL
发送临时重定向响应到客户端。
|
PrintWriter getWriter()
|
用于往
body
中写入文本格式数据
.
|
OutputStream getOutputStream()
|
用于往
body
中写入二进制格式数据
.
|
注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方
法都是 "写" 方法.
Cookie和Session
回顾 Cookie
HTTP 协议自身是属于 "无状态" 协议:默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的,例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否是已经登陆过了.

Cookie的作用是让同一个客户端和服务端之间,多次通信产生联系。(粗略理解为Cookie可以帮助保存一些信息,如账号密码等,下次重新打开该网页的时候,这些内容还存在)
理解会话机制(Session)
服务器同一时刻收到的请求是很多的
.
服务器需要区分清楚每个请求是从属于哪个用户
,
就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系。
会话的本质就是一个
"
哈希表
",
存储了一些键值对结构
. key
就是令牌的
ID(token/sessionId), value
就是用户信息(
用户信息可以根据需求灵活设计
).
令牌ID-用户信息
通过令牌ID,判断是哪个客户端来访问,再传递出对应的用户信息。
Cookie 和 Session 的区别
- Cookie 是客户端的机制. Session 是服务器端的机制.
- Cookie 和 Session 经常会在一起配合使用. 但是不是必须配合.
- 完全可以用 Cookie 来保存一些数据在客户端. 这些数据不一定是用户身份信息, 也不一定是 token / sessionId
- Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递.
核心方法
HttpServletRequest
类中的相关方法

HttpServletResponse
类中的相关方法
方法
|
描述
|
void addCookie(Cookie cookie)
|
把指定的
cookie
添加到响应中
.
|
HttpSession
类中的相关方法
一个
HttpSession
对象里面包含多个键值对
.
我们可以往
HttpSession
中存任何我们需要的信息
.
方法
|
描述
|
Object getAttribute(String
name)
|
该方法返回在该
session
会话中具有指定名称的对象,如果没有指定名称的对象,则返回null
|
void setAttribute(String
name, Object value)
|
该方法使用指定的名称绑定一个对象到该
session
会话
|
boolean isNew()
|
判定当前是否是新创建出的会话
|
Cookie
类中的相关方法
每个
Cookie
对象就是一个键值对
方法
|
描述
|
String getName()
|
该方法返回
cookie
的名称。名称在创建后不能改变。
(
这个值是
Set-Cooke 字段设置给浏览器的
)
|
String getValue()
|
该方法获取与
cookie
关联的值
|
void setValue(String
newValue)
|
该方法设置与
cookie
关联的值。
|
