目录
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程序为例:
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进行实例化:
(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方法:
注:但此处的destroy方法是否能被执行到并不确定:
第一种情况:如果是使用smart tomcat的停止按钮终止程序,这个操作本质上是通过tomcat的8005端口来主动停止,可以触发destroy方法;
第二种情况:如果是直接杀进程,此时可能就来不及执行destroy方法;
故而并不推荐使用destroy方法;
1.3 service方法
当收到路径匹配的HTTP请求就会触发service方法,
比如doGet方法就是在service方法中调用的:
父类HttpServlet有一个service方法,其内部就会调用doGet方法;
1.4 Servlet的生命周期
开始时,执行 init;
每次收到请求,执行 service;
销毁前执行 destroy;
注:一个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构造请求
基于一次浏览器地址访问后(即已构造一个GET请求),使用postman依次构造:GET,POST,PUT,DELETE请求并发送,再查看服务器日志:
1.5.2 使用ajax构造请求
1. 在IDEA中创建.html文件:
tomcat要求:.html文件设置在webapp目录下,与WEB-INF同级;
可以使用vscode进行编写:
用vscode打开即可;
2. 编写代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
$.ajax({
type:'get',
url:'method',
// 此处url为相对路径,此时基准路径为当前html所在的路径:127.0.0.1:8080/hello_servlet
success:function(body, status){
console.log(body);
}
})
</script>
</body>
</html>
在浏览器输入地址:
按f12进入chrome的console标签页:
同样在服务器日志也可见:
3. 依次修改请求类型为post,put,delete,再运行,即可得到不同的结果:
注:使用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("<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>");
// 获取到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());
}
}
运行后根据url打开页面:
注:(1)此处实现html页面的换行操作不能使用\n,而使用<br>标签,同时设置响应的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:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<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>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
(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打开浏览器页面如下:
注:(1)输入在url后的查询字符串键值对会被Tomcat处理成形如Map这样的结构,后续就可以随时通过key来获取到value了;
(2)如果key在query string中不存在,则返回值为空;
3.2 通过POST,借助form表单
form表单也是键值对形式组织数据的,只是这部分内容在body中;
对于前端是form表单这样格式的数据,后端还是使用paraMeter来获取;
(1)前端:test.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<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>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
(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打开浏览器页面:
提交后,页面如下:
注:(1)使用query string和使用form表单的后端代码格式基本相同,都是通过getParameter根据key获取value,只是使用form表单构造的键值对不在query string中,而是在body中;
(2)getParameter方法不仅能获取到请求中query string中的键值对,还可以获取到form表单构造的body中的键值对;
(3)以form表单形式构造并发送的请求一定会触发页面跳转;
(4).html及请求、浏览器页面的对应关系图:
(5)前端后端交互的过程:
图1:
图2:
3.3 通过POST,使用json格式构造body
json也是键值对结构的数据格式,可以把body按照该形式组织;
在前端,可以通过ajax方式构造该内容,也可以使用postman;
(1)postman构造请求:
(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请求:
当请求到达tomcat后,tomcat就会将其解析为req对象;
在servlet代码中,req.getInputStrea,,读取body内容,又把body的内容构造成一个响应结果返回给浏览器(此例中为postman),在post页面中就有响应结果:
注:(1)使用form表单构造请求传参与json格式请求传参的代码执行流程是类似的,只是传输数据的格式不同,
form表单的格式形如:
json格式形如:
但是当前通过json传递数据,服务器只是将整个body都读出来,没有按照键值对的方式处理,还不能根据key获取到value,可以使用第三方库来解析json的body,如jackson,gson,fastjson,由于spring mvc内置了jackson这个库,故而天然就支持处理jackson的数据。
故而,以引入jackson库为例:
通过maven引入第三方库:
基于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);
此时根据路径打开浏览器页面,就会出现乱码:
为了避免这种情况,我们需要在写回响应前,显式指定响应的编码格式字符集:
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);
刷新浏览器页面,有:
(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");
}
}
运行代码后,根据路径打开浏览器页面,即可跳转至搜狗主页:
前后端交互逻辑如下:
实现重定向,尤其是返回错误页面有多种方式: