作者
:学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方法
- 2、servlet生命周期
- 3、服务器接收请求三种方式
- 3.1、地址栏输入URL发送get请求
- 3.2、通过postman发送http请求
- 3.3、使用ajax发送http请求
- 3.4、使用JavaScript构造body为json格式的post请求
- 二、HttpServletRequest
- 1、HttpServletRequest方法
- 2、方法的使用
- 3、前后端交互方式(重点)
- 3.1、GET请求,query string传参
- 3.2、POST请求,使用form表单
- 3.3、POST请求,body使用json格式(重点)
- 3.4、小结:前后端数据交互
- 三、HttpServletResponse
一、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的键值对中的 键,以枚举的方式返回。
StringgetParameter(String name)
方法可以获取地址栏输入地址时, get请求的 query String的键值对中的 值,以字符串的方式返回。
Enumeration getHeaderNames() 方法和 StringgetHeader(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 中写入二进制格式数据. |