在当前表白墙的案例中,我的需求主要是为了实现服务器来保存用户提交的留言。
第一个交互接口:客户端从服务器拿数据。
当页面加载(刷新页面)的时候,客户端就需要给服务器发送一个请求,把之前已经保存在服务器上的信息,获取下来,展示到浏览器的页面上。
第二个交互接口:从客户端往服务器提交数据。
当浏览器页面的用户点击提交的时候,就要给服务器发送一个请求,把这次留言的信息传给服务器。
下面代码是【纯前端的表白墙 html 页面代码】,也就是【不涉及前后端交互的一个简单 html 实现的页面】,在下面的内容中,我们需要对这里的代码进行修改。最重要的是,我们应该理解,html 代码在什么场景下,修改了哪些部分。
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>表白墙title>
head>
<body>
<div class="parent">
<h2> 表白墙 h2>
<p>输入后点击提交, 会将信息显示在表格中p>
<div class="row">
<span>谁: span>
<input type="text">
div>
<div class="row">
<span>对谁: span>
<input type="text">
div>
<div class="row">
<span>说什么: span>
<input type="text">
div>
<button class="button"> 提交 button>
div>
<script>
//1. 获取输入框中的内容
let submit = document.querySelector('.button');
submit.onclick = function() {
let input = document.querySelectorAll('input');
let from = input[0].value;
let to = input[1].value;
let message = input[2].value;
//console.log(from + ',' + to + ',' + message);
if(from == '' || to == '' || message == '') {
return;
}
//2. 创建节点, 并把节点放入 DOM 树中
let div = document.createElement('div');
div.className = 'row';
div.innerHTML = from + " 对 " + to + " 说: " + message;
let parent = document.querySelector('.parent');
parent.appendChild(div);
//3. 一趟后,清空输入框
for(let i=0; i<input.length; i++) {
input[i].value = '';
}
}
script>
<style>
.parent {
width: 400px;
margin-left: auto;
margin-right: auto;
/* background-color: aquamarine; */
}
h2 {
text-align: center;
font-size: 30px;
padding: 20px;
}
p {
text-align: center;
font-size: 14px;
padding: 10px;
}
.row {
height: 70px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: gray; */
}
span {
width: 150px;
font-size: 18px;
/* text-align: center; */
/* background-color: aquamarine; */
}
input {
width:250px;
height: 45px;
border-radius: 5px;
border-color: black;
/* border: none; */
/* background-color: aquamarine; */
}
.button {
width: 400px;
height: 37px;
background-color: coral;
color: white;
font-weight: bold;
text-align: center;
line-height: 35px;
margin: 10px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: aquamarine; */
}
.button:hover {
background-color: blue;
}
style>
body>
html>
服务器 解析请求、根据请求计算响应的步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,通过 jackson API 来将 Java 对象中的字符串解析成 json 格式的数据,并写在 HTTP 响应的 正文body 中。
③ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset = utf-8");
// 将 Java 对象 转换成 json 格式的字符串,并写在 HTTP 响应的 正文body 中
objectMapper.writeValue(resp.getWriter(), messageList);
}
首先,我们必须明确,GET 请求在当前案例的逻辑是什么?
答:GET 请求:点击浏览器刷新按钮,将服务器的数据展示在浏览器上。
怎么展示?
答:先要获取 HTTP 响应中正文 body 的内容,再展示前端页面上。
客户端 构造 GET 请求、获取响应、显示响应结果的步骤:
① 首先,从 HTTP 响应中的正文 body 中获取 json 格式的内容
② 其次,从 客户端获取到 HTTP响应 的时候,响应中的 正文body 已然从 json 格式自动转换成了 数组形式,放在了 data 中,那么我们就可以创建一个数组对象 messages 来接收 data.
③ 最后,将 数组messages 中的 from, to, message 的三个值挂在 DOM 树的末尾,这样一来,就可以在浏览器上显示出来了。
// 在页面加载的时候,通过这个 load 函数,访问服务器并获取消息列表,最后展示
function load() {
$.ajax({
type: 'GET',
url: 'love', //约定路径
success: function(data, status) {
let container = document.querySelector('.parent');
// data 对应着 HTTP响应 中的 body
let messages = data;
// 这里相当于增强的 for 循环
for(let m of messages) {
let row = document.createElement('div');
row.className = 'row';
row.innerHTML = m.from + '对' + m.to + '说: ' + m.message;
container.appendChild(row);
}
}
});
}
load();
首先,我们必须明确,POST 请求在当前案例的逻辑是什么?
答:POST请求:点击提交按钮,将客户端的数据提交到服务器上。
怎么提交?
答:客户端先将内容写在 HTTP 请求中的正文 body 中,服务器接收到 HTTP请求后,读取请求中的正文 body 内容,并以顺序表这个数据结构保存起来。
客户端 构造 POST 请求、获取响应、显示响应结果的步骤:
① 直接通过 ajax 代码,往 HTTP 请求中写 json 格式的数据即可。
② 最后,约定一个接收到 HTTP 响应的结果,即提交请求成功与失败。
// 构造一个 HTTP请求,把消息发给服务器保存
$.ajax({
type: 'POST',
url: 'love', //约定路径
// 构造一个 json 格式的内容
data: JSON.stringify({from: from, to: to, message: message}),
contentType: "application/json; charset=UTF-8",
success: function(data, status) {
if (data.OK == 1) {
console.log('提交消息成功!');
} else {
console.log('提交消息失败');
}
}
});
服务器 解析请求、根据请求计算响应的步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,通过 jackson API 来将 json 格式的数据解析成 Java 对象,最终以键值对的形式呈现出来,再将整个对象放入顺序表中,暂时存起来。
③ 接着,我们手动设置 HTTP 响应的 header 头、响应正文 body…
④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 将 json 格式的字符串转换成 Java 对象,最终以键值对的形式呈现出来
LovelyMessage lovelyMessage = objectMapper.readValue(req.getInputStream(), LovelyMessage.class);
// 再将整个对象放入顺序表中
messageList.add(lovelyMessage);
// 设置正文 body 文本格式为 json
resp.setContentType("application/json; charset = UTF-8");
// 手动为正文 body 拼接字符串
resp.getWriter().write("{\"OK\": 1}");
}
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>表白墙title>
head>
<body>
<div class="parent">
<h2> 表白墙 h2>
<p>输入后点击提交, 会将信息显示在表格中p>
<div class="row">
<span>谁: span>
<input type="text">
div>
<div class="row">
<span>对谁: span>
<input type="text">
div>
<div class="row">
<span>说什么: span>
<input type="text">
div>
<button class="button"> 提交 button>
div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js">script>
<script>
// 一、GET请求:客户端从服务器拿数据
function load() {
// (1) 首先,从 HTTP 响应中的正文 body 中获取 json 格式的内容
// (2) 其次,从 浏览器获取到数据的时候,已然自动转换成了 数组形式,放在了 data 中
// (3) 最后,将数组中的 from, to, message 的三个值挂在 DOM 树中
// (4) 这样一来,就可以在浏览器上显示出来了
$.ajax({
type: 'GET',
url: 'love',
success: function(data, status) {
let container = document.querySelector('.parent');
// data 对应着 HTTP响应 中的 body
let messages = data;
// 这里相当于增强的 for 循环
for(let m of messages) {
let row = document.createElement('div');
row.className = 'row';
row.innerHTML = m.from + '对' + m.to + '说: ' + m.message;
container.appendChild(row);
}
}
});
}
// 通过这个 load 函数,访问服务器并获取消息列表
load();
//将提交设为点击事件
let submit = document.querySelector('.button');
submit.onclick = function() {
// (1) 获取三个输入框中的内容
let input = document.querySelectorAll('input');
let from = input[0].value;
let to = input[1].value;
let message = input[2].value;
//console.log(from + ',' + to + ',' + message);
//如果三个输入框没填满,就提交不了
if(from == '' || to == '' || message == '') {
return;
}
// (2) 创建节点, 并把节点放入 DOM 树中
let div = document.createElement('div');
div.className = 'row';
div.innerHTML = from + " 对 " + to + " 说: " + message;
let parent = document.querySelector('.parent');
parent.appendChild(div);
// (3) 一趟后,清空输入框
for(let i=0; i<input.length; i++) {
input[i].value = '';
}
// 二、POST请求:从客户端往服务器提交数据
$.ajax({
type: 'POST',
url: 'love',
// 构造一个 json 格式的内容
data: JSON.stringify({from: from, to: to, message: message}),
contentType: "application/json; charset=UTF-8",
success: function(data, status) {
if (data.OK == 1) {
console.log('提交消息成功!');
} else {
console.log('提交消息失败');
}
}
});
}
script>
<style>
.parent {
width: 400px;
margin-left: auto;
margin-right: auto;
/* background-color: aquamarine; */
}
h2 {
text-align: center;
font-size: 30px;
padding: 20px;
}
p {
text-align: center;
font-size: 14px;
padding: 10px;
}
.row {
height: 70px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: gray; */
}
span {
width: 150px;
font-size: 18px;
/* text-align: center; */
/* background-color: aquamarine; */
}
input {
width:250px;
height: 45px;
border-radius: 5px;
border-color: black;
/* border: none; */
/* background-color: aquamarine; */
}
.button, .clear {
width: 400px;
height: 37px;
background-color: coral;
color: white;
font-weight: bold;
text-align: center;
line-height: 35px;
margin: 10px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: aquamarine; */
}
.button:hover {
background-color: blue;
}
.clear:hover {
background-color: black;
}
style>
body>
html>
class LovelyMessage {
public String from;
public String to;
public String message;
}
@WebServlet("/love")
public class LovelyWall extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List<LovelyMessage> messageList = new ArrayList<>();
// 这个方法用来处理,从服务器获取到消息数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset = utf-8");
// 将 Java 对象 转换成 json 格式的字符串,并写在 HTTP 响应的 正文body 中
objectMapper.writeValue(resp.getWriter(), messageList);
}
// 这个方法用来处理,从客户端提交数据给服务器
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 将 json 格式的字符串转换成 Java 对象,最终以键值对的形式呈现出来
LovelyMessage lovelyMessage = objectMapper.readValue(req.getInputStream(), LovelyMessage.class);
// 再将整个对象放入顺序表中
messageList.add(lovelyMessage);
resp.setContentType("application/json; charset = UTF-8");
resp.getWriter().write("{\"OK\": 1}");
}
// 测试用例
@Override
public void init() throws ServletException {
LovelyMessage m = new LovelyMessage();
m.from = "卡布达";
m.to = "小让";
m.message = "一起加油吧!";
messageList.add(m);
m = new LovelyMessage();
m.from = "飞翔机器人";
m.to = "田德丽娜";
m.message = "加油加油...";
messageList.add(m);
}
}
POST 请求与响应的抓包结果:
浏览器展示结果:
GET 请求与响应的抓包结果:
浏览器展示结果:
在上面的客户端与服务器交互的过程中,从客户端往服务器提交数据,这一过程并没有什么问题,但客户端从服务器拿数据时,就出现了问题。
在我们使用 GET 请求与响应的时候,重启 Tomcat 服务器,然后刷新浏览器页面,之前客户端往服务器提交数据,就加载不出来了。这是为什么呢?
答:当前服务器把数据保存到了 messageList 变量,其实也就是将数据放到是内存中了,此时,一旦程序重启 ( 服务器重启 ),内存中的东西就没了。
private List<LovelyMessage> messageList = new ArrayList<>();
从上面的存储方式看,我们从客户端发给服务器的数据,只是一份 “临时文件”,然而,那么,如何做到服务器重启后,数据也不丢失呢?
答:内存存储的是一份临时数据,那么,什么才能够持久化存储呢?一定就是硬盘了!如何写硬盘呢?
(1) 直接写到文件中
(2) 保存到数据库中
由于客户端发给服务器的数据,在服务器端没有做到持久化存储,所以,客户端这里的代码不需要改变,只需要改变服务器端的这边代码即可。
class LovelyMessageSave {
public String from;
public String to;
public String message;
}
@WebServlet("/love2")
public class LovelyWallSave extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private String filePath = "D:/文件/LovelyMessageSave.txt";
// 这个方法用来处理,从服务器获取到消息数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset = utf-8");
// 用顺序表来接收 load 方法,其中存放着 Java 对象
List<LovelyMessageSave> messageSaveList = load();
// 将 Java 对象 转换成 json 格式的字符串,并写在 HTTP 响应的 正文body 中
objectMapper.writeValue(resp.getWriter(), messageSaveList);
}
private List<LovelyMessageSave> load() {
// load() 方法负责读文件,把读到的数据获取后,放到顺序表中
List<LovelyMessageSave> messageSaveList = new ArrayList<>();
// 由于此处按行读取文本,FileReader 类本身不支持,所以使用 BufferedReader 类
try ( BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
while (true) {
// 按行读文本
String line = bufferedReader.readLine();
if (line == null) {
break;
}
// 如果读取到 line 中的内容,再将 line 解析成一个对象,最后放入到顺序表中
// 通过 split 方法分割字符串,按照制表符分割
String[] tokens = line.split("\t");
LovelyMessageSave lovelyMessageSave = new LovelyMessageSave();
lovelyMessageSave.from = tokens[0];
lovelyMessageSave.to = tokens[1];
lovelyMessageSave.message = tokens[2];
messageSaveList.add(lovelyMessageSave);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return messageSaveList;
}
// 这个方法用来处理,从客户端提交数据给服务器
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 将 json 格式的字符串转换成 Java 对象,最终以键值对的形式呈现出来
LovelyMessageSave lovelyMessageSave = objectMapper.readValue(req.getInputStream(), LovelyMessageSave.class);
// 将整个对象放入文件中
save(lovelyMessageSave);
resp.setContentType("application/json; charset = UTF-8");
resp.getWriter().write("{\"OK\": 1}");
}
private void save(LovelyMessageSave lovelyMessageSave) {
System.out.println("向文件写入数据!");
// 这里的第二个参数设置为 true 很关键,这样一来,就能够续写文件了
// 若不设为 true,结果就是,每次写入数据,上一次的数据都会消失
try (FileWriter fileWriter = new FileWriter(filePath, true)) {
// 将文本以按行的形式写入文件中,并且,每次写入文本,都将文本放入文件末尾
fileWriter.write(lovelyMessageSave.from + "\t" + lovelyMessageSave.to +
"\t" + lovelyMessageSave.message + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
POST 响应的代码步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,先通过 jackson API 来将 json 格式的数据解析成 Java 对象,再将整个对象写入文件中。写入文件的逻辑由 save 函数完成。
LovelyMessageSave lovelyMessageSave = objectMapper.readValue(req.getInputStream(), LovelyMessageSave.class);
save(lovelyMessageSave);
③ 接着,创建一个 save 函数,用来处理(向文件写入数据),在写入文件的时候,需要注意下面的点:
// 这里的第二个参数设置为 true 很关键,这样一来,就能够续写文件了
// 若不设为 true,结果就是,每次写入数据,上一次的数据都会消失
try ( FileWriter fileWriter = new FileWriter(filePath, true) )
④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
GET 响应的代码步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,先用顺序表来接收 load 方法,其中存放着 Java 对象;再通过 jackson API 来将 Java 对象中的字符串解析成 json 格式的数据,并写在 HTTP 响应的 正文body 中。
List<LovelyMessageSave> messageSaveList = load();
objectMapper.writeValue(resp.getWriter(), messageSaveList);
③ 此外,在 load 方法中,应该按行读取数据。
// 由于此处按行读取文本,FileReader 类本身不支持,所以使用 BufferedReader 类
try ( BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)) )
④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
class LovelyMessageSave3 {
public String from;
public String to;
public String message;
}
@WebServlet("/love4")
public class LovelyWallSave3 extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
// 用顺序表来接收 load 方法,其中存放着 Java 对象
List<LovelyMessageSave3> messageSave3List = load();
// 将 Java 对象 转换成 json 格式的字符串,并写在 HTTP 响应的 正文body 中
objectMapper.writeValue(resp.getWriter(), messageSave3List);
}
private List<LovelyMessageSave3> load() {
List<LovelyMessageSave3> messageSave3List = new ArrayList<>();
System.out.println("从数据库中读数据!");
// 1. 创建好数据源
DataSource dataSource = new MysqlDataSource();
( (MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/lovely_messages?characterEncoding=utf8&useSSL=false");
( (MysqlDataSource)dataSource).setUser("root");
( (MysqlDataSource)dataSource).setPassword("lfm10101988");
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 2. 让代码和数据库建立连接
connection = dataSource.getConnection();
// 3. 操作数据库
String sql = "select * from messages";
statement = connection.prepareStatement(sql);
// 4. 执行 SQL
// 从数据库的临时表中读取数据放到结果集中
resultSet = statement.executeQuery();
// 4.1 遍历结果集, 若遍历到结果集的末尾,next 返回 false,也就意味着遍历结束
while (resultSet.next()) {
LovelyMessageSave3 lovelyMessageSave3 = new LovelyMessageSave3();
lovelyMessageSave3.from = resultSet.getString("from");
lovelyMessageSave3.to = resultSet.getString("to");
lovelyMessageSave3.message = resultSet.getString("message");
// 往顺序表中插入 Java 对象,对象中存着 数据库每一列每一行对应的值
messageSave3List.add(lovelyMessageSave3);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return messageSave3List;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
LovelyMessageSave3 messageSave3 = objectMapper.readValue(req.getInputStream(), LovelyMessageSave3.class);
save(messageSave3);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"OK\": 1}");
}
private void save(LovelyMessageSave3 messageSave3) {
System.out.println("往数据库中写数据!");
// 1. 创建好数据源
DataSource dataSource = new MysqlDataSource();
( (MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/lovely_messages?characterEncoding=utf8&useSSL=false");
( (MysqlDataSource)dataSource).setUser("root");
( (MysqlDataSource)dataSource).setPassword("lfm10101988");
Connection connection = null;
PreparedStatement statement = null;
try {
// 2. 让代码和数据库建立连接
connection = dataSource.getConnection();
// 3. 操作数据库
String sql = "insert into messages values(?,?,?)";
statement = connection.prepareStatement(sql);
// 3.1 进行替换操作
statement.setString(1, messageSave3.from);
statement.setString(2, messageSave3.to);
statement.setString(3, messageSave3.message);
System.out.println(statement);
// 4. 执行 SQL
int ret = statement.executeUpdate();
System.out.println(ret);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5. SQL 执行完毕,需要释放资源
try {
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
POST 响应的代码步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,先通过 jackson API 来将 json 格式的数据解析成 Java 对象,再将整个对象写入文件中。写入文件的逻辑由 save 函数完成。
LovelyMessageSave lovelyMessageSave = objectMapper.readValue(req.getInputStream(), LovelyMessageSave.class);
save(lovelyMessageSave);
③ 接着,创建一个 save 函数,用来处理(向数据库写入数据),在写入数据库的时候,需要按照 JDBC 编程的固定写法来完成。
1. 创建好数据源
2. 让代码和数据库建立连接
3. 操作数据库
3.1 进行替换操作
4. 执行 SQL
5. SQL 执行完毕,需要释放资源
④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
GET 响应的代码步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。
② 其次,先用顺序表来接收 load 方法,其中存放着 Java 对象;再通过 jackson API 来将 Java 对象中的字符串解析成 json 格式的数据,并写在 HTTP 响应的 正文body 中。
List<LovelyMessageSave> messageSaveList = load();
objectMapper.writeValue(resp.getWriter(), messageSaveList);
③ 此外,在 load 方法中,用来处理(从数据库读取数据),同样地,在读取数据库数据的时候,需要按照 JDBC 编程的固定写法来完成。这里注意:读数据库使用 select 语句,这一点和增删改操作大不相同。
1. 创建好数据源
2. 让代码和数据库建立连接
3. 操作数据库
4. 执行 SQL
4.1 遍历结果集, 若遍历到结果集的末尾,next 返回 false,也就意味着遍历结束
5. SQL 执行完毕,需要释放资源
④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。
在当前的案例中,可以看到,写入文件的代码量要少很多。然而,是不是所有的场景下,都应该写入文件呢?然后从文件读呢?
答案是否定的。因为我们知道,不管是 Windows 还是 IOS 系统,文件格式能够存储的数据都是较为简单的,而将数据存储到数据库中,就截然不同了,数据库可以存储更加复杂格式的数据。
但话又说回来了,我们应该根据不同的场景,采取不同的存储方式。
在 html 模板文件中,我改进了几个地方。
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>表白墙title>
head>
<body>
<form action="messageThymeleaf" method="POST">
<div class="parent">
<h2> 表白墙 h2>
<p>输入后点击提交, 会将信息显示在表格中p>
<div class="row">
<span>谁: span>
<input type="text" name="from">
div>
<div class="row">
<span>对谁: span>
<input type="text" name="to">
div>
<div class="row">
<span>说什么: span>
<input type="text" name="msg">
div>
<button class="submit"> 提交 button>
<div class="row" th:each="message:${messages}">
<span th:text="${message.from}">span>
<span>对span>
<span th:text="${message.to}">span>
<span>说:span>
<span th:text="${message.msg}">span>
div>
div>
form>
<style>
.parent {
width: 400px;
margin-left: auto;
margin-right: auto;
/* background-color: aquamarine; */
}
h2 {
text-align: center;
font-size: 30px;
padding: 20px;
}
p {
text-align: center;
font-size: 14px;
padding: 10px;
}
.row {
height: 70px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: gray; */
}
span {
width: 200px;
font-size: 18px;
/* text-align: center; */
/* background-color: aquamarine; */
}
input {
width:250px;
height: 45px;
border-radius: 5px;
border-color: black;
/* border: none; */
/* background-color: aquamarine; */
}
.submit {
width: 400px;
height: 37px;
background-color: coral;
color: white;
font-weight: bold;
text-align: center;
line-height: 35px;
margin: 10px;
display: flex;
justify-content: center;
align-items: center;
/* background-color: aquamarine; */
}
.submit:hover {
background-color: blue;
}
style>
body>
html>
ThymeleafConfig 类用于初始化模板引擎,并放入 ServletContext 对象中。这样做的目的是:方便同一个 webapp 目录下的所有 Servlet 程序都能够拿到 TemplateEngine 构造的实例。
@WebListener
public class ThymeleafConfig implements ServletContextListener {
/**
* ServletContext 初始化完后,会调用这个方法
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext context = servletContextEvent.getServletContext();
// 1. 创建一个 TemplateEngine 实例
TemplateEngine engine = new TemplateEngine();
// 2. 创建一个 ServletContextTemplateResolver 实例
ServletContextTemplateResolver resolver =new ServletContextTemplateResolver(context);
resolver.setPrefix("WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
engine.setTemplateResolver(resolver);
// 3. 把创建好的 engine 对象放到 ServletContext 对象中去
context.setAttribute("engine", engine);
System.out.println("TemplateEngine 初始化完毕!");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
在 Servlet 代码中,我们创建一个 messageList 顺序表,表示当前服务器把数据保存到了内存中。这和我写的第一个表白墙前后端交互的逻辑是差不多的,拿顺序表存,也从顺序表中读。
不过,这里有一个很关键的点,就是:当服务端做出响应的时候,重定向到 GET 响应,这样一来,客户端也就能够接收到页面信息了。
class Message {
public String from;
public String to;
public String msg;
public Message(String from, String to, String msg) {
this.from = from;
this.to = to;
this.msg = msg;
}
}
@WebServlet("/messageThymeleaf")
public class LoveWallThymeleaf extends HttpServlet {
// 创建一个顺序表,用来临时存放 客户端 提交的数据
private List<Message> messageList = new ArrayList<>();
/**
* 测试用例
*/
@Override
public void init() throws ServletException {
// messageList.add(new Message("Rose", "Jack", "我爱你!"));
// messageList.add(new Message("111", "222", "333"));
// messageList.add(new Message("卡布达","金贵次郎", "加油!"));
}
/**
* GET 请求用于处理客户端从服务器中拿数据
* 这里的 doGet 方法用于处理 GET 响应
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 将前端代码中的 ${message} 与后端代码 messageList 关联起来
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("messages", messageList);
// 从 ServletContext 对象中取出初始化后的 TemplateEngine 实例
ServletContext context = this.getServletContext();
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
// 进行模板的最后渲染
String html = engine.process("lovelyWall", webContext);
resp.getWriter().write(html);
}
/**
* POST 请求用于处理客户端提交数据到服务器中
* 这里的 doPost 方法用于处理 POST 响应
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求的时候,也应该设置 UTF-8 这样的字符集,否则提交就会出现乱码的情况
req.setCharacterEncoding("UTF-8");
// 处理 HTTP 请求的内容,直接将 from, to, msg 通过 new 一个对象,放入顺序表中
String from = req.getParameter("from");
String to = req.getParameter("to");
String msg = req.getParameter("msg");
messageList.add(new Message(from, to, msg));
// 直接重定向 GET 响应的页面,也就能自动加载顺序表中的内容了
resp.sendRedirect("messageThymeleaf");
}
}
我们先进行提交,也就是客户端先进行 POST 请求,然后,POST 响应就会重定向到 GET 响应,那么,最终为我们呈现的结果如下:
可以看到,就当前案例来说,基于模板引擎实现的表白墙案例,相比于前后端完全分离简单些。
因为前后端分离后,虽然能够将前端代码归为前端管,后端代码归为后端管,但是,由于 json 格式的相互转换,也较为复杂。此外,就 html 和 css 代码不说,而 JS 代码要懂一点,Java 代码要懂一些,前后端交互的每一环约定都要环环相扣,否则,直接影响最终的结果。
然而,当前案例若基于模板引擎实现的话,JS 代码就不需要写了,所有的逻辑只需要通过 Java 逻辑完成即可,但也必须提前明确,怎么套用模板。
1. 页面和逻辑是混合在一起。 ( 例如:JSP, PHP, ASP ),这种情况下,写一些简单的页面尚可,复杂的页面却难以维护。
2. 页面和逻辑分离,通过模板引擎来实现。( 例如:Java Servlet 基于一些模板引擎来开发;Python Django;Ruby on Rails, Go. ). 这种情况下,前端和后端开发不方便分工,虽然做到了大部分的分离,但是,一些前端代码包含着后端代码,一些后端代码包含着前端代码,这依然很难让前端开发人员只负责前端,后端开发人员只负责后端。
3. 前后端彻底的分离,前端页面通过 ajax / Postman 和后端通信,后端只返回数据 ( 通常是 Json 格式 ),后端不再去拼接页面,都是由前端拿到数据之后,自己负责拼接。【 这种情况下,是当前的主流开发方式 】
基于 HTTP 协议,客户端与服务器通信流程的本质上就是:一发一收。
一发一收指的并不是只有一个客户端发送,一个服务器接收。一般情况下,一个服务器在同一时刻可以与许许多多的客户端交互。
可以联想到:我们通过百度查资料,同一时刻,可能就是成千上万的人同时向百度服务器发出查询请求。
然而,不同的客户端在与同一个服务器进行交互的时候,它们之间的约定是不一样的。( 比如:客户端 访问 / 接收路径,访问 / 接收数据… )
所以说,一发指的是客户端发一条指令,一收指的是服务器收到指令就会处理响应。
理解一发一收,对于 Web 开发是相当重要的。
在客户端发送请求之后,接着,服务器接收请求了,那么,服务器如何构造响应,选择在什么样的时刻处理请求,选择以什么样的格式返回响应等等…这些都是需要基于 “一发一收” 这样的规则。
综上所述,在写案例的时候,就需要先约定好:( 页面和服务器之间,要按照什么样的方式来交互),页面给服务器发送什么样的请求,服务器返回什么样的响应,这个是 Web 开发中最关键的,也是最难的步骤。
由于 前端负责构造发送请求,后端负责接收并解析请求;前端负责接收并解析响应,后端负责构造发送响应。如果不事先约定好,那么,就会造成无法正常沟通的状况。
比如:约定 HTTP请求 到底是 GET 还是 PUT ?约定 HTTP响应 到底是 json 格式 还是类似于 query string 的格式 ?约定 两者交互时的共同路径是什么 ?
同时,这也就是属于 Web程序 【设计】的一部分,这个和技术关系不大,主要是得理清楚业务流程。而业务流程取决于你的需求,这也就是你设计的初衷。
在当前表白墙的案例中,我的需求主要是为了实现服务器来保存用户提交的留言。
1. 第一个交互接口:从客户端往服务器提交数据。
当浏览器页面的用户点击提交的时候,就要给服务器发送一个请求,把这次留言的信息传给服务器。
2. 第二个交互接口:客户端从服务器拿数据。
当页面加载(刷新页面)的时候,客户端就需要给服务器发送一个请求,,把之前已经保存在服务器上的信息,获取下来,展示到浏览器的页面上。
这算是我做前后端交互的第一个案例。
首先,刚开始接触案例的时候,发现,对【请求与响应】比较浑,前面虽然理解了 HTTP协议,但是,实际上自己应用起来的时候,却发现,很多东西看起来很熟悉,做起来却无从下手。我认为,这是逻辑不清楚的原因。
其次,对于各种各样的 API 运用的不是很熟悉,比方说:Servlet, Jackson, MySQL,…另外,对于 Java 语言本身的对象操作,还是有些地方很糊涂…
我将这个案例,实现了三个版本。
(1) 将数据写入内存
(2) 将数据写入文件
(3) 将数据写入数据库
可以说,每个版本的理解,对于刚开始的我来说,都是比较难的。后来,多敲了几次代码,就发现熟悉了很多。