【Java EE】-Servlet(二) Servlet API 详解

作者:学Java的冬瓜
博客主页:☀冬瓜的主页🌙
专栏【JavaEE】
分享:寂寞会发慌,孤独是饱满的。——史铁生《命若琴弦》

主要内容:HttpServlet的方法,init,service,destroy,doGet/doPost/doPut/doDelete…,servlet的生命周期、1>使用地址栏输入URL发送get请求,2>使用postman发送http请求,3>使用ajax发送http请求,4>使用JavaScript发送body为json格式的post请求。HttpServletRequest的相关方法的使用,前后端交互的方式,1>get请求,query string 传参,2> post请求,使用form表单传参,3> post请求,body使用json格式传参,req.getParameter(String name),req.getInputStream()。

在这里插入图片描述

一、HttpServlet

1、HtteServlet方法

HttpServlet方法调用时机
init在HttpServlet被实例化之后被调用一次
destroy在HttpServlet不再使用的时候调用一次,具体的不使用时机下面会讨论
service收到Http请求的时候调用
doGet收到 Get请求的时候,由service方法调用
doPost收到Post请求的时候,由service调用
doPut / doDelete / doOptions…收到对应方法的时候,由service方法对应调用
  • init方法使用类似 饿汉模式的方法,是在首次请求时才执行,而不是服务器启动时执行,且只执行这一次。
  • destroy方法执行时机:是在servlet生命周期结束时调用 destroy方法。有的版本直接关闭服务器是来不及执行 destroy方法的,是直接杀进程,不能执行到destroy方法;而有些版本则是使用管理端口8005再做一些事情才关闭服务器,这种就可以执行到destroy方法。由于destroy执行的时机不好把握,所以不推荐使用destroy方法。
  • service,每次请求都会调用service方法,再通过service方法判断是 get/post/…方法,然后调用请求对应的方法。

2、servlet生命周期

1> 首次请求时,创建servlet,执行init方法。在第一次访问时,servlet容器中会创建对应的servlet类的实例,每个servlet类只有一个实例,但一个servlet容器可以包含多个servlet实例对应多个servlet类。
2> 每次收到请求,都会执行 service方法,通过service方法去调用请求对应的方法。
3> 在servlet销毁前,调用destroy方法。

注意:一个servlet程序可以包含多个servlet,单个的servlet的生死并不影响整个servlet程序。

3、服务器接收请求三种方式

以下三种方式复用这同一份服务器端的servlet代码:

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 HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Get");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Post");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Delete");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Put");
    }

}

3.1、地址栏输入URL发送get请求

  • 注意:地址栏输入URL,按回车,只能发送get请求。
    在这里插入图片描述

3.2、通过postman发送http请求

  • 注意:使用postman发送请求,可以发送get、post、put、delete等各种http请求。
    在这里插入图片描述
    在这里插入图片描述

3.3、使用ajax发送http请求

前端不传数据给后端:此时Content-Length=0,Content-Type未指定

  • 注意:使用ajax发送请求,可以发送get、post、put、delete等各种http请求。

另外使用一个html文件来写ajax,从而构造各种请求,发送给后端。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>构造ajax请求访问服务器</title>
</head>
<body>
    <!--使用这个页面来构造ajax请求-->
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        $.ajax({
            type: 'post',
            url: 'method', //使用相对路径
            success: function(body){
                console.log(body) //此处的body即是HTTP响应报文里的body
            }
        })
    </script>
</body>
</html>
  • 交互过程如下图所示:通过地址栏输入,然后服务器返回304,浏览器就访问本地的 test.html页面,在这个html中,type表示请求的方法,url则要访问的是服务器端资源的地址。服务器这里,在Smart Tomcat这里可以看Context Path;服务器端使用了注解表示servlet path。

在这里插入图片描述

  • 从下图(使用fiddler抓包)可以看出,启动服务器后,在地址栏输入创建的 test.html的路径,浏览器发送一个get请求,服务器返回一个304提示客户端可以直接使用客户端本地缓存的资源,就是test.html,所以浏览器就执行 test.html。然后使用ajax构造请求,发送给服务器,服务器执行 HelloServlet的相应方法,服务器返回200表示访问成功。
  • 客户端第二次发送请求,即ajax构造完请求发送http给服务器时,浏览器的地址栏上的地址就变为了:127.0.0.1:8080/servletHello2/method
    在这里插入图片描述
    上述过程简化后如下图:
    在这里插入图片描述

