> 文章列表 > Servlet API 详解

Servlet API 详解

Servlet API 详解

目录

一、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运行原理

细心的同学会发现,我们的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中的数据按照一定的格式显示出来


二、Servlet API

2.1 HttpServlet

2.1.1 核心方法:

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 自然也就会被调用多次。  

2.1.2 生命周期

我们就把这几个关键方法,以及它们的调用时机,称为 Servlet 的 生命周期

Servlet的生命周期:
1.开始的时候执行init
2.每次收到请求后,执行service
3.销毁之前执行destroy

生命周期,这个词是计算机里一个挺常见的术语。
它的意思就是:什么时候该做什么事情。
比如:
        我们在上学,该做的事情就是 学习。
        我们在工作,该做的事情就是 挣钱。
        稳定之后,该做大的事情就是找对象,结婚生子了。
        …
也就是说,在人生的每一个阶段,我们都是有事可做的。这就是 一个普通人 的 生命周期。

我们的代码,也是类似的。
很多的对象,也是会划分出几个阶段。
这个对象,第一阶段做什么,第二阶段做什么…
所以这里划分出的这些阶段,以及每个阶段执行的时机,就就叫做 生命周期。 


2.1.3 处理请求

我们重写doGET方法,当浏览器使用GET方法访问该Servlet路径(method)时,我们在控制台打印doGet,并给浏览器发一个doGet响应

@WebServlet("/method")
public class MethodServlet extends HttpServlet{@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doGet");resp.getWriter().write("doGet");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doPost");resp.getWriter().write("doPost");}@Overrideprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doPut");resp.getWriter().write("doPut");}@Overrideprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("doDelete");resp.getWriter().write("doDelete");}}

 那其他请求怎么发送?

2.1.4 postman 


2.1.5 ajax构造请求 

我们在webapp目录下创建testMethod.html,我们的Servlet程序中可以同时部署静态文件,静态文件我们放在webapp目录下即可


我们编写前端代码,一般使用VScode编写

 

我们打开文件路径后,直接右键打开方式VScode打开即可,然后我们开始使用ajax构造请求发送给服务器
我们首先引入jquery

 然后我们启动tomcat,使用浏览器访问testMethod.html

 


 大家需要注意这里的访问路径,我们这里访问的是test.html文件

想要处理其他方法也是同理,在html中type类型修改,servelet方法中重写对应方法即可

每次修改完代码之后都需要重启服务器


2.2 HttpServletRequest

2.2.1 关键方法

当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象

 query string 是键值对结构,可以通过getParameter根据key获取到value
接下来就使用这些方法来将请求信息打印到浏览器上

@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {@Overrideprotected 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("<br>");stringBuilder.append(req.getMethod());stringBuilder.append("<br>");stringBuilder.append(req.getRequestURI());stringBuilder.append("<br>");stringBuilder.append(req.getContextPath());stringBuilder.append("<br>");stringBuilder.append(req.getQueryString());stringBuilder.append("<br>");stringBuilder.append("<br>"); resp.getWriter().write(stringBuilder.toString());}
}


2.2.2 前后端交互

        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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//通过这个方法来处理body为json的数据//这里我们使用getInputstream 将body数据读出来//再流对象中读多少个字节,取决于Content-Lengthint 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引入该依赖

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.1</version>
</dependency>

 在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对象

写好处理逻辑后,前端再发一次请求  


2.3 HttpServletResponse

2.3.1 核心方法:

演示一下部分方法: 

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{@Overrideprotected 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{@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setStatus(404);
//        但是这里不设置body}
}

 因为我们的body没有空着设置,浏览器自动给了一个默认404界面