目录
一、Servlet运行原理
二、Servlet API
2.1 HttpServlet
2.1.1 核心方法:
2.1.2 生命周期
2.1.3 处理请求
2.1.4 postman
2.1.5 ajax构造请求
2.2 HttpServletRequest
2.2.1 关键方法
2.2.2 前后端交互
2.3 HttpServletResponse
2.3.1 核心方法
细心的同学会发现,我们的Servlet代码中并没有写main方法,那么我们的doGet代码是如何被调用的?相应又是如何返回给浏览器的?
当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器就可以接收到这个请求。
接受请求:
在浏览器输入一个URL,浏览器就会构造一个HTTP请求,这个请求会经过网络协议栈逐层的进行封装直到bit流,通过物理层硬件设备转换为光/电信号传输出去,服务器收到该信号后,又通过网络协议栈逐层分用,层层解析,最终还原成HTTP请求交给Tomcat进行处理,Tomcat通过socket读取到该字符串,按照HTTP请求的格式来解析该请求,根据Context path确定一个webapp,再通过Servlet path确定一个具体的类,然后再根据HTTP请求的方法,确定该类的具体方法,我们的方法中的HttpServletRequest中就包含该HTTP请求的详细信息
计算响应:
服务器在方法中,根据请求,然后计算响应
返回响应:
服务器方法执行完,Tomcat会自动把HttpServletResponse我们设置的对象转换成一个HTTP协议的字符串,通过Socket将该响应发出去,层层封装最后浏览器获取到HTTP响应,浏览器的Socket读到该响应,按照HTTP响应的格式来解析该响应,并把body中的数据按照一定的格式显示出来
init方法: 该方法是在tomcat首次收到了该类相关联(访问/hello路径的请求)的请求时,就会调用到HelloServlet,就需要先对HelloServlet进行实例化,后续在收到请求时,不必再实例化了,直接复用之前的HelloServlet实例即可,只执行一次(类似于懒汉模式)
destroy方法: 当HttpServlet实例不再使用时调用该方法,啥时候该实例就不再使用了?服务器只要不停止,该实例就一直被使用,只有当服务器停止后了,才会调用该方法,只执行一次
这里的destroy能否被执行到,是存在争议的:
如果通过停止按钮,这个操作本质是通过tomcat的8005端口,主动停止,这样才能触发到destroy
如果是直接杀死进程,此时就来不及执行destroy
所以不建议在destroy内执行有效代码
service: 每次收到HTTP请求就会调用 ,HttpServlet父类里会有一个service方法,service会调用doGet。doPut,doDelete…也是同理,在收到对应请求时调用,由service调用
注意! init 和 destroy 方法,都是一个 Servlet 对象调用一次。
而 service 可能会被一个对象,给调用很多次。
毕竟我们大部分时候,进入一个网站都是有目的,至少UI浏览一下,在浏览的过程中就需要进行多次交互,也就会发送多次请求,service 自然也就会被调用多次。
我们就把这几个关键方法,以及它们的调用时机,称为 Servlet 的 生命周期。
Servlet的生命周期:
1.开始的时候执行init
2.每次收到请求后,执行service
3.销毁之前执行destroy
生命周期,这个词是计算机里一个挺常见的术语。
它的意思就是:什么时候该做什么事情。
比如:
我们在上学,该做的事情就是 学习。
我们在工作,该做的事情就是 挣钱。
稳定之后,该做大的事情就是找对象,结婚生子了。
…
也就是说,在人生的每一个阶段,我们都是有事可做的。这就是 一个普通人 的 生命周期。
我们的代码,也是类似的。
很多的对象,也是会划分出几个阶段。
这个对象,第一阶段做什么,第二阶段做什么…
所以这里划分出的这些阶段,以及每个阶段执行的时机,就就叫做 生命周期。
我们重写doGET方法,当浏览器使用GET方法访问该Servlet路径(method)时,我们在控制台打印doGet,并给浏览器发一个doGet响应
@WebServlet("/method")
public class MethodServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
resp.getWriter().write("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost");
resp.getWriter().write("doPost");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPut");
resp.getWriter().write("doPut");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doDelete");
resp.getWriter().write("doDelete");
}
}
那其他请求怎么发送?
我们在webapp目录下创建testMethod.html,我们的Servlet程序中可以同时部署静态文件,静态文件我们放在webapp目录下即可
我们打开文件路径后,直接右键打开方式VScode打开即可,然后我们开始使用ajax构造请求发送给服务器
我们首先引入jquery
然后我们启动tomcat,使用浏览器访问testMethod.html
大家需要注意这里的访问路径,我们这里访问的是test.html文件
想要处理其他方法也是同理,在html中type类型修改,servelet方法中重写对应方法即可
每次修改完代码之后都需要重启服务器
当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象
query string 是键值对结构,可以通过getParameter根据key获取到value
接下来就使用这些方法来将请求信息打印到浏览器上
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//指定返回响应的格式
// 这里是设置响应的content-type,告诉浏览器,响应body里的数据格式是啥样的
resp.setContentType("text/html; charset = utf8");
// 搞个StringBuilder,把这些api的结果拼起来,统一写回到响应中
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(req.getProtocol());
//换行
stringBuilder.append("
");
stringBuilder.append(req.getMethod());
stringBuilder.append("
");
stringBuilder.append(req.getRequestURI());
stringBuilder.append("
");
stringBuilder.append(req.getContextPath());
stringBuilder.append("
");
stringBuilder.append(req.getQueryString());
stringBuilder.append("
");
stringBuilder.append("
");
resp.getWriter().write(stringBuilder.toString());
}
}
1.GET通过query string
2.POST通过form
3.POST通过json
我们下来演示一下上述三种方法,前端给后端传参,我们后端获取到数据
1、GET,query string
前端这里直接通过地址栏构造一个URL发送给后端,useId = 10 & classId = 001,我们后端使用getParameter()方法获取到数据然后响应给浏览器
@WebServlet("/getParameter")
public class GetParameterServelet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//预期浏览器会发一个形如 /getParameter?studentId=10&classId=20 请求
//使用getParameter获取前端query string的数据
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("studentId="+studentId +",classId= " + classId);
}
}
需要注意的是我们这里的value都是String类型的,如果我们getParameter的参数前端并没有传递,那么我们的value就是null
2、POST,form
对于前端是form表格这样格式的数据,后端仍然是使用getParameter来获取,因为它的数据也是键值对,只不过这部分是在body中
我们来提交一组数据
我们可以发现报了404错误,因为我们servlet还没有重写doPOST方法,可以在fiddler里看
我们接下来重写doPost方法
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/postParameter")
public class PostParameterServlet extends HelloServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("studentId="+studentId +",classId= " + classId);
}
}
此时我们就可以获取到前端数据,并且响应给浏览器,使用getParameter,既可以获取到query string中的键值对,也可以获取到form表单body中的键值对
3、POST,json
json是目前比较主流的一种数据格式,也是键值对格式,我们可以将body按照这样的格式组织,在前端既可以使用ajax的方式构造,更简单的是使用postman直接构造.
先实现一下后端处理逻辑:
@WebServlet("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//通过这个方法来处理body为json的数据
//这里我们使用getInputstream 将body数据读出来
//再流对象中读多少个字节,取决于Content-Length
int length = req.getContentLength();
byte[] buffer = new byte[length];
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
// 把这个字节数组构造成String,打印出来
String body = new String(buffer,0,length,"utf8");
System.out.println("body= "+body);
resp.getWriter().write(body);
}
}
然后我们在postman构造一下josn格式的请求,大家需要注意一些格式细节
我们可以看到我们读取到的body数据,我们也可以抓包来看一下。
这个代码的执行流程和form表单传参是类似的,只不过是传输的数据格式是不同的,但是当前通过Json传输数据时,服务器只是把整个body读出来了,并没有按照键值对的方式来处理
这里我们使用第三方库比较合适:jackson,我们通过maven引入该依赖
com.fasterxml.jackson.core
jackson-databind
2.14.1
在pom.xml引入该依赖,记得刷新,现在读取body为json格式的数据时,就可以简化了
class Student {
public int userId;
public int classId;
}
// 使用jackson涉及到的核心对象
ObjectMapper objectMapper = new ObjectMapper();
// readValue就是把一个json格式的字符串转成java对象
Student student = objectMapper.readValue(req.getInputStream(),Student.class);
System.out.println(student.classId+","+student.userId);
这里需要根据前端json中的key来设置我们类中的属性,一一对应,如果前端传的参数多了,会报500错误
ObjectMapper是我们jackson中涉及到的核心对象
这一步操作我们实际会做以下操作:
1.会将body中json格式的数据取出
2.根据第二个类参数,创建Student实例
3.根据json格式的字符串,处理成map键值对结构
4.遍历键值对,看键的名字是否与Student实例的属性名匹配,如果匹配就将value赋值给该属性
5.返回该Student对象
演示一下部分方法:
1、void setCharacterEncoding(String charset)
设置被发送到客户端响应的字符编码
resp.setContentType("text/html; charset=utf8");
因为没有指明编码方式,此时浏览器只能随便指定一个编码方式,出不出现乱码完全是运气问题,所以我们需要指明编码方法(注意:设置setContentType和字符集务必要再write上边)
2、void sendRedirect(String location)
使用重定向位置URL发送临时重定向给客户端
@WebServlet("/redirect")
public class RedirectServlet extends HelloServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("https://www.baidu.com");
}
}
当我们访问redirect路径时,我们就会跳转到百度主页,我们来抓包看一下
这里请求就是一个正常的请求,正常的访问servlet路径
3、void setStatus(int sc)
给响应设置状态码
@WebServlet("/status")
public class StatusServlet extends HelloServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(404);
// 但是这里不设置body
}
}
因为我们的body没有空着设置,浏览器自动给了一个默认404界面