3.4、使用JavaScript构造body为json格式的post请求

进阶:前端需要使用json格式传数据给后端,后端对请求进行处理:此时Content-Length != 0,Content-Type指定为 application/json 格式

客户端中,使用 testJS.html来构造body为json格式的post请求,在JavaScript中使用XMLHttpRequest对象来发送http请求。(使用ajax构造body为json格式的post请求后面再讲)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JavaScript构造body为json格式的post请求</title>
</head>
<body>
    <script>
        // 1.构造body对象
        let body = {
            username: "zhangsan",
            password: "123"
        }
        
        // 2.将body对象转化为json字符串
        let jsonBody = JSON.stringify(body);

        // 3.获取XMLHttpRequest对象,在ajax中使用XMLHttpRequest对象来发送http请求
        // 然后设置请求头的方式,设置请求的body为 json格式。
        let xhr = new XMLHttpRequest();
        xhr.open('post', 'getParameter2', true); //设置请求方法和请求地址
        xhr.setRequestHeader('Content-type','application/json; charset=utf-8'); //设置数据格式

        // 4.设置回调函数
        xhr.onreadystatechange = function(){
            if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200){
                // 请求成功,设置响应的打印到浏览器的数据
                let response = JSON.parse(xhr.responseText);
                console.log(response);
            }
        };

        // 5.发送请求
        let date = xhr.send(jsonBody);

    </script>
</body>
</html>

在后端服务器中,使用GetParameterServlet2.java来处理前端使用JavaScript构造body为json格式的post请求。
注意:由于 JavaScript构造的body为json格式的post请求可能格式上与后端的jackson不匹配,所以可能会出问题。我这里就直接使用InputStream把post的整个body取出来,然后把他写回浏览器中。

import com.fasterxml.jackson.databind.ObjectMapper;

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("/getParameter2")
public class GetParameterServlet2 extends HttpServlet {
    //获取post请求,且body的类型为json的数据
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1.获取postman构造的post请求报文的header中描述 body长度的字段Content-Length,根据字段创建缓冲数组
        int length = req.getContentLength();
        byte[] buffer = new byte[length];

        // 2.获取InputStream对象,并且将body的内容读入buffer中。
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);

        // 3.把字符数组转换成字符串,在服务器控制台打印字符串且也在浏览器上(postman)打印
        String body = new String(buffer,0,length,"utf-8");
        System.out.println("body = " + body);
        resp.getWriter().write(body);
    }
}

post请求抓包结果如下:
在这里插入图片描述

响应发回testJS.html回调函数这里时,在客户端的控制台打印body。因为地址栏上的地址没有变成 127.0.0.1:8080/servlet/getParameter2,所以使用XMLHttpRequest对象send发送请求,是请求转发。
在这里插入图片描述

二、HttpServletRequest

1、HttpServletRequest方法

HttpServletRequest方法描述
String getProtocol()返回协议的名称和版本
String getMethod()返回 http方法 请求的名称
String getRequestURI()返回URL的一部分,即从从URL开头到查询字符串之前的部分
String getContextPath()返回URL的一部分,即从开头到第二级路径servlet Path之前的部分,也就是URL从开头到项目根目录的部分
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串
Enumeration getParameterNames()获取queryString里(get请求地址栏上)的键,返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。
String getParameter(String name)queryString(get请求地址栏上)根据键获取值,以字符串形式返回请求参数的值,或者如果参数不存在则返回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 对象。
  • Enumeration getParameterNames()方法可以获取地址栏输入地址时,get请求的 queryString的键值对中的 键,以枚举的方式返回。
    String getParameter(String name)方法可以获取地址栏输入地址时, get请求的 query String的键值对中的 值,以字符串的方式返回。
    Enumeration getHeaderNames() 方法和 String getHeader(String name) 方法与上述两个方法相似。
  • Header里包含 字符编码,请求正文的类型,请求正文的长度,这里用单独的三个方法封装这些属性,从而不需要先获取header,再去获取这些属性。
  • InputStream getInputStream(),获取输入流对象,通过这个输入流对象,就可以把body的内容读出来。

2、方法的使用

1> 列表中前五个方法的使用:

