哥几个来学 Servlet 啦 ~~
这个是 Servlet(上篇)的链接,
(2条消息) Servlet (上篇)_小枫 ~的博客-CSDN博客https://blog.csdn.net/m0_64247824/article/details/131229873主要讲了 Servlet的定义、Servlet的部署方式、Servlet 的运行原理、Servlet 的生命周期 ~~
本篇主要讲解 HttpServlet 类 ~~
目录
一、HttpServlet 类
核心方法:
① 代码示栗:处理 GET 请求 和 POST 请求
二、Response 乱码问题
三、HttpServletRequest
① 代码示栗:打印请求信息
② 代码示栗:通过 query string 获取 GET 请求中的参数
③ 代码示栗:采用 form 表单获取 POST 请求中的参数
Request 乱码问题
④ 代码示栗:JSON 格式 获取 POST 请求中的参数
四、HttpServletResponse
① 代码示栗:设置状态码
② 代码示栗:自动刷新
③ 代码示栗:重定向
HttpServlet 是继承于 GenericServlet 抽象类而来的。
而这个 GenericServlet 抽象类又实现了 Servlet、ServletConfig、Serializable 接口。
因此, HttpServlet类 中实现了 Servlet 里的方法(比如我们上一篇所说的 init() 、service()、destroy() 等方法)
我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet,并重写其中的某些方法。
方法名称 | 调用时机 |
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service() 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service() 方法调用) |
doPut/doDelete/doOptions/... | 收到其他请求的时候调用(由 service() 方法调用) |
我们实际开发的时候主要重写 doXXX 方法,很少会重写 init / destory / service 。
因为 service()方法 会解析HttpServletRequest中的方法参数,并调用以下方法之一:doGet() 、doPost() 、doHead() 、doPut() 、doTrace() 、doOptions() 和doDelete() 。
service()方法 源码:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
使用 HttpServlet 抽象类时,还需要借助分别代表Servlet请求和Servlet响应的HttpServletRequest和HttpServletResponse对象。
我们以 doGet()方法 来举例:
补全完之后,要把代码中的 “super.doGet(req, resp);” 删除!!
可以看见里面要传两个参数:1. HttpServletRequest 对象 2. HttpServletResponse 对象。
HttpServletRequest 为 服务器 从 客户端 收到的 请求
HttpServletResponse 为 服务器 要 发送给 客户端 而构造出来的 响应
创建 ServletMethod.java, 创建 doGet 方法 和 doPost 方法
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 ServletMethod extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这行代码相当于用于往 响应的 body 中写入文本格式数据
resp.getWriter().write("Get response");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这行代码相当于用于往 响应的 body 中写入文本格式数据
resp.getWriter().write("Post response");
}
}
同时,我们创建 testMethod.html, 放到 webapp 目录中,形如:
注意:这个文件与 WEB-INF 同级
然后在 testMethod.html 里写入:
TestMethod
重新部署程序,在浏览器中通过
TestMethodhttp://localhost:8080/HelloServlet/testMethod.html访问,可以看到:
用 Fiddler 抓包可以看到它们两个请求的响应:
GET 请求的响应:
POST 请求的响应
如果我们在响应代码中写入中文,例如:
此时在浏览器访问的时候,会看到 "乱码" 的情况:
关于 "乱码":
中文的编码方式有很多种,其中最常见的就是 utf-8 。
如果没有显式的指定编码方式,则浏览器不能正确识别编码,就会出现乱码的情况。
可以在代码中,通过 resp.setContentType("text/html; charset=utf-8"); 显式的指定编码方式:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
//resp 就是要返回给客户端的响应
resp.setContentType("text/html;charset=utf-8");
// 这行代码相当于用于往 响应的 body 中写入文本格式数据
resp.getWriter().write("Get 响应");
}
此时,就不会出现乱码现象了:
通过抓包也可以看出 Content-Type 被设置成了 text/html;charset=utf-8
Request 乱码问题会在下面的“③ 代码示栗:采用 form 表单获取 POST 请求中的参数” 中提及。
HttpServletRequest 可以理解为 服务器 从 客户端 收到的请求。
一个 Http 请求中里包含什么内容,那么 这个 HttpServletRequest 就有什么参数。比如:方法、URL、版本号、header、body ...
核心方法:
方法 | 描述 |
String getProtocol() | 返回请求协议的名称和版本。 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如:GET、POST 或 PUT。 |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分。 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。 |
Enumeration getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名 称。 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回 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 对象。 |
通过这些方法可以获取到一个请求中的各个方面的信息。
注意:请求对象是服务器收到的内容,不应该修改。因此上面的方法也都只是 "读" 方法,而不是 "写" 方法。
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 {
//这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
//resp 就是要返回给客户端的响应
resp.setContentType("text/html;charset=utf-8");
// 先创建一个 StringBuilder 类,让它来接收 客户端的浏览器 发过来的请求的信息
StringBuilder respBody = new StringBuilder();
//返回请求协议的名称和版本
respBody.append(req.getProtocol());
respBody.append("
");//由于返回的是 html 格式的代码,因此换行不能用 "\n",而是用 "
"
//返回请求的 HTTP 方法的名称
respBody.append(req.getMethod());
respBody.append("
");
//从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
respBody.append(req.getRequestURI());
respBody.append("
");
//返回指示请求上下文的请求 URI 部分
respBody.append(req.getContextPath());
respBody.append("
");
//返回包含在路径后的请求 URL 中的查询字符串
respBody.append(req.getQueryString());
respBody.append("
");
//获取header
respBody.append("headers:
");
//返回一个枚举,包含在该请求中包含的所有的头名
Enumeration headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
//一个一个获取,再一个一个拼装
String headerName = headerNames.nextElement();
respBody.append(headerName + " ");
//通过 header名 获取header
respBody.append(req.getHeader(headerName));
respBody.append("
");
}
//这行代码相当于用于往 响应的 body 中写入文本格式数据
//注意:write()里的参数是String类型
resp.getWriter().write(respBody.toString());
}
}
使用 Smart Tomcat 部署,访问:localhost:8080/HelloServlet/showRequest
Get 请求中的参数一般都是通过 query string 传递给服务器的,形如:
https://v.bitedu.vip/personInf/student?userId=1111&classId=100
此时浏览器通过 query string 给服务器传递了两个参数:userId 和 classId,值分别是 1111 和 100。
在服务器就可以通过 getParameter 来获取到参数的值。
创建 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 GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
//resp 就是要返回给客户端的响应
resp.setContentType("text/html;charset=utf-8");
//获取 名为userId 的参数
String userId = req.getParameter("userId");
//获取 名为classId 的参数
String classId = req.getParameter("classId");
//这行代码相当于用于往 响应的 body 中写入文本格式数据
resp.getWriter().write("userId = " + userId + "
classId = " + classId);
}
}
重新部署程序,在浏览器中通过 localhost:8080/HelloServlet/getParameter 访问,可以看到:
如果 url 后面不写参数,那么参数的值为 null
如果 url 后面写了参数,那么就可以获取到值了
POST 请求的参数一般通过 body 传递给服务器,body 中的数据格式有很多种。如果是采用 form 表单的形式,仍然可以通过 getParameter 获取参数的值。
创建类 PostParameter
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 PostParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/http;charset=utf-8");
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId = " + userId + " classId = " + classId);
}
}
创建 testPost.html, 放到 webapp 目录中
TestPost
重新部署程序,通过 URL http://127.0.0.1:8080/ServletHelloWorld/testPost.html 访问
输入参数,点击提交之后
可以看到跳转到了新的页面,并显示出了刚刚传入的数据
那如果输入的参数里含有中文呢?
那么就会出现乱码
用 Fiddler 抓包 发现是一串 看不懂的字符
解决post提交方式的乱码:
request.setCharacterEncoding("UTF-8");
结果:不再乱码
而 解决get提交的方式的乱码:
parameter = newString(parameter.getbytes("iso8859-1"),"utf-8");
创建 PostParameterJson 类
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("/postParameterJson")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//告诉 客户端浏览器 body 的格式是 json 格式的
resp.setContentType("application/json;charset=utf-8");
//处理请求里的body
String body = readBody(req);
//往 响应的 body 中写入文本格式数据
resp.getWriter().write(body);
//打印从请求中得到的Body
System.out.println(body);
}
private String readBody(HttpServletRequest req) throws IOException {
//获取请求正文的长度
int contentLength = req.getContentLength();
//创建一个byte数组来接收请求的body
//为什么要用 byte 呢? 因为接收的时候是按照字节流接收的
byte[] body = new byte[contentLength];
//创建一个输入流对象
InputStream inputStream = req.getInputStream();
//将请求的body写入到数组中
inputStream.read(body);
//将 byte 数组转化为 String,并设置为 utf-8字符集
return new String(body, "utf-8");
}
}
创建 testPostJson.html
testPostJson
在浏览器中通过 testPostJsonhttp://localhost:8080/HelloServlet/testPostJson.htmltestPostJson 访问, 可以看到
点击按钮,则浏览器就会给服务器发送一个 POST 请求,body 中带有 JSON 格式(使用 Fiddler 抓包):
服务器收到这个结果之后,又把数据返回了回去(打印到控制台上):
注意:到目前为止,服务器拿到的 JSON 数据仍然是一个整体的 String 类型,如果要想获取到 userId 和 classId 的具体值,还需要搭配 JSON 库进一步解析。
我们需要引入 Jackson 这个库, 进行 JSON 解析:
(1) 在中央仓库中搜索 Jackson,选择 JackSon Databind
(2) 选择 2.12.3 版本
(3) 把中央仓库中的依赖配置添加到 pom.xml 中, 形如
(4) 在 PostParameterJson 类中修改代码
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;
//创建一个 类 来接收数据,这个类里的 成员变量 要与 Json 里的字符串的 key 相同~~
class JsonData {
public String userId;
public String password;
}
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//告诉 客户端浏览器 body 的格式是 html 格式的
resp.setContentType("text/html;charset=utf-8");
//处理请求里的body
String body = readBody(req);
//ObjectMapper类 是 Json 的核心类
ObjectMapper objectMapper = new ObjectMapper();
//用这个创建出来的 JsonData 类来接收数据
// 通过 readValue() 方法把 body 这个字符串转成 JsonData 对象
JsonData jsonData = objectMapper.readValue(body, JsonData.class);
resp.getWriter().write("userId = " + jsonData.userId + " password = " + jsonData.password);
}
private String readBody(HttpServletRequest req) throws IOException {
//获取请求正文的长度
int contentLength = req.getContentLength();
//创建一个byte数组来接收请求的body
//为什么要用 byte 呢? 因为接收的时候是按照字节流接收的
byte[] body = new byte[contentLength];
//创建一个输入流对象
InputStream inputStream = req.getInputStream();
//将请求的body写入到数组中
inputStream.read(body);
//将 byte 数组转化为 String,并设置为 utf-8字符集
return new String(body, "utf-8");
}
}
控制台结果:
注意:
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 中写入二进制格式数据。 |
注意:响应对象是服务器要返回给浏览器的内容,一个 HTTP 响应报文里有什么内容,程序员就可以设置什么内容。因此上面的方法都是 “写” 方法。
此外,对于 状态码 / 响应头 的设置要返回到 getWriter / getOutStream 之前,否则可能设置失效。
实现一个程序,用户在浏览器通过参数指定要返回响应的状态码。
创建 StatusServlet 类:
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("/statusServlet")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求里的状态码
String statusString = req.getParameter("status");
//如果不为空
if (statusString != null) {
//将响应里的状态码设置为刚刚获取到的
resp.setStatus(Integer.parseInt(statusString));
}
//将响应里的状态码写到响应的 body 中
resp.getWriter().write("status = " + resp.getStatus());
}
}
访问 localhost:8080/HelloServlet/statusServlet
变换不同的 status 的值,就可以看到不同的响应结果。
实现一个程序,让浏览器每秒钟自动刷新一次,并显示当前的时间戳。
创建 AutoRefreshServlet 类:
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.Date;
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//通过 设置 HTTP 的响应报文里的 Header 中的 Refresh 字段,可以实现自动刷新
resp.setHeader("Refresh", "1");
//获取毫米级的 时间戳
long time = new Date().getTime();
//将时间戳写入到相应的 Body 中
resp.getWriter().write("TimeStamp = " + time);
}
}
访问:localhost:8080/HelloServlet/statusServlet
抓包结果:
实现一个程序,返回一个重定向 HTTP 响应,自动跳转到另外一个页面。
创建 RedirectServlet 类:
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("/redirectServlet")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置状态码
resp.setStatus(302);
//使用指定的重定向位置 URL 发送临时重定向响应到客户端。
resp.sendRedirect("https://www.csdn.net/");
}
}
访问 http://localhost:8080/HelloServlet/redirectServlet
发现成功跳转到了 CSDN 界面
并且通过抓包可以看到 状态码被设置为 302 (临时重定向)
虽然说 Servlet 是一门古老的技术,现在很多公司都用的是SpringMVC-Spring-MyBatis / SpringBoot 做开发了,但是它们都相当于 Servlet 的简化版,因此,学习完 Servlet ,你将有更好的基础去面对后面的框架的知识。 所以,加油吧 ~~