【JavaEE】_Servlet API

目录

1. HttpServlet

1.1 init方法

1.2 destroy方法

1.3 service方法

1.4 Servlet的生命周期

1.5 代码示例

1.5.1 使用postman构造请求

1.5.2 使用ajax构造请求

2. HttpServletRequest

2.1 核心方法

2.2 代码示例1:打印请求信息

3. 前端给后端传参

3.1 通过GET的query string

3.2 通过POST,借助form表单

3.3 通过POST,使用json格式构造body

4. HttpServletResponse


[便捷起见,非必要时Servlet程序均以smart tomcat方式部署程序]

Servlet有3个重要类,分别为HttpServlet,HttpServletRequest,HttpServletResponse;

1. HttpServlet

编写Servlet程序第一步就是创建一个类继承自HttpServlet类并重写其doGet方法;

其核心方法有:

方法名称 调用时机
init 在HttpServlet实例化之后被调用一次
destroy 在HttpServlet实例不再使用的时候调用一次
service 收到HTTP请求的时候调用
doGet 收到GET请求时调用(由service方法调用)
doPost 收到POST请求时调用(由service方法调用)
doPut/doDelete/do Options/... 收到其他请求时调用(由service方法调用)

1.1 init方法

1. 不由程序员手动调用,由tomcat自动调用

2. 当tomcat首次收到了和该类相关联的请求时,才会进行实例化

    以上一篇用Servlet的hello world程序为例:

【JavaEE】_Servlet API_第1张图片

WebServlet注解就是将/hello类和HelloServlet类绑定在一起,表示:

如果Tomcat收到了/hello这样路径的请求,就会调用HelloServlet,于是就对HelloServlet进行实例化;

实例化只进行一次,后续再收到/hello,就不必再重复实例化了,直接复用之前的HelloServlet实例即可;

3. 可以重写init方法,插入一些我们自己的初始化的逻辑:

运行以下代码:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        // 重写init方法
        System.out.println("init");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        把数据显示在服务器控制台
        System.out.println("Hello world");
//        把数据写回浏览器
        resp.getWriter().write("Hello world");
    }
}

(1)运行程序,仅启动服务器时,在控制台并未打印init:

原因:只有当请求关联到hello路径时才会打印;

(2)在浏览器中打开页面,即触发了请求,此时服务器的日志上就会调用init进行实例化:

【JavaEE】_Servlet API_第2张图片

(3)多次刷新页面,即多次发送请求:

【JavaEE】_Servlet API_第3张图片

doGet方法多次被调用,而init方法仅被调用一次;

4. 也可以通过修改web.xml配置让tomcat启动时立即实例化该servlet;

5. servlet是服务器上运行的代码,只要服务器不重新启动,init就不会再执行

1.2 destroy方法

1. 可以使用destroy方法进行一些清理工作

2. 只要服务器在运行,都有可能再使用,服务器终止时就不可再使用了;

3. 重写destroy方法:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        // 重写init方法
        System.out.println("init");
    }

    @Override
    public void destroy() {
//        重写destroy方法
        System.out.println("destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        把数据显示在服务器控制台
        System.out.println("Hello world");
//        把数据写回浏览器
        resp.getWriter().write("Hello world");
    }
}

启动服务器并刷新页面后,点击停止后,可见服务器日志调用了destroy方法:

【JavaEE】_Servlet API_第4张图片

注:但此处的destroy方法是否能被执行到并不确定:

第一种情况:如果是使用smart tomcat的停止按钮终止程序,这个操作本质上是通过tomcat的8005端口来主动停止,可以触发destroy方法;

第二种情况:如果是直接杀进程,此时可能就来不及执行destroy方法;

故而并不推荐使用destroy方法;

1.3 service方法

当收到路径匹配的HTTP请求就会触发service方法

比如doGet方法就是在service方法中调用的:

父类HttpServlet有一个service方法,其内部就会调用doGet方法;

1.4 Servlet的生命周期

开始时,执行 init;

每次收到请求,执行 service;

销毁前执行 destroy;

【JavaEE】_Servlet API_第5张图片

注:一个servlet程序包含很多个servlet,某个servlet的生死,不影响整个sevlet程序;

1.5 代码示例

在java目录下再创建一个类:MethodServlet;

其内容如下:

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

直接在浏览器输入地址访问,只能发送GET请求,其他请求类型可以通过ajax或postman进行构造。

1.5.1 使用postman构造请求

【JavaEE】_Servlet API_第6张图片

基于一次浏览器地址访问后(即已构造一个GET请求),使用postman依次构造:GET,POST,PUT,DELETE请求并发送,再查看服务器日志:

【JavaEE】_Servlet API_第7张图片

1.5.2 使用ajax构造请求

1. 在IDEA中创建.html文件:

【JavaEE】_Servlet API_第8张图片

tomcat要求:.html文件设置在webapp目录下,与WEB-INF同级

可以使用vscode进行编写:

【JavaEE】_Servlet API_第9张图片

用vscode打开即可;

2. 编写代码:




    
    
    
    Document


    
    

在浏览器输入地址:

【JavaEE】_Servlet API_第10张图片

按f12进入chrome的console标签页:

【JavaEE】_Servlet API_第11张图片

同样在服务器日志也可见:

【JavaEE】_Servlet API_第12张图片

3. 依次修改请求类型为post,put,delete,再运行,即可得到不同的结果:

【JavaEE】_Servlet API_第13张图片

注:使用ajax构造HTTP请求的注意点:

(1)html文件的位置:webapp下,与WEB-INF同级;

(2)在html文件中使用ajax构造HTTP请求时的url可以写为相对路径,该相对路径的基准路径就是当前html文件的路径,在此例中为:127.0.0.1:8080/hello_servlet;

也可以写为绝对路径:(浏览器要求:以 / 开头的为绝对路径

        $.ajax({
            type:'get',
            url:'/hello_servlet/method',
            success:function(body, status){
                console.log(body);
            }
        })

(3)注意@WebServlet注解的路径必须 / 开头,此处的含义不是绝对路径,而是servlet的要求;

2. HttpServletRequest

HttpServletRequest表示一个HTTP请求,这个对象是tomcat自动构造的,

tomcat其实会自动监听端口,接受连接,读取请求,解析请求,构造请求对象等一系列工作;

2.1 核心方法

方法 描述
String getProtocal() 返回请求协议的名称和版本

String getMethod()

返回请求的HTTP方法的名称,如GET、POST或PUT
String getQequestURI() 从协议名称直到HTTP请求的第一行的查询字符串中,返回该请求的URL的一部分
String getContexPath() 返回指示请求上下文的请求URI部分
String getQueryString() 返回包含在路径后的请求URL中的查询字符串(?后的参数)
Enumeration getParameterNames() 返回一个String对象的枚举,包含在该请求中包含的参数的名称
String getParameter(String name) 以字符串形式返回请求参数的值,或者如果参数不存在则返回null
String[] getParameterValues(String name) 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回null
Enumeration getHeaderNames() 返回一个枚举,包含在该请求中包含的所有头名
String getHeader(String name) 以字符串形式返回指定的请求头的值
String getCharacter Encoding() 返回请求主题中使用的字符编码的名称
String getContentType() 返回请求主题的MIME类型,如果不知道类型则返回null
int getContentLength() 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回-1
InputStream getInputStream() 用于读取请求的body内容,返回一个InputStream对象

 注:(1)query string是键值对结构,此处可以通过getParameter方法来根据key获取到value

(2)InputStream就是输入流对象,进行read操作即可把body数据读取出来;

2.2 代码示例1:打印请求信息

基于hello_servlet项目,创建一个ShowRequestServlet.java文件,其内容如下:

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;
import java.util.Enumeration;

@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应的content-type,即表明body里的数据格式是何种类型
        resp.setContentType("text/html");
        // 创建一个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("
"); stringBuilder.append("
"); // 获取到header中的所有键值对 Enumeration headerNames = req.getHeaderNames(); while(headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); stringBuilder.append(headerName+" : "+req.getHeader(headerName)); stringBuilder.append("
"); } resp.getWriter().write(stringBuilder.toString()); } }

运行后根据url打开页面:

【JavaEE】_Servlet API_第14张图片

注:(1)此处实现html页面的换行操作不能使用\n,而使用
标签
,同时设置响应的content-type为text/html,告诉浏览器,响应的body里的数据格式是何种类型;

(2)按照没有添加query string时,默认响应为null,比如增加query string 为a=10&b=20,响应的query string便为a=10&b=20;

3. 前端给后端传参

3.1 通过GET的query string

 (1)前端:test.html:




    
    
    
    Document


    

(2)后端:GetParameterServlet.java:

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("/getParameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 预期浏览器会发一个形如 /getParameter?studentId=001&classId=2001;
        // 借助req的getParameter方法就能拿到query string 中的键值对内容了
        // getParameter得到的是一个String类型的值
        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        resp.setContentType("text/html");
        resp.getWriter().write("studentId = "+studentId +" classId = "+classId);
    }
}

 运行程序后,根据url打开浏览器页面如下:

【JavaEE】_Servlet API_第15张图片

注:(1)输入在url后的查询字符串键值对会被Tomcat处理成形如Map这样的结构,后续就可以随时通过key来获取到value了;

(2)如果key在query string中不存在,则返回值为空;

3.2 通过POST,借助form表单

form表单也是键值对形式组织数据的,只是这部分内容在body中;

对于前端是form表单这样格式的数据,后端还是使用paraMeter来获取;

(1)前端:test.html:




    
    
    
    Document


    

 (2)后端:PostParameterSrvlet.java:

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("/postParameter")
public class PostParameterServlet extends HttpServlet {
    @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");
        resp.getWriter().write("studentId = "+studentId+" classId = "+classId);
    }
}

运行程序后根据url打开浏览器页面:

【JavaEE】_Servlet API_第16张图片

提交后,页面如下:

【JavaEE】_Servlet API_第17张图片

 注:(1)使用query string和使用form表单的后端代码格式基本相同,都是通过getParameter根据key获取value,只是使用form表单构造的键值对不在query string中,而是在body中;

(2)getParameter方法不仅能获取到请求中query string中的键值对,还可以获取到form表单构造的body中的键值对;

(3)以form表单形式构造并发送的请求一定会触发页面跳转;

(4).html及请求、浏览器页面的对应关系图:

【JavaEE】_Servlet API_第18张图片

(5)前端后端交互的过程:

图1:

【JavaEE】_Servlet API_第19张图片

图2:

【JavaEE】_Servlet API_第20张图片

3.3 通过POST,使用json格式构造body

json也是键值对结构的数据格式,可以把body按照该形式组织;

在前端,可以通过ajax方式构造该内容,也可以使用postman;

(1)postman构造请求:

【JavaEE】_Servlet API_第21张图片

 (2)后端:PostParameter2Sevlet.java

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;
import java.io.InputStream;

@WebServlet("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法处理 body 为 json 格式的数据
        // 直接把 req 对象中的 body 完整地读取出来

        // 获取请求的body长度并构造数组
        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.setContentType("text/html");
        resp.getWriter().write(body);
    }
}

使用fiddler可以查看到构造的json请求:

【JavaEE】_Servlet API_第22张图片

当请求到达tomcat后,tomcat就会将其解析为req对象;

在servlet代码中,req.getInputStrea,,读取body内容,又把body的内容构造成一个响应结果返回给浏览器(此例中为postman),在post页面中就有响应结果:

【JavaEE】_Servlet API_第23张图片

注:(1)使用form表单构造请求传参与json格式请求传参的代码执行流程是类似的,只是传输数据的格式不同,

form表单的格式形如:

json格式形如:

【JavaEE】_Servlet API_第24张图片

但是当前通过json传递数据,服务器只是将整个body都读出来,没有按照键值对的方式处理,还不能根据key获取到value,可以使用第三方库来解析json的body,如jackson,gson,fastjson,由于spring mvc内置了jackson这个库,故而天然就支持处理jackson的数据。

故而,以引入jackson库为例:

通过maven引入第三方库:

【JavaEE】_Servlet API_第25张图片

基于jackson库,修改代码使得获取到请求body内部的键值对:

 class Student{
    public int studentId;
    public int classId;
}
@WebServlet("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 使用jackson的核心对象:
  ObjectMapper objectMapper = new ObjectMapper();

// readValue就是把一个json格式的字符串转为java对象:
  Student student = objectMapper.readValue(req.getInputStream(), Student.class);

    }
}

注意:(1)objectMapper对象的readValue方法的作用:

① 将json格式的字符串读取出来;

② 根据第二个参数类对象创建Student实例,第二个参数是一个类对象

③ 解析json格式的字符串,处理成map键值对结构;

④ 遍历所有键值对,看键的名字和Student实例的哪个属性名匹配,就把对应的value设置到该属性中;

⑤ 返回该Student实例;

(2)readValue方法用于将json字符串转成java对象,writeValue方法用于把一个java对象转成json格式字符串

(3)在上文代码中,将Student类内的属性设置为public修饰,这在Java中并不常见。但是如果要把一个类作为jackson返回的对象,就需要让jackson能够看到类中的属性,可以采取的方法有:

① 把属性设置为public;② 给该属性提供public修饰的getter与setter方法;

4. 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中写入二进制格式数据

(1)一个HTTP响应中报头的key是可以存在多个重复的

(2)可以通过setCharacterEncoding指定响应的编码格式:

浏览器默认不知道程序员的页面编码方式,会采取猜测的方式使用字符集进行解析,比如在getParameterServlet.java中将写回浏览器的响应格式写为:

resp.setContentType("text/html");
resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

 此时根据路径打开浏览器页面,就会出现乱码: 

【JavaEE】_Servlet API_第26张图片

为了避免这种情况,我们需要在写回响应前,显式指定响应的编码格式字符集:

        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

刷新浏览器页面,有: 

【JavaEE】_Servlet API_第27张图片

(3)也可以把字符集和ContentType一起设置:

        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

(4)void sendRedirect用于构造重定向响应,3xxx的状态码就会跳转到另外一个页面:

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 RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("https://www.sogou.com");
    }
}

运行代码后,根据路径打开浏览器页面,即可跳转至搜狗主页:

【JavaEE】_Servlet API_第28张图片

前后端交互逻辑如下:

 【JavaEE】_Servlet API_第29张图片

实现重定向,尤其是返回错误页面有多种方式:

【JavaEE】_Servlet API_第30张图片

你可能感兴趣的:(JavaEE,java-ee,servlet,java,vscode,postman,intellij-idea)