实现一个简单的前后端交互页面

实现表白墙简单的前后端交互

  • 客户端与服务器之间的通信流程
  • 理解当前案例的前后端交互流程
  • 表白墙的交互接口
  • 纯前端的表白墙 html 页面代码
  • 第一个交互接口:客户端从服务器拿数据
    • 1. 通过 Servlet 构造 GET 响应
      • (1) 步骤
      • (2) 代码
    • 2. 通过 ajax 构造 GET 请求
      • (1) 步骤
      • (2) 代码
  • 第二个交互接口:从客户端往服务器提交数据
    • 1. 通过 ajax 构造 POST 请求
      • (1) 步骤
      • (2) 代码
    • 2. 通过 Servlet 构造 POST 响应
      • (1) 步骤
      • (2) 代码
  • 前后端完全分离的总代码
    • 1. ajax 代码
    • 2. Servlet 代码
    • 3. 展示结果
      • (1) 提交数据展示
      • (2) 刷新浏览器展示结果
    • 4. 提出问题
  • 解决方案
    • 一、将数据写入文件
      • 详解代码步骤
        • (1) POST
        • (2) GET
      • 1. 展示写入文件
      • 2. 展示读取文件
    • 二、将数据写入数据库
      • 详解代码步骤
        • (1) POST
        • (2) GET
      • 1. 展示写入数据库
      • 2. 展示读取数据库
      • 三、对比两者
  • 基于模板引擎实现表白墙案例
    • 1. html 模板文件
    • 2. ThymeleafConfig 类
    • 3. Servlet 代码
    • 展示结果
      • 抓包结果
      • 浏览器显示结果
    • 总结基于模板引擎实现的表白墙思想
  • Web 开发的发展历程
  • 总结
  • 我的体验

客户端与服务器之间的通信流程

实现一个简单的前后端交互页面_第1张图片

理解当前案例的前后端交互流程

实现一个简单的前后端交互页面_第2张图片

表白墙的交互接口

在当前表白墙的案例中,我的需求主要是为了实现服务器来保存用户提交的留言。

第一个交互接口:客户端从服务器拿数据。

当页面加载(刷新页面)的时候,客户端就需要给服务器发送一个请求,把之前已经保存在服务器上的信息,获取下来,展示到浏览器的页面上。

第二个交互接口:从客户端往服务器提交数据。

当浏览器页面的用户点击提交的时候,就要给服务器发送一个请求,把这次留言的信息传给服务器。

纯前端的表白墙 html 页面代码

下面代码是【纯前端的表白墙 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>

第一个交互接口:客户端从服务器拿数据

1. 通过 Servlet 构造 GET 响应

(1) 步骤

服务器 解析请求、根据请求计算响应的步骤:

① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。

② 其次,通过 jackson API 来将 Java 对象中的字符串解析成 json 格式的数据,并写在 HTTP 响应的 正文body 中。

③ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。

(2) 代码

@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);
}

2. 通过 ajax 构造 GET 请求

首先,我们必须明确,GET 请求在当前案例的逻辑是什么?
答:GET 请求:点击浏览器刷新按钮,将服务器的数据展示在浏览器上。

怎么展示?
答:先要获取 HTTP 响应中正文 body 的内容,再展示前端页面上。

(1) 步骤

客户端 构造 GET 请求、获取响应、显示响应结果的步骤:

① 首先,从 HTTP 响应中的正文 body 中获取 json 格式的内容

② 其次,从 客户端获取到 HTTP响应 的时候,响应中的 正文body 已然从 json 格式自动转换成了 数组形式,放在了 data 中,那么我们就可以创建一个数组对象 messages 来接收 data.

③ 最后,将 数组messages 中的 from, to, message 的三个值挂在 DOM 树的末尾,这样一来,就可以在浏览器上显示出来了。

实现一个简单的前后端交互页面_第3张图片

(2) 代码

// 在页面加载的时候,通过这个 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();

第二个交互接口:从客户端往服务器提交数据

1. 通过 ajax 构造 POST 请求

首先,我们必须明确,POST 请求在当前案例的逻辑是什么?
答:POST请求:点击提交按钮,将客户端的数据提交到服务器上。

怎么提交?
答:客户端先将内容写在 HTTP 请求中的正文 body 中,服务器接收到 HTTP请求后,读取请求中的正文 body 内容,并以顺序表这个数据结构保存起来。

(1) 步骤

客户端 构造 POST 请求、获取响应、显示响应结果的步骤:

① 直接通过 ajax 代码,往 HTTP 请求中写 json 格式的数据即可。

② 最后,约定一个接收到 HTTP 响应的结果,即提交请求成功与失败。

