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{

    @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");
    }
    
}

 那其他请求怎么发送?

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 {
    @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("<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 {
    @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引入该依赖

<!-- 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{
    @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界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值