文章目录
- 写在前面
- Smart Tomcat 插件
- Servlet 中常见的API
- 1. HttpServlet
- init 方法
- destroy 方法
- service 方法
- Servlet 的生命周期
- 使用 postman 构造请求
- 使用 ajax 构造请求
- 2. HttpServletRequest
- 3. 前端给后端传参
- 1). GET, query string
- 2). POST, form
- 3). json
- 4. HttpServletResponse
写在前面
上一篇我们写了第一个 Servlet 程序, 是使用 Servlet 最朴素的方式. 我们也可以通过一些操作来简化这些步骤.
这就不得不提到插件了
IDEA 功能非常强大吗, 但也不是面面俱到的, IEDA 提供了一些 API , 可以让程序员开发插件对 IDEA 现有的功能进行扩展.
Smart Tomcat 插件
安装好重启 IDEA 即可
首次使用这个插件的时候, 我们需要配置一下.
- 新增一个运行配置
- 新增配置
- 设置一下 Tomcat 的位置, 其他的保持默认即可
- 运行
运行之后, idea 就会自己调用 Tomcat 执行了.
这样的话我们是不是就方便了许多呢, 就算我们修改数据之后也只需在 idea 中重启一下服务器即可, 就不需要手动打包部署了.
smart tomcat 的工作原理
它不是把 war 包拷贝了(webapp 里没变), idea是通过另一种方式来启动 tomcat 的
tomcat 支持启动的时候显示指定一个特定的 webapp 目录, 相当于是让 tomcat 加载单个 webapp 运行.
idea 直接调用 tomcat , 让 tomcat 加载当前项目中的目录.
这个过程其实没有打 war 包的过程, 也没有拷贝, 也没有解压缩的过程.
Servlet 中常见的API
1. HttpServlet
我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法
方法名称 | 调用时机 |
---|---|
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/… | 收到其他请求的时候调用(由 service 方法调用) |
init 方法
然后我们重启服务器, 重新访问
我们多刷新几次
tomcat 收到了 /hello 这样路径的请求就会调用 hello_servlet_1 进行实例化(实例化只进行一次)
后续在收到 /hello 此时不必再进行实例化, 直接复用之前的 hello_servlet_1 即可
destroy 方法
然后关闭服务器
但是这里的 destroy 不一定被执行到
- 如果是通过 smart tomcat 的停止按钮, 这个操作本质上是通过 tomcat 的 8005 端口, 主动停止, 能够触发 destroy.
- 如果是直接杀进程, 此时可能来不及执行 destroy 就关闭了.
因此不太推荐使用 destroy, 不靠谱
service 方法
Servlet 的生命周期
- 开始的时候, 执行 init
- 每次收到请求, 执行 service
- 销毁之前, 执行 destroy
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");
}
}
根据不同的请求, 返回不同的响应
使用 postman 构造请求
使用 ajax 构造请求
首先创建一个 html 文件, 一定注意文件位置在 webapp 目录下
引入 jQuery cdn 链接, 编写代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 使用这个页面来构造 ajax 请求-->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$.ajax({
type:'get',
// 相对路径
// url:'method',
// 绝对路径
url:'/test2/mothod',
success: function(body, status) {
console.log(body);
}
})
</script>
</body>
</html>
这里的 url 是个相对路径, 相对路径的基准目录就是该 html 所在的路径
然后重新启动服务器, 重新访问
改这里的 type 类型就能构造出不同的请求
2. HttpServletRequest
核心方法
方法 | 描述 |
---|---|
String getProtocol() | 返回请求协议的名称和版本 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分。 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。Enumeration |
getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。 |
String getParameter(Stringname) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回null。 |
String[] getParameterValues(Stringname) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。 |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名。 |
String getHeader(Stringname) | 以字符串形式返回指定的请求头的值。 |
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 ShowRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这里设置 content-type
resp.setContentType("text/html");
// 搞个StringBuild , 把这些的 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>");
stringBuilder.append("<br>");
stringBuilder.append("<br>");
stringBuilder.append("<br>");
// 获取 header 中的键值对
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
stringBuilder.append(headerName + ":" + req.getHeader(headerName));
stringBuilder.append("<br>");
}
resp.getWriter().write(stringBuilder.toString());
}
}
3. 前端给后端传参
1). GET, query string
在前端给后端传练个数字, 一个是同学的 studentiId, 一个是 classId
?studentIt=10&classId=20
下面编写代码来处理这个请求
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=10&classId=20 请求
// 借助 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 + " class = " + classId);
}
}
2). POST, form
对于前端是 form 表单这样格式的数据, 后端还是使用 getParameter 来获取.
form 表单 这种格式也是键值对, 和 query string 的格式一样
只是这部分内容存在 body 中
下面我们编写代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="postParameter" method="post">
<input type="text" name="studentId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
</body>
</html>
输入内容提交就能构造出一个 POST 请求, body 就是form 表单格式.
我们也可以抓包看看
下面我们编写后端的代码
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 + " class " + classId);
}
}
使用 getParameter 方法, 既可以获取到 query string 中的键值对, 也可以获取到 form 表单构造的 body 中的键值对.
3). json
json 也是一种非常主流的数据格式, 也是键值对结构.
我们可以把 body 部分 按照这个格式来组织
前端可以使用 ajax 的方式来构造出这个内容. 还可以用 postman 直接构造
编写代码
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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* @author chenhongfei
* @version 1.0
* @describe
* @date 2023/11/14
*/
@WebServlet("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 body 为 json 格式的值
// 直接把 req 对象里 body 完整读取出来
// getInputStream
// 在流对象中读多少个字节, 取决于 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);
}
}
服务器这边我们也能看到
图解:
这个代码的执行流程, 和上个通过 form 表单传参, 流程是类似的. 只不过是传输的数据类型不同罢了.
form 表单是形如 classId=20&studentId=10
json 的格式形如:
{
classId:20,
studentId:10
}
本质上来说, 这三种方式都是等价的.
当前通过 json 传递数据, 但是服务器这边只是把整个 body 读出来, 还没有按照键值对的方式来处理(不能根据 key 获取 value)
form 表单是可以根据 key 获取 value (getParameter 就支持了).
这里我们推荐使用第三方库, Jackson
通过 Meven 引入第三方库
meven 库
把代码复制到 pom.xml
编写代码
Student student = objectMapper.readValue(req.getInputStream(),Student.class);
- 这个代码会从 body 中取出 json 格式的字符串
{
classId: 20,
studentId 10
}- 根据第二个参数类对象, 创建 Student 对象
- 解析上述 json 的字符串, 处理成 map 键值对结构
- 遍历所有键值对, 看键的名字和 Student 实例的哪个属性名字匹配, 就把对应的属>性 value 设置到该属性中
- 返回该 Student 实例
4. HttpServletResponse
Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到HttpServletResponse 对象中.
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket 写回给浏览器
核心方法
方法 | 描述 |
---|---|
void setStatus(int sc) | 为该响应设置状态码。 |
void setHeader(String name,String value) | 设置一个带有给定的名称和值的 header. 如果 name 已经存在,则覆盖旧的值. |
void addHeader(Stringname, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对 |
void setContentType(Stringtype) | 设置被发送到客户端的响应的内容类型。 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8。 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据. |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据. |
设置字符编码
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=10&classId=20 请求
// 借助 req 里的 getParameter 方法就能拿到 query string 中的键值对了.
// getParameter 得到的是 String 类型的结果.
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
// 设置字符集, 中文不乱码
resp.setContentType("text/html;charset=utf8");
// resp.setCharacterEncoding("utf-8");
resp.getWriter().write("学生 = " + studentId + " 班级 = " + classId);
}
}
重定向
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");
}
}
点击就跳转到了搜狗了
单独设置也可以
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//resp.sendRedirect("https://www.sogou.com");
resp.setStatus(302);
resp.setHeader("Location","https://www.sogou.com");
}