1. WEB通信模型
概念: web通信就是客户端和服务端基于HTTP协议下的请求和响应的过程:
- 协议:web服务器必须遵守
HTTP协议,其底层是TCP/IP协议:TCP负责将数据完整的送到目的地,尽管路途中可能会将数据拆成若干小块。IP负责把数据准确地送到目的地。
- 客户端
client:指人类用户或者浏览器。 - 服务端
server:指物理主机硬件或WEB服务器应用软件。
z-image/WEB通信模型.jpg
1.1 GET请求
概念: GET请求无请求体,用于从服务器快速获取资源:
- GET请求模板:
通信协议://服务器IP或名称:服务器端口/资源所在目录/资源?查询串- 所请求的资源,可以是HTML,PDF,Servlet等。
- 查询串记录着请求携带的数据,长度有限制。
- GET请求理解:
- 客户端:"嘿!服务器,给我拿(get)下某服务器中的某资源,哦对了,这有一些给你的查询串参数,请快点!"
- 服务端:"好的,我会去拿(get)那个资源,也谢谢你提供的查询串参数,不过有一点要说明,咱俩的HTTP协议中,可没有包含 '快一点' 这一项!"
- GET请求观察:
- 开发首页
index.html,布局"HelloWorld"文字,部署,运行。 - 打开浏览器开发者模式,切换到
Network选项卡。 - 发送get请求
{{tomcat}}/servlet3/index.html。 - 点击
Network中出现的本次请求路径,查看请求响应信息。 - 查看
Headers选项卡中的General请求常规信息:RequestURL:请求的URL,包括查询串。RequestMethod:请求的方式。Status Code:请求状态码和状态码文字信息。
- 查看
Headers选项卡中的Response Header响应头信息:Content-Length:响应数据的字节大小。Content-Type:响应的数据MIME类型。Date:响应发生的时间。
- 查看
Headers选项卡中的Request Header请求头信息:Accept:设定请求可接收的MIME数据类型列表,响应数据类型不匹配会拒绝。User-Agent:请求的浏览器信息。
- 查看
Headers选项卡中的Query String Prameters查询串信息。 - 查看
Response选项卡中的响应的数据。
- 开发首页
布局: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="#" method="post">
<label for="username-ipt">账号</label>
<input type="text" id="username-ipt" name="username">
<label for="password-ipt">密码</label>
<input type="password" id="password-ipt" name="password">
<button type="submit">登陆</button>
</form>
</body>
</html>
2.2 POST请求
概念: POST就是使用请求体携带额外数据的GET请求,牺牲了速度,但安全,传输量更大:
- POST请求理解:
- 客户端:"嘿!服务器,请把这个请求提交(post)给某服务器中的某资源,别忘了看我的请求体,那里面有我发送的一些重要的表单数据!"
- 服务端:"好的,我会去寻找那个地址上的资源,等我找到了,我会把你在请求体中发送的数据交给它!"
- POST请求观察:
- 在
index.html中开发表单,action="#",method="post",部署运行。 - 访问首页
{{tomcat}}/servlet3/index.html。 - 打开浏览器开发者模式,切换到
Network选项卡。 - 提交表单,点击
Network中出现的本次请求路径,查看请求响应信息。 - 额外查看
Headers选项卡中的Form Data请求体表单数据。
- 在
布局: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="#" method="post">
<label for="username-ipt">账号</label>
<input type="text" id="username-ipt" name="username">
<label for="password-ipt">密码</label>
<input type="password" id="password-ipt" name="password">
<button type="submit">登陆</button>
</form>
</body>
</html>
2.3 其他请求
概念: 大部分浏览器仅支持前两种类型的HTTP请求:
get:请求URL上的一个资源。post:请求URL上的一个资源,且要求服务器接收附加到请求体中的信息。head:请求URL上的一个资源,但要求只返回响应头信息,不返回响应体。trace:请求URL上的一个资源,要求回送请求消息,以便客户端测试或者排错。put:请求URL上的一个资源,且携带额外一些包含的信息。delete:请求删除URL上的一个资源。
2. Servlet入门
概念: web容器擅长提供静态页面,不擅长操作 just-in-time 即时动态页面,若想得到一个即时显示当前的系统时间的HTML,则web容器必须求助一些辅助程序,如servlet:
- 客户端发送请求,请求URL指向web容器中的辅助程序servlet。
- web容器分析出这个请求是发给servlet的,而非直接获取静态资源。
- web容器启动并运行客户端指定的servlet,将请求和请求参数一并转交。
- servlet执行
service(),构造一个插入了当前系统时间的HTML静态代码。 - servlet将HTML静态代码返回给web容器。
- web容器关闭servlet,并将HTML静态代码响应给客户端。
- 客户端解析HTML静态代码并渲染到浏览器中,展示最终页面。
servlet3依赖:
javax.servlet-api。
配置: pom.xml
<!-- 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>
2.1 XML配通
概念: 采取继承,重写,配置的纯手动模式来配置一个servlet:
- 开发
XmlServlet类继承HttpServlet:- 实现
Servlet接口和继承GenericServlet接口均已过时。
- 实现
- 重写
HttpServlet中的doGet()和doPost():- 方法参数:
(HttpServletRequest req, HttpServletResponse resp)。
- 方法参数:
- 配置servlet:在
web.xml文件中添加:- 父标签:
<servlet>:配置servlet基本信息,包括名称和类全名。 - 父标签:
<servlet-mapping>:配置servlet映射信息,包括名称和路由。 - 子标签:
<servlet-name>:配置servlet名称,不对外暴露。 - 子标签:
<servlet-class>:配置servlet类全名。 - 子标签:
<url-pattern>:配置servlet路由,须以/开头。
- 父标签:
- 开发
doGet():设置响应MIME类型和编码,获取响应流并回写数据:resp.setContentType("text/html;charset=utf-8")resp.getWriter().print("数据")resp.getOutputStream().print("数据")
- PM:
{{tomcat}}/servlet3/api/servlet/xml
源码: XmlServlet.java
package com.yap.start.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.io.PrintWriter;
import java.util.Date;
public class XmlServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Request Headers [Accept:text/html...]
resp.setContentType("text/html;charset=utf-8");
// response data
PrintWriter writer = resp.getWriter();
writer.println("<!DOCTYPE html>");
writer.println("<html>");
writer.println("<head>");
writer.println("<meta charset=\"UTF-8\">");
writer.println("<title>xml-servlet</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("系统时间:" + new Date());
writer.println("</body>");
writer.println("</html>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
配置: web.xml
<servlet>
<servlet-name>xmlServlet</servlet-name>
<servlet-class>com.yap.start.servlet.XmlServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>xmlServlet</servlet-name>
<url-pattern>/api/servlet/xml</url-pattern>
</servlet-mapping>
2.2 注解配通
概念: servlet3+版本支持使用 @WebServlet 注解省略servlet手动配置:
- 开发
AnnotationServlet类,完成继承重写步骤。 - 对servlet类添加
@WebServlet注解:- 必须使用
value或urlPatterns来指定一个或多个路由。
- 必须使用
- 开发
doGet():回写插入了当前系统时间的页面。 - PM:
{{tomcat}}/servlet3/api/servlet/annotation_a。 - PM:
{{tomcat}}/servlet3/api/servlet/annotation_b。
源码: AnnotationServlet.java
package com.yap.start.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;
import java.io.PrintWriter;
import java.util.Date;
@WebServlet({"/api/servlet/annotation_a", "/api/servlet/annotation_b"})
public class AnnotationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Request Headers [Accept:text/html...]
resp.setContentType("text/html;charset=utf-8");
// response data
PrintWriter writer = resp.getWriter();
writer.println("<!DOCTYPE html>");
writer.println("<html>");
writer.println("<head>");
writer.println("<meta charset=\"UTF-8\">");
writer.println("<title>xml-servlet</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("系统时间:" + new Date());
writer.println("</body>");
writer.println("</html>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
2.3 初始化参数
概念: servlet在初始化时可以设置一些独享的String类型参数:
- XML配通可以在
<servlet>中使用<init-param>完成初始化参数设置:<param-name>tel</param-name><param-value>182</param-value>
- 注解配通可以在
@WebServlet中使用initParams属性完成初始化参数设置:initParams = { @WebInitParam(name="tel", value="182")... }
- 在
doGet()中获取当前servlet的初始化参数(无法设置):super.getServletConfig().getInitParameter(KEY)
源码: ServletConfigXmlServlet.java
package com.yap.start.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 ServletConfigXmlServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(super.getServletConfig().getInitParameter("tel"));
System.out.println(super.getServletConfig().getInitParameter("email"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
配置: web.xml
<servlet>
<servlet-name>initParamXmlServlet</servlet-name>
<servlet-class>com.joezhou.start.servlet.ServletConfigXmlServlet</servlet-class>
<init-param>
<param-name>tel</param-name>
<param-value>18210210122</param-value>
</init-param>
<init-param>
<param-name>email</param-name>
<param-value>yy06200210@163.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>initParamXmlServlet</servlet-name>
<url-pattern>/api/servlet/servlet_config_xml</url-pattern>
</servlet-mapping>
源码: ServletConfigAnnotationServlet.java
package com.yap.start.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
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(value = "/api/servlet/servlet_config_annotation", initParams = {
@WebInitParam(name = "tel", value = "1297849057"),
@WebInitParam(name = "email", value = "1297849057@qq.com"),
})
public class ServletConfigAnnotationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(super.getServletConfig().getInitParameter("tel"));
System.out.println(super.getServletConfig().getInitParameter("email"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
2.4 全局参数
概念: web容器在启动时可以设置一些所有servlet共享的String类型参数:
- 在
web.xml中添加<context-param>完成全局参数设置:<param-name>loc</param-name><param-value>china</param-value>
- 在
doGet()中获取全局参数(无法设置):super.getServletContext().getInitParameter(KEY)
源码: ServletContextServlet.java
package com.yap.start.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("/api/servlet/servlet_context")
public class ServletContextServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(super.getServletContext().getInitParameter("loc"));
System.out.println(super.getServletContext().getInitParameter("type"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
配置: web.xml
<context-param>
<param-name>loc</param-name>
<param-value>china</param-value>
</context-param>
<context-param>
<param-name>type</param-name>
<param-value>human</param-value>
</context-param>
3. Servlet生命周期
概念: servlet是单例多线程的,web容器会每个指向servlet的请求都分配一个单独的线程:
- 启动服务器时:
- web容器寻找部署文件(DD文件)
web.xml。 - web器找所有的servlet类,如
LifeCycleServlet.class并加载它。 - web容器调用这个类的构造方法来实例化,所以我们自己不要重写它的构造器。
- web容器为每一个servlet建立一个ServletConfig对象,负责读servlet的初始化参数,还能来访问ServletContext(负责读取web信息,如工程首页等)。
- web容器寻找部署文件(DD文件)
- 请求到来时:
- web容器分析出这个请求指向的是一个servlet 。
- web容器创建
HttpServletRequest请求对象和HttpServletResponse响应对象。 - web容器根据请求的URL找到
LifeCycleServlet。 - web容器调用servlet类的
init()初始化这个类,只执行一次。 - web容器为该请求分配线程,用于调用
service()并支付req和resp。 service()根据请求头决定调用doGet()或doPost(),并支付req和resp。doGet()或doPost()开始执行代码并做出响应。- 线程结束,线程销毁或被遣返回线程池,
req和resp离开作用域,等待GC。 - 客户得到最终响应。
- 关闭服务器时:
- web容器调用servlet的
destroy()做一些清理工作,只执行一次。
- web容器调用servlet的
若servlet配置了
<load-on-startup>且值大于等于0,则init()在servlet的实例化时执行,值越小加载优先级越高,若值小于0或不配置,则init()在第一次servlet请求时执行。
源码: LifeCycleServlet.java
package com.yap.start.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(value = "/api/servlet/life_cycle", loadOnStartup = 1)
public class LifeCycleServlet extends HttpServlet {
@Override
public void destroy() {
System.out.println("destroy()...");
}
@Override
public void init() {
System.out.println("init()...");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("doGet()...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
Servlet生命周期小说
第一章:启动服务器
我叫 HelloServlet,我是一个字节码文件,我出生在一个混沌的世界中,我对出生的印象很模糊,不知道自己的父母是谁。
我听见有人在敲我的门,打开门后看见的是一个一脸严肃的人。
"你好,我叫类加载器,特意来要带你离开这里。"
"带我去哪里?"
"Tomcat服务器已经启动了,我需要把一些类加载到内存中去..."
"所有的类都要加载吗?"
"不,只需要一些特殊的类,它们都是 HttpServlet 的孩子..."
"你是怎么找到我的?"
"Web容器手里有一份部署文件名单(web.xml),我通过上面的地址()找到了你,你是最后一个。"
“我一直想问,我是从哪来的?是你口中那个Web容器创造的我吗?”
"没错,服务器启动的时候,Web容器就已经根据 HelloServlet.java 来编译创造了你,它是你的本体。"
"那我的本体现在在哪?"
"因为它比较重要,所以它不会被发布到服务器,仍在硬盘世界里,你是找不到它的,其余更多的内容你就不要再问了。"
我哦了一声,跟着它终于来到了服务器中,映入眼帘的是一个公告板,上面写着一句话:“项目的首页是index.html”。
“这是什么意思?”我指着公告板问类加载器。
他回答我说:“这个板子只有一个,它叫做 ServletContext ,上面的是整个工程的配置信息,所有的Servlet都能看见这块板子"。
我点点头,然后被他带进了一个名字叫做 WEB-INF/classes 的房间中。
"接下来我要干点什么?"
"什么都不用做,等着就行了,会有人来找你的,我得走了,再见!"
第二章:来请求了
浑浑噩噩中,突然脑袋上方传来一个声音:"醒一醒!"
我被吓了一跳,连忙问道:“你是谁?”
“我是Web容器,有客户发请求了,想要访问你,现在把你的 init() 方法执行一下,这里有一张你的任务清单,每一个Servlet都会有一张,拿好了。"
我伸手拿过这张任务清单,上面的标题是:servletConfig ,上面已经详细的纪录了我的所有出生信息和初始化参数,但是最不能让我容忍的是,我居然有一个工作小名,叫abc。”
"没问题,但是我的 init() 方法里什么都没有,是不是就不用做什么了?"
"嗯对。"
Web容器话音未落,身后已经多了一个人,他对我说道:“你好,我是一个线程,我叫9527,是老大分配过来的。”
"你要做什么?"
"我需要把你的 service() 执行一下,正好我刚拿到老大创建的 request 和 response。对了,用户发送的是一个get请求,service() 方法会自动调用 doGet() ,并且把这两个参数传递给 doGet() 方法,然后我要执行它里面的内容!"
话音未落,突然身后又多了一个人,他对我说道:“你好,我是一个线程,我叫9528,是老大分配过来的。”
"又来一个?"
我回过身问9527怎么办,9527淡定地回复我说:“没关系,我们所有线程都可以同时工作,互相不干扰,因为我们谁都没有带锁,如果涉及到共享数据,我们可能会干一架,你在旁边看就好了。”
过了几分钟,两个人都把活干完了,并且他们的请求已经得到了正确的响应,然后他们先后对我说了再见,并且回到了线程池中,临走的时候9527还把那两个参数随手一扔,跟我说这两个参数已经没有什么用了,扔地上吧,一会儿GC大妈会过来处理。
第三章:关闭服务器
我的生活就在这里日复一日,直到有一天,头顶又传来那个讨厌的声音:“abc!服务器要重启了!请调用一下你的 destroy() 方法,留最后的遗言!”
"可是我的 destroy() 方法中没有任何代码.."
“哦,那你去死吧...”
然后我眼前一黑,大家再见。