目录
一、Servlet运行原理
1.1、问题
1.2、Servlet的具体执行过程
1.3、Tomcat初始化流程小结
1.4、Tomcat处理请求流程
二、Servlet API详解
2.1、HttpServlet类
2.1.1、处理Get请求
2.2、HttpServletRequest类
2.3、HttpServletResponse类
2.3.1、设置状态码
2.3.2、自动刷新
2.3.3、重定向
一、Servlet运行原理
1.1、问题
在Servlet的代码中我们并没有写main方法,要知道一个程序的入口是main方法,那么没有main方式是如何被调用呢?响应又是如何返回给浏览器呢?
当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器,就可以接收到这个请求
1.2、Servlet的具体执行过程
客户端发送请求->到Tomcat的webServer->Servlet管理器(多个)->Servlet实例
具体过程:
(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中的数据按照一定的格式显示在浏览器的界面上。
1.3、Tomcat初始化流程小结
- Tomcat的代码中内置了main方法,当我们启动Tomcat的时候,就是从Tomcat的main方法开始执行的;
- 被@webservlet注解修饰的类会在Tomcat启动的时候就会被获取到,并集中管理;
- Tomcat通过 反射 这样的语法机制来创建被 @WebServlet 注解修饰的类的实例;
这些实例被创建完了之后 , 会点调用其中的 init 方法进行初始化 . ( 这个方法是 HttpServlet 自带的 , 我们自己写的类可以重写 init); 这些实例被销毁之前 , 会调用其中的 destory 方法进行收尾工作 . ( 这个方法是 HttpServlet 自带的 , 我们自己写的类可以重写 destory);
1.4、Tomcat处理请求流程
- Tomcat 从 Socket 中读到的 HTTP 请求是一个字符串, 然后会按照 HTTP 协议的格式解析成一个HttpServletRequest 对象;
- Tomcat 会根据 URL 中的 path 判定这个请求是请求一个静态资源还是动态资源. 如果是静态资源, 直接找到对应的文件把文件的内容通过 Socket 返回. 如果是动态资源, 才会执行到 Servlet 的相关逻辑.
- Tomcat 会根据 URL 中的 Context Path 和 Servlet Path 确定要调用哪个 Servlet 实例的 service 方法.
- 通过 service 方法, 就会进一步调用到我们之前写的 doGet 或者 doPost
二、Servlet API详解
2.1、HttpServlet类
在写Servlet代码的时候,第一步是创建一个类,继承HttpServlet,并重写其中的方法
方法 | 调用时机 |
init | 在HttpServlet实例化之后被调用一次 |
destroy | 在HttpServelet实例不再使用时调用一次 |
service | 收到HTTP请求时调用 (由service调用) |
doGet | 收到GET请求时调用 (由service调用) |
doPost | 收到POST请求时调用 (由service调用) |
doPut / doDelete… | 收到对应请求时调用 (由service调用) |
init方法:该方法是在tomcat首次收到了该类相关联的请求时,就会调用到HelloServlet,就需要先对HelloServlet进行实例化,后续在收到请求时,不必再实例化了,直接复用之前的HelloServlet实例即可,只执行一次
destroy方法:当HttpServlet实例不再使用时调用该方法,啥时候该实例就不再使用了?服务器只要不停止,该实例就一直被使用,只有当服务器停止后了,才会调用该方法,只执行一次
Tomcat有两种方式结束:
- 通过8005端口,给Tomcat发起特殊的请求,Tomcat就关闭了,这就能执行destory
- 直接杀死Tomcat进程,比如使用任务管理器关闭Tomcat服务器,才是就不能执行desstory方法
service方法:service中根据请求的类型不同,调用不同的方法,doGet,doPost方法等等,会执行多次,每收到一次HTTP请求就执行一次
面试题:谈谈Servlet的生命周期
- webapp刚被加载的时候,调用servlet的init方法
- 每次收到请求的时候,调用service方法
- webapp要结束的时候,调用destory方法
注意:HttpServlet的实例只是在程序启动时创建一次,而不是每次收到HTTP请求都重新创建实例
2.1.1、处理Get请求
1、直接在浏览器中,通过URL就能构造(GET请求最常用法)
2、通过postman构造Get请求
3、通过ajax构造Get请求
2.2、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 对象 |
注意:请求对象是服务器收到的内容, 不应该修改。因此上面的方法也都只是 "读" 方法, 而不是 "写"方法。
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet("/request")
public class RequestServlet extends HelloServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//显示告诉浏览器,你拿到的数据是html
resp.setContentType("text/html");
//调用req的各个方法,把得到的结果汇总到一个字符串中,统一返回到页面上
StringBuilder respBody = new StringBuilder();
//下列内容是在浏览器上按照html方式来展示的,此时\n在html中并不是换行
//而我们需要使用<br>标签进行换行
respBody.append(req.getProtocol()); //HTTP版本号
respBody.append("<br>");
respBody.append(req.getMethod()); //返回HTTP方法的名称
respBody.append("<br>");
respBody.append(req.getRequestURI()); //请求路径
respBody.append("<br>");
respBody.append(req.getContextPath()); //上下文路径
respBody.append("<br>");
respBody.append(req.getQueryString()); //QuertString
respBody.append("<br>");
//拼接header
//获取请求的header头
Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()) {
String headerName = headers.nextElement();
respBody.append(headerName);
respBody.append(": ");
respBody.append(req.getHeader(headerName));
respBody.append("<br>");
}
//统一返回结果
resp.getWriter().write(respBody.toString());
}
}
浏览器响应结果如下:
2.3、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
中写入二进制格式数据
|
注意:响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序员设置的。因此上面的方法都是"写"方法。
注意:对于状态码/响应头的设置要放到getWriter/GetOutputStream之前,否则可能设置失效。
2.3.1、设置状态码
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("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//resp.setStatus(404);
resp.setStatus(200);
//返回Tomcat自带的错误页面
resp.sendError(500);
}
}
2.3.2、自动刷新
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("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//告诉浏览器每隔2秒刷新一次
resp.setHeader("refresh", "2");
//返回系统时间
resp.getWriter().write("time: " + System.currentTimeMillis());
}
}
2.3.3、重定向
实现一个程序,返回一个重定向HTTP响应,自动跳转到另一个页面
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("/redirect")
public class Redirect extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实现重定向,让浏览器自动跳转到百度浏览器
resp.setStatus(302);
//设置重定向,让浏览器自动跳转到百度
resp.setHeader("Location", "https://www.baidu.com");
}
}