servlet-API
HttpServlet
写 Servlet 代码的时候,首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法。
init
在 HttpServlet 实例化之后被调用一次。
destory
在 HttpServlet 实例不再使用的时候调用一次
service
收到HTTP请求就会调用
上面三个方法是HttpServlet最重要的三个方法。虽然不太直接用。
面试题:servlet的生命周期
生命周期:每个阶段在干什么?
servlet:
- 开始的时候,执行init
- 每次收到请求,执行service
- 销毁之前,执行destroy
doGet
收到 GET 请求的时候调用(由 service 方法调用)
doPost
收到 POST 请求的时候调用(由 service 方法调用
doPut / doDelete / doOptions
收到其他请求的时候调用(由 service 方法调用)
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
System.out.println("doGet");
resp.getWriter().write("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
System.out.println("doPost");
resp.getWriter().write("doPost");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPut(req, resp);
System.out.println("doPut");
resp.getWriter().write("doPut");
}
}a
这里的GET方法,直接通过页面访问即可,但使用其他方法,就需要使用ajax或者借助工具postman
postman:
postman是后端开发非常好用的工具,
后端需要实现一些HTTP的接口,前端发对应的请求来访问HTTP接口。
就可以通过postman对后端代码进行验证/测试。
ajax 构造请求:
将HTML页面放在webapp目录下
引入jQuery,写Ajax
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
$.ajax ({
type: 'post',
//相对路径的写法 不带 /
//相对路径都有基准目录,基准目录就是该HTML所在的路径
url: 'method',
//绝对路径的写法 带 /
// url: '/hello_servlet2/method',
success: function(body, status) {
console.log(body);
}
});
</script>
访问http://localhost:8080/hello_servlet2/test.html
得到
上述相对路径的写法 url: 'method'
就相当于在http://localhost:8080/hello_servlet2 的基础上,再拼一个method:http://localhost:8080/hello_servlet2/method
这里的method表示访问method这个servlet构成的动态页面。
这里的@WebServlet(“/method”)的路径必须是 / 开头,但表示的含义并非绝对路径,而是servlet的要求。
端口被占用
如果发现端口被占用,先找是哪个进程占用了端口 cmd netstat -ano | findstr "8080"
再在任务管理器中找到这个进程,结束进程。【命令行直接结束也可】
HttpServletRequest
HttpServletRequest
表示的是HTTP请求。
HttpServletRequest
对象是tomcat
自动构造的。
tomcat
会实现监听端口、接受连接、读取请求、解析请求、构造请求对象等一系列工作。
API
前端给后端传参:
- GET query string
- POST form
- POST json
1. GET query string
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 预期浏览器会发送一个形如 /getParameter?studentId=1&classId=2 的请求
// 借助 req 里的 getParameter 方法就能拿到 query string 中的键值对内容。
// getParameter 得到的是 String 类型的结果
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html");
resp.getWriter().write("stu id : " + studentId + "cla id : " + classId);
}
}
http://localhost:8080/hello_servlet2/getParameter?studentId=1&classId=2
这里的 query string 键值对会自动被tomcat处理成形如Map这样的结构,后续就随时可以通过key获取value了
如果再网页中输入的key在 query string 中不存在,此时返回的值就是null
2. POST form
通过form表单,构造post请求,访问servlet代码。
前端通过form表单提交值到tomcat服务器,tomcat服务器构造req和resp对象,值存在req方法里,通过req的方法调用其中的值,再通过resp空对象编写响应,写回到tomcat,tomcat发送响应给浏览器显示。
浏览器先找tomcat请求一个页面,tomcat返回我们写了form表单的test.html页面。
浏览器发送form生成的请求,POST hello_servlet2/postParameter?studentId=1&classId=2,tomcat拿到请求后,解析该请求,把这个请求的方法、url、版本号等,构造req对象,tomcat调用postParameterservlet.doPost studentId=1&classId=2,生成resp,返回给浏览器
@WebServlet("/postParameter")
public class PostParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html");
resp.getWriter().write("stu id : " + studentId + "cla id : " + classId);
}
}
<form action="postParameter" method="post">
<input type="text" name="studentId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
提交的数据也是键值对形式,和 query string 格式一样,只是这部分提交的内容在body中。
点击提交后:
3. POST json
json 一种非常主流的数据格式,键值对结构
{
classId:10,
studentId:20
}
可以把body按照这个格式来组织。
前端可以通过Ajax的方式来构造这个内容,也可以使用postman直接构造。
通过getContentLength
获取body的长度,在通过长度,从请求对象中读取数据。
@WebServlet("/postParameter2")
public class PostParameterServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
// 通过这个方法处理 body 为 json 格式的数据
// 通过 getInputStream 直接把 resp 对象里的 body 完整的读取出来
// getInputStream 得到的是流对象
//在流对象中读多少个字节,取决于context-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);
}
}
启动服务器,在postman这边点击发送请求,postman中返回:
服务器返回,此时带你到结果就是从请求的body中读的内容。
前后端交互过程:
- 首先,postman构造出一个指定的post请求,body就是json数据。
- 接着,请求到达tomcat,tomcat解析成req对象
- 在servlet中,使用req.getInputStream 读取body的内容,把body的内容构造成响应结果,返回给浏览器(postman)
- 最后,数据在postman返回。
通过json和通过form表单传参,效果是类似的,只是传输的数据格式不同。
form表单是形如:studentId=1&classId=2
json的格式是形如
{
studentId:1,
classId:2
}
使用jackson 解析json格式
当前,通过json传递数据,但是服务器只是把整个body读出来,并没有按照键值对的方式来处理(还不能根据key获取value)。【上一个方法的form表单是可以根据key获取value的。】
使用第三方库jackson
解析json格式。通过maven引入第三方库。
在maven仓库搜索jackson
粘贴到pom.xml中。
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;
class Student {
public int studentId;
public int classId;
//如果想要把 类 作为 Jackson 返回的对象,就需要让Jackson能够看到里面有那些属性
//因此要么把属性设置成public 或者 给属性提供public的getter和setter
}
@WebServlet("/postParameter2")
public class PostParameterServlet2 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. 从body中读取json格式的字符串
//2. 根据第二个参数类对象,创建Student实例
//3. 解析上述json格式的字符串,处理成map键值对结构
//4. 遍历所有的键值对,看键的名字和Student实例的哪个属性名字匹配,就把对应 value 设置到该属性中。
//5. 返回该Student实例。
//readValue: 把json字符串转成Java对象
//writeValue: 把Java对象转成json字符串
//req.getInputStream(): 流对象
//Student.class: 类对象是类的图纸,显示的告诉readValue,解析时按照哪个类为目标进行解析
System.out.println(student.studentId + ", " + student.classId);
}
}
如果后端有的属性,前端没有传输参数,会返回默认值;
如果前端传入了后端没有的参数,会报错。
jackson格式
json格式的字符串 转成 Java对象
class Student {
public int classId;
public int studentId;
}
public class TestJackson {
//json格式的字符串 转成 Java对象
public static void main1(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String ss = "{ \"classId\" : 10, \"studentId\": 20}";
//readValue是把json格式的字符串转成Java对象
Student student = objectMapper.readValue(ss, Student.class);
System.out.println(student.studentId);
System.out.println(student.classId);
}
}
Java对象 转成 json格式的字符串
class Student {
public int classId;
public int studentId;
}
public class TestJackson {
//Java对象 转成 json格式的字符串
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Student student = new Student();
student.studentId = 10;
student.classId = 20;
String ss = objectMapper.writeValueAsString(student);
System.out.println(ss);
}
}
总结:从请求中获取参数
- GET,参数通过query string 传递,服务器使用req.getParameter方法
- POST,参数通过body(form表单格式传递) 服务器使用req.getParameter方法
- POST,参数通过body(json格式传递)服务器通过getInputStream读取到body值。
在工作中,这三种方式都很常见。本质上这三种方式是等价的。
在以前,通过get和form表单比较多,现在使用json格式更常见。
HttpServletResponse
API
方法 | 描述 |
---|---|
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。设置字符集必须在getWriter().write()的上面 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 构造重定向响应 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据。 |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据。 |
setCharacterEncoding
resp.setContentType("text/html");
resp.setCharacterEncoding("utf8"); //设置字符集必须在getWriter().write()的上面。
resp.getWriter().write("学生stu id : " + studentId + "cla id : " + classId);
resp.setContentType("text/html; charset=utf8"); //也可以和字符集一起设置
resp.getWriter().write("学生stu id : " + studentId + "cla id : " + classId);
sendRedirect
浏览器访问http://127.0.0.1:8080/hello_servlet2/redirect
后,访问代码,代码执行跳转至https://www.baidu.com
重定向的两种不同的写法:
@WebServlet("/redirect")
public class RediectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
resp.sendRedirect("https://www.baidu.com");
}
}
@WebServlet("/redirect")
public class RediectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(302);
resp.setHeader("Location", "https://www.baidu.com");
}
}
如果不设置状态码,直接设置Location,返回的时非法的响应,具体如何处理,是浏览器行为,不同的浏览器处理方式可能不同。
应用:表白墙
在当前表白墙的页面上,存在的问题是,一旦刷新/关闭,数据就没有了,因为数据保存在浏览器页面里。
现在,我们需要让这些数据保存在服务器中:
- 可以保证页面刷新/关闭 也能加载出数据。
- 其他用户也能看到数据。
让服务器存储用户提交的数据,当新的浏览器打开页面时,再从服务器获取数据。
此处服务器就可以用来进行存档,读档。
涉及前后端交互的环节:
- 点击提交。触发一次存档操作,浏览器把当前用户输入的信息存入服务器。【存储】一条一条存储
- 页面加载。触发一次读档操作,浏览器从服务器获取到所有的信息,展示到页面上【读取】一次读取所有
进行web开发时,一定要重点考虑如何进行前后端交互,约定好前后端交互的数据格式,
考虑请求是啥样的,响应是啥样的,浏览器什么时候发送请求,浏览器按照什么格式解析等等
这一过程称为:设计前后端交互接口。
1. 后端点击提交
约定用POST json数据格式进行提交。
请求:
POST /message
{
from:"黑猫",
to:"白猫",
message:"喵喵"
}
响应:
HTTP/1.1 200 OK
doPost:向服务器提交数据
private List<Message> messageList = new ArrayList<>();
private ObjectMapper objectMapper = new ObjectMapper();
//向服务器提交数据
//doPost:把解析的message,往list里填上就行了
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
// ObjectMapper objectMapper = new ObjectMapper();
// 把body中的内容读取出来的,解析成了message对象
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
//不设置默认也是200,此处可省略
resp.setStatus(200);
}
2. 后端页面加载
请求:
GET /message
响应:
HTTP/1.1 200 OK
[
{
from:"黑猫",
to:"白猫",
message:"喵喵"
} ,
{
from:"黑猫",
to:"白猫",
message:"喵喵"
}
]
在这里我们约定了前后端数据交互的格式,约定并没有固定的要求,只要保证能够实现必须的需求即可。
private List<Message> messageList = new ArrayList<>();
private ObjectMapper objectMapper = new ObjectMapper();
//从服务器获取数据
//doGet:把list里的结果返回给前端
//针对doGet,只是把messageList,转成json字符串,返回给浏览器即可。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
// ObjectMapper objectMapper = new ObjectMapper();
resp.setContentType("application/json; charset=utf8");
objectMapper.writeValue(resp.getWriter(), messageList);
// 把messageList进行格式转换成json,把json写到响应对象中。
//上面的 writeValue 相当于这里的这两步
String jsonResp = objectMapper.writeValueAsString(messageList); //把java对象转成json字符串
System.out.println(jsonResp);
//把这个字符串写入到响应body中
resp.getWriter().write(jsonResp);
}
代码
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.util.ArrayList;
import java.util.List;
class Message {
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
private List<Message> messageList = new ArrayList<>();
private ObjectMapper objectMapper = new ObjectMapper();
//向服务器提交数据
//doPost:把解析的message,往list里填上就行了
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ObjectMapper objectMapper = new ObjectMapper();
// 把body中的内容读取出来的,解析成了message对象
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
//不设置默认也是200,此处可省略
resp.setStatus(200);
}
//从服务器获取数据
//doGet:把list里的结果返回给前端
//针对doGet,只是把messageList,转成json字符串,返回给浏览器即可。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ObjectMapper objectMapper = new ObjectMapper();
resp.setContentType("application/json; charset=utf8");
objectMapper.writeValue(resp.getWriter(), messageList);
// 把messageList进行格式转换成json,把json写到响应对象中。
//上面的 writeValue 相当于这里的这两步
//String jsonResp = objectMapper.writeValueAsString(messageList); //把java对象转成json字符串
//System.out.println(jsonResp);
//把这个字符串写入到响应body中
//resp.getWriter().write(jsonResp);
}
}
测试
首先,没有数据,使用GET请求,获取到空
POST提交多条数据。
再次GET获取到了刚刚POST存储的多条数据。
此时后端的代码就完成了,接下来编写前端代码。让页面能够发起上述请求,并解析响应。
POST是点击提交按钮时发起,
GET是页面加载的时候发起。
要实现前后端交互,需要使用Ajax,因此我们先引入jQuery,搜索jQuery cdn
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
编写前端请求POST【提交数据】
//4. [新增] 给服务器发起post请求,把上述数据提交到服务器这边
//定义一个js对象body,非常类似于js的键值对,key其实是字符串,value是js中的变量/常量
let body = {
"from":from,
"to":to,
"message":msg
};
// 加不加引号都可以,因为js要求对象的key务必是字符串,因此此处的引号可以省略。
// 后面的value是变量:从页面中的三个输入框,读取到数据,放到变量中的。
// 当前的body是js对象,不是字符串,网络传输只能穿字符串,不能传对象。
//因此需要把这个对象转成json格式的字符串。
//js内置了json转换的库 JSON.stringify()
let strBody = JSON.stringify(body);
console.log("strBody" + strBody);
$.ajax({
type: 'post',
url: 'message',
data: strBody,
contentType: "application/json; charset=utf8",
success: function(body) {
console.log("数据发布成功");
}
});
}
访问http://localhost:8080/messageWall/messageWall.html
,并输入内容,浏览器发送了POST请求和构造的json字符串,这些内容就发送到后端服务器这边,触发到服务器的doPost代码,在doPost中,把收到的body中的内容读取出来,解析成了message对象,存到了messageList中,然后后端代码返回200,200就回到了前端代码的success回调函数中。
编写前端读档get 【获取数据】
让浏览器通过Ajax,发送get请求。
服务器本身是存储多条消息的,此处需要从服务器上把全部的消息都获取过来。
//[新增] 在页面加载的时候,发送get请求,从服务器获取到数据,并添加到页面中。
$.ajax ({
type: 'get',
url: 'message',
success: function(body) {
//此处拿到的 body 就是一个 JS 的对象数组
//本来服务器返回的是一个 Json 格式的字符串,但是 jQuery 的 Ajax 能够自动识别
//自动把 JSON 字符串,转成 js 对象数组
//接下来遍历数组,把元素取出来,构造到页面中即可。
let containerDiv = document.querySelector('.container')
for(let message of body) {
//针对每个元素构造一个div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
// rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
rowDiv.innerHTML = message.from + ' 对 ' + message.to + ' 说: ' + message.message;
containerDiv.appendChild(rowDiv);
}
}
});
前端构造出一个GET请求,请求发到后端服务器,由服务器doGet代码处理,在doGet中,把存储数据的messageList转成响应数据返回,将json数组返回给了前端的body,在对body进行遍历,构造div,拼成字符串,显示到页面中。
数据库
当前我们的数据是在内存(变量)中保存的, 重启服务器就没有了。
- 创建数据表,此处只有一个表
message(from,to,message)
- 封装数据库连接操作
- 修改后端代码连接数据库
// 通过这个类,把数据库连接过程封装一下。
public class DBUtil {
private static DataSource dataSource = new MysqlDataSource();
static {
//使用静态代码块,针对 dataSource 进行初始化操作。
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java106?characterEncoding=utf8&useSSl=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("111");
}
//建立连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//断开连接 释放资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
//此处的3个try catch 分来写,避免前面的异常导致后面的代码不能执行。
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//往数据库中存一条消息
private void save(Message message) {
Connection connection = null;
PreparedStatement statement = null;
try {
//1. 建立连接
connection = DBUtil.getConnection();
//2. 构造SQL语句
String sql = "insert into message values(?, ?, ?)";
statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
//3. 执行SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4. 关闭连接
DBUtil.close(connection, statement, null);
}
}
//从数据库取所有的数据
private List<Message> load() {
List<Message> messageList = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1. 和数据库建立连接
connection = DBUtil.getConnection();
//2. 构造SQL
String sql = "select * from message";
statement = connection.prepareStatement(sql);
//执行SQL
resultSet = statement.executeQuery();
//4.遍历结果集合
while (resultSet.next()) {
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messageList.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return messageList;
}
修改doGet和
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
// ObjectMapper objectMapper = new ObjectMapper();
// 把body中的内容读取出来的,解析成了message对象
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
// messageList.add(message);
save(message);
//不设置默认也是200,此处可省略
resp.setStatus(200);
}
//从服务器获取数据
//doGet:把list里的结果返回给前端
//针对doGet,只是把messageList,转成json字符串,返回给浏览器即可。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
// ObjectMapper objectMapper = new ObjectMapper();
resp.setContentType("application/json; charset=utf8");
// objectMapper.writeValue(resp.getWriter(), messageList);
List<Message> messageList = load();
// 把messageList进行格式转换成json,把json写到响应对象中。
//上面的 writeValue 相当于这里的这两步
String jsonResp = objectMapper.writeValueAsString(messageList); //把java对象转成json字符串
System.out.println(jsonResp);
//把这个字符串写入到响应body中
resp.getWriter().write(jsonResp);
}