@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 需要设置ContextType为text/html,下面的代码才有效
        resp.setContentType("text/html; charset=utf-8");
        // 使用StringBuilder,把内容拼接在一起,然后写回resp的body中
        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());
		
		// 把内容写入resp
        resp.getWriter().write(stringBuilder.toString());
    }
}

在这里插入图片描述
2> 获取header中的键值对的键和值

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 ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 需要设置ContextType为text/html,下面的html代码才有效
        resp.setContentType("text/html; charset=utf-8");
        // 使用StringBuilder,把内容拼接在一起,然后写回resp的body中
        StringBuilder stringBuilder = new StringBuilder();

        // 获取header里面的键headerName(使用getHeaderNames())和值headerValue(使用getHeader(键))
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()){
            // 获取枚举中的header每个键值对的的键
            String headerName = headerNames.nextElement();
            // 获取枚举中的header每个键值对的的值
            String headerValue = req.getHeader(headerName);
            stringBuilder.append(headerName+":"+headerValue);
            stringBuilder.append("<br>");
        }

        resp.getWriter().write(stringBuilder.toString());
    }
}

在这里插入图片描述

  • 使用HttpServletRequest类提供的方法写出来的结果和 fiddler抓包的结果是一样的。我们在服务器端设置了响应报文类型为:text/html;charset=utf-8,可以在fiddler的响应报文中看到:
    在这里插入图片描述

3、前后端交互方式(重点)

3.1、GET请求,query string传参

  • 直接在地址栏输入URL + query string(协议版本号浏览器会自动为我们加上),那么使用req.getParameter(String name) 就可以获取到query string中 键name对应的值。
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?user=lisi&age=20
        String user = req.getParameter("user");
        String age = req.getParameter("age");

        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("user:"+user+" age:"+age);
    }
}

在这里插入图片描述

3.2、POST请求,使用form表单

  • 使用test.html中的form表单,构造post请求发送给服务器并定位到getParameter注解,服务器收到请求后调用注解getParameter对应的这个servlet的post方法。
  • 注意:form表单中的方法为get时,在input标签输入的内容会自动出现在地址栏上作为query string;方法为post时,在input标签输入的内容会填充到http请求报文的body中。这两种情况,服务器都可以使用req.getParameter(String name) 获取到键对应的值。

test.html中使用form表单构造http请求,get/post/put…

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>前端给后端传参方式</title>
</head>
<body>
	<!--这里action属性使用了相对路径-->
    <form action="getParameter" method="get">
        <!-- name属性的值决定了后端取前端传的值时使用什么取出参数,如:getParameter("此处name的值")-->
        <span>username:</span><input type="text" name="username">
        <br>
        <span>password:</span><input type="password" name="password">
        <br>
        <input type="submit" value="submit">
    </form>
</body>
</html>

后端服务器收到form表单的请求后,根据action属性的url定位到GetParameterServlet.java,然后执行对应的get/post/put…方法,然后使用req.getParameter获取到前端发送过来的数据。

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 doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 地址栏路径:/getParameter
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("username:"+username+" password:"+password);
    }
}

在这里插入图片描述

3.3、POST请求,body使用json格式(重点)

第一种方式:直接获取到body:

  • 可以使用postman来构造post请求的body为 json格式,也可以使用ajax构造post请求的body为 json格式。
  • 注意:客户端发送json格式的数据,服务端不能再使用req.getParameter(String name)取其中的键对应的值;而是使用req.getInputStream(),读取长度取决于请求报文中header里的 Content-Length多大。

postman作为浏览器发送post请求,经过服务器处理,在服务器控制台打印这个post请求的body,且原样返回了post请求中body的json格式的数据,打印到postman(浏览器)上。如下图:

在这里插入图片描述

在这里插入图片描述

代码如下:

客户端利用postman发送body为json格式的post请求数据,服务器端处理post请求的代码 :GetParameterServlet2.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("/getParameter2")
public class GetParameterServlet2 extends HttpServlet {
    //获取post请求,且body的类型为json的数据
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1.获取postman构造的post请求报文的header中描述 body长度的字段Content-Length,根据长度字段创建缓冲数组
        int length = req.getContentLength();
        byte[] buffer = new byte[length];

        // 2.获取InputStream对象,并且将body的内容读入buffer中。
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);

        // 3.把字符数组转换成字符串,在服务器控制台打印字符串且也在浏览器上(postman)打印
        String body = new String(buffer,0,length,"utf-8");
        System.out.println("body = " + body);
        resp.getWriter().write(body);
    }
}

