目录
一、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{@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//显示告诉浏览器,你拿到的数据是htmlresp.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()); //QuertStringrespBody.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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//实现重定向,让浏览器自动跳转到百度浏览器resp.setStatus(302);//设置重定向,让浏览器自动跳转到百度resp.setHeader("Location", "https://www.baidu.com");}
}