(2) 代码

// 构造一个 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('提交消息失败');
        }
    }
});

2. 通过 Servlet 构造 POST 响应

(1) 步骤

服务器 解析请求、根据请求计算响应的步骤:
① 首先,Tomcat 服务器自动为我们实现了读取请求的逻辑。

② 其次,通过 jackson API 来将 json 格式的数据解析成 Java 对象,最终以键值对的形式呈现出来,再将整个对象放入顺序表中,暂时存起来。

③ 接着,我们手动设置 HTTP 响应的 header 头、响应正文 body…

④ 最后,Tomcat 服务器自动为我们实现了返回响应的逻辑。

(2) 代码

@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}");
}

前后端完全分离的总代码

1. ajax 代码

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>

2. Servlet 代码

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);
    }
}

3. 展示结果

(1) 提交数据展示

POST 请求与响应的抓包结果:

实现一个简单的前后端交互页面_第4张图片

浏览器展示结果:

实现一个简单的前后端交互页面_第5张图片

(2) 刷新浏览器展示结果

GET 请求与响应的抓包结果:

实现一个简单的前后端交互页面_第6张图片

浏览器展示结果:

实现一个简单的前后端交互页面_第7张图片

4. 提出问题

在上面的客户端与服务器交互的过程中,从客户端往服务器提交数据,这一过程并没有什么问题,但客户端从服务器拿数据时,就出现了问题。

在我们使用 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();
        }
    }
}

详解代码步骤

(1) POST

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 服务器自动为我们实现了返回响应的逻辑。

(2) GET

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 服务器自动为我们实现了返回响应的逻辑。

1. 展示写入文件

实现一个简单的前后端交互页面_第8张图片

2. 展示读取文件

实现一个简单的前后端交互页面_第9张图片

二、将数据写入数据库

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();
            }
        }
    }
}

详解代码步骤

(1) POST

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 服务器自动为我们实现了返回响应的逻辑。

(2) GET

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 服务器自动为我们实现了返回响应的逻辑。

1. 展示写入数据库

实现一个简单的前后端交互页面_第10张图片

2. 展示读取数据库

实现一个简单的前后端交互页面_第11张图片

三、对比两者

在当前的案例中,可以看到,写入文件的代码量要少很多。然而,是不是所有的场景下,都应该写入文件呢?然后从文件读呢?

答案是否定的。因为我们知道,不管是 Windows 还是 IOS 系统,文件格式能够存储的数据都是较为简单的,而将数据存储到数据库中,就截然不同了,数据库可以存储更加复杂格式的数据。

但话又说回来了,我们应该根据不同的场景,采取不同的存储方式。

基于模板引擎实现表白墙案例

1. html 模板文件

在 html 模板文件中,我改进了几个地方。

  1. 原先是通过 ajax 的方式构造 POST 请求,现在使用 form 表单构造。
  2. 由于 form 表单有一个提交按钮,所以将其 【class 属性】从 button 改为 submit.
  3. input 标签中的 【name 属性】对应着 HTTP 请求中的正文内容,所以要在 input 标签中加上 【name 属性】。
  4. 原先的 JS 代码,全部删除,由于这是通过模板引擎,进行变量替换的逻辑,所以,我们只在服务器端,处理即可。
  5. 此外,在使用模板替换之前,就需要考虑前后端变量替换的一致性。不能够出现变量名称不一致的情况,否则,就无法正确显示。
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>

2. ThymeleafConfig 类

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) {

    }
}

3. Servlet 代码

在 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 响应,那么,最终为我们呈现的结果如下:

抓包结果

实现一个简单的前后端交互页面_第12张图片

浏览器显示结果

实现一个简单的前后端交互页面_第13张图片

总结基于模板引擎实现的表白墙思想

可以看到,就当前案例来说,基于模板引擎实现的表白墙案例,相比于前后端完全分离简单些。

因为前后端分离后,虽然能够将前端代码归为前端管,后端代码归为后端管,但是,由于 json 格式的相互转换,也较为复杂。此外,就 html 和 css 代码不说,而 JS 代码要懂一点,Java 代码要懂一些,前后端交互的每一环约定都要环环相扣,否则,直接影响最终的结果。

然而,当前案例若基于模板引擎实现的话,JS 代码就不需要写了,所有的逻辑只需要通过 Java 逻辑完成即可,但也必须提前明确,怎么套用模板。

Web 开发的发展历程

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) 将数据写入数据库

可以说,每个版本的理解,对于刚开始的我来说,都是比较难的。后来,多敲了几次代码,就发现熟悉了很多。

你可能感兴趣的:(Java,EE,交互,http)