第二种方式:使用jackson将body键值对封装成java对象

  • 另外,发送post请求的body使用json格式传输数据,这种方法是最新流行的前后端交互的 方式,越新的项目可能使用 json格式的方式就更多。
  • post请求的body为json格式这种方式,不能和form表单一样,直接使用getParameter(String name) 使用key获取value。需要引入第三方库——可以使用jackson,引入第三方库后,服务器就可以解析json格式的数据,把body键值对分到Java对象属性中 => 引入方法:直接通过maven来引入第三方库,找到文件向pox.xml中引入即可,如果报红,就刷新一下maven。
    【点击进入Maven相关文件下载地址】

使用postman构造http的post请求,然后这个post请求的body使用json格式,服务器需要对json格式的 post请求的body进行处理。

在这里插入图片描述
代码如下:

使用jackson把json格式的键值对封装成Java对象,从而对Java对象的属性可以做访问操作。

注意:

  • 应该将创建的User类的成员变量进行封装,提供get和set方法,这里为了方便没有封装,而是直接将成员变量名设置为public。
  • body中的键值对个数和Java对象成员变量不匹配的影响:
    1> 如果post请求中的body的键值对个数 小于等于 服务器中的Java对象成员变量个数,但是内容是匹配的(键和成员变量名),那么post中未传键值对对应 的Java对象的成员变量值为null;
    2> 如果post请求键值对个数等于 服务器中Java对象成员变量的个数,但是post请求的body中键名和 后端中的java对象的成员变量名不同,那就是500错误,或者post请求的键值对个数 大于 服务器端的Java对象的成员变量个数,也是500错误。
import com.fasterxml.jackson.databind.ObjectMapper;

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;

// 使用jackson时,封装一个类,用于对应post请求的body的 json类型的键值对数据,即使用jackson把json格式的键值对封装成Java对象。
class User {
    public String username;
    public String password;
}

@WebServlet("/getParameter2")
public class GetParameterServlet2 extends HttpServlet {
    //获取post请求,且body的类型为json的数据
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 使用jackson涉及到的核心对象:ObjectMapper
        ObjectMapper objectMapper = new ObjectMapper();
        // 把传入的数据 body的json类型键值对数据封装成一个类,.readValue()方法就是读取post请求中的数据
        // 然后根据第二个参数的属性依次填入对象,得到一个Java对象。得到Java对象后,属性值就随便取了。
        User user = (User) objectMapper.readValue(req.getInputStream(), User.class);//需要强制类型转换一下,因为返回的是一个object对象。

        // 把获取到的内容改变格式 到浏览器输出
        resp.getWriter().write("username=" + user.username + " password=" + user.password);
    }
}

3.4、小结:前后端数据交互

  • req.getParameter(String name) 可以获取 非form表单和form表单 get请求的 query string;也可以获取 form表单中post请求 的body中的键值对根据键获得值。
    req.getInputStream() 可以读取body中的内容,但是只能完整读取整个body,想要获取json格式的请求的body的键值对,可以引入第三方库,可以使用jackson
  • 对jackson的readValue方法的理解:
    在这里插入图片描述
    在这里插入图片描述

三、HttpServletResponse

  • HttpServletResponse的方法相对来说,比较简单,这里直接列出了方法和描述,不再过多赘述,只做简单介绍。
  • sendRedirect(String location) 方法是重定向,第一次请求时,服务器会给浏览器返回一个临时重定向状态码302,然后浏览器重新发送请求 访问 location。
    如果不用sendRedirect(String location)方法,也可以使用setStatus(int sc)方法设置响应的 header的 status为302,setHeader(String name,String value)方法 设置location为目标访问地址。
  • 关于setStatus(int sc)方法,使用setStatus(404),则浏览器显示一个默认的404页面;加上resp.getWriter().write("<h1>404<h1>"),则显示我们后端写的这个错误页面;使用resp.setError(404),则显示通常访问是404的页面,这个页面和浏览器有关。
    在这里插入图片描述
HttpServletResponse方法描述
void setStatus(int sc)为该响应设置状态码。
void setHeader(String name,String value)在响应的header中添加键值对,如果 name已经存在,则覆盖旧的值
void addHeader(Stringname, String value)在响应的header末尾添加键值对,添加一个带有给定的名称和值的 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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学Java的冬瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值