Servlet API 代码示例:服务器版表白墙

一、服务器版表白墙

实现的这个表白墙,就通过服务器,保存这里的消息数据,从而进一步的做到 “持久化” 存储~~

1、确定交互接口

在实现这个程序的时候,就需要先考虑清楚,客户端和服务器之间该如何进行交互~

约定前后端交互的接口~~

(自定义应用层协议)
既然是搞一个服务器,服务器就得提供一些服务,具体是提供啥样的服务? 以及这些服务该如何触发? 都需要去考虑清楚~~

对于表白墙来说,主要要提供两个接口:

1). 告诉服务器,当前留言了一条啥样的数据~~ (当用户点击提交按钮的时候,就会给服务器发送一个 HTTP 请求,让服务器把这个消息给存下来)

约定好,为了实现这个效果,客户端要发送一个啥样的 HTTP 请求,服务器返回一个啥样的 HTTP响应~

请求:

POST /message
    
{
    
    
    from: "黑猫",
    to: "白猫",
    message: "喵"
}
POST /messageWall
    
from=黑猫&to=白猫&message=
GET /message?from=黑猫&to=白猫&message=

按照上述的思路发散开,有无数种方式来约定请求格式!!! 方法可以变,路径可以变,参数的名字也可以变…

这么多的约定方式,到底采取哪一种??
哪种都可以!!! 只要你能够明确下来其中的一种,并且在后续编写前端 / 后端代码的时候能够严格执行,就是 ok 的!!

这也正是咱们约定前后端接口的意义!!!

自定义协议!! (正是在约束程序猿的自由,不能随便乱写代码)

在咱们未来的实际开发工作中,尤其是,前端和后端两个程序猿配合实现一个功能的时候,这种约定前后端交互接口的行为是至关重要的,一定是进行开发动作之前,第一件要完成的事情~~

我们就约定使用:

请求:

POST /message
    
{
    
    
    from: "黑猫",
    to: "白猫",
    message: "喵"
}

响应:

HTTP/1.1 200 OK
    
{
    
    
    ok: true
}

2). 从服务器获取到,当前都有哪些留言数据~~ (当页面加载,就需要从服务器获取到曾经存储的这些消息内容)

请求:

GET /message

响应:JSON 格式的数组,每个元素都是一个 JSON 对象

HTTP/1.1 200 OK
Content-Type: application/json
    
{
    
    
    {
    
    
        from: "黑猫",
        to: "白猫",
        message: "喵"
    },
    {
    
    
        from: "黑猫",
        to: "白猫",
        message: "喵"
    },
    {
    
    
        from: "黑猫",
        to: "白猫",
        message: "喵"
    }
}

2、后端代码

  • 确定好接口之后,就可以编写代码了
  • 既需要编写后端代码,也需要编写前端代码
  • (实际工作中,往往,后端和前端是两个人分别负责,俩人就是在并行的开发) —— 联调

后端开发:

先进行准备工作

  1. 创建项目 (maven)
  2. 引入依赖 (Servlet,Jackson)
	<dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.6.1</version>
        </dependency>
    </dependencies>
  1. 创建目录

  2. 编写代码

// 和前面约定的 前后端交互接口 相匹配
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 处理提交信息请求
        resp.getWriter().write("hello post");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 获取到消息列表
        resp.getWriter().write("hello get");
    }
}
    1. 打包和部署 (使用 smart tomcat 来进行)

在这里插入图片描述

  1. 验证

在这里插入图片描述

完整代码:

class Message {
    
    
    public String from;
    public String to;
    public String message;
}

// 和前面约定的 前后端交互接口 相匹配
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    
    
    private ObjectMapper objectMapper = new ObjectMapper();

    private List<Message> messages = new ArrayList<>();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 处理提交信息请求
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        // 最简单的保存放法就是保存到内存中
        messages.add(message);
        // 通过 ContentType 告知页面,返回的数据是 json 格式
        // 有了这样的声明,此时 jquery ajax 就会自动帮我们把字符串转成 js 对象
        // 如果没有,jquery ajax 只会当成字符串来处理
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write("{ \"ok\": true }");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 获取到消息列表 将消息列表的内容整个的都返回给客户端即可
        // 此处需要使用 ObjectMapper 把 Java 对象,转成 JSON 格式字符串
        String jsonString = objectMapper.writeValueAsString(messages);
        System.out.println("jsonString: " + jsonString);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(jsonString);
    }
}

启动服务器,使用 Postman 构造请求

GET:
在这里插入图片描述

POST:

在这里插入图片描述

构造三次 PSOT,然后回到 GET:

在这里插入图片描述


3、前端代码

当前已经把后端逻辑编写完毕,接下来来写前端代码~~
就可以把之前前面写好的页面给拷贝过来

再在之前的代码基础上,加上 ajax 的操作~

在这里插入图片描述

let body = {
    
    
	"from": from,
	to: to,
	message: msg
};
{
    
    
    "from": "黑猫",
    "to": "白猫",
    "message": "喵"
},

JSON 中,key 要带引号,但是在 JS 中,对象这里的 key 可以带引号,也可以不带

带还是不带,都是字符串类型~~
(正常是要带的,但是 js 为了让大家写起来方便一些,允许省略引号)

如果要是 key 里面带有一些特殊符号,比如像 空格 – 这种~~ 此时就必须带引号了

对象和 JSON 字符串之间的转换:

  • Java:
    • objectMapper.readValue 把 json 字符串转成对象
    • objectMapper.writeValueAsString 把对象转成 json 字符串
  • JS:
    • JSON.parse 把 json 字符串转成对象
    • JSON.stringify 把对象转成 json 字符串

完整代码:

<style>
    * {
      
      
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    .container {
      
      
        width: 100%;
    }

    h3 {
      
      
        text-align: center;
        padding: 30px 0; /* 上下内边距 20,左右为 0 */
        font-size: 24px;
    }

    p {
      
      
        text-align: center;
        color: #999;
        padding: 10px 0;
    }

    .row {
      
      
        width: 400px;
        height: 50px;
        margin: 0 auto;

        display: flex;
        justify-content: center;
        align-items: center;
    }

    .row span {
      
      
        width: 60px;
        font-size: 20px;
    }

    .row input {
      
      
        width: 300px;
        height: 40px;
        line-height: 40px;
        font-size: 20px;
        text-indent: 0.5em;
        /* 去掉输入框的轮廓线 */
        outline: none;
    }

    .row #submit {
      
      
        width: 200px;
        height: 40px;
        font-size: 20px;
        line-height: 40px;
        margin: 0 auto;
        color: white;
        background-color: orange;
        /* 去掉边框 */
        border: none;
        border-radius: 10px;
    }

    /* 按下的效果 */
    .row #submit:active {
      
      
        background-color: grey;
    }
</style>

<div class="container">
    <h3>表白墙</h3>
    <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>
    <div class="row">
        <button id="submit">提交</button>        
    </div>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<script>
    // 加入 ajax 的代码,此处要加入的逻辑有两个部分
    // 点击按钮提交的时候,ajax 要构造数据发送给服务器
    // 页面加载的时候,从服务器获取消息列表,并在界面上直接显示

    function getMessages() {
      
      
        $.ajax({
      
      
            type: 'get',
            url: "message",
            success: function(body) {
      
      
                // 当前 body 已是一个 js 对象数组了,ajax 会根据响应的 content type 来自动进行解析
                // 如果服务器返回的 content-type 已经是 application/json 了,ajax 就会把 body 自动转成 js 对象
                // 如果客户端没有自动转,也可以通过 JSON.parse() 这个函数手动转换
                let container = document.querySelector('.container');
                for (let message of body) {
      
      
                    let div = document.createElement('div');
                    div.innerHTML = message.from + ' 对 ' + message.to + ' 说 ' + message.message;
                    div.className = 'row'; // 应用 row 的样式
                    container.appendChild(div);
                }
            }
        });
    }

    // 加上函数调用
    getMessages();


    // 当用户点击 submit,就会获取 input 中的内容,把内容构造成一个 div,插入页面末尾
    let submitBtn = document.querySelector('#submit');
    submitBtn.onclick = function() {
      
      
        // 1、获取 2 个 input
        let inputs = document.querySelectorAll('input');
        let from = inputs[0].value;
        let to = inputs[1].value;
        let msg = inputs[2].value;
        if (from == '' || to == '' || msg == '') {
      
       // 用户还未填写完毕
            return;
        }
        // 2、生成一个新的 div,内容就是 input 中的内容,新的 div 加到页面中
        let div = document.createElement('div');
        div.innerHTML = from + ' 对 ' + to + ' 说 ' + msg;
        div.className = 'row'; // 应用 row 的样式
        let container = document.querySelector('.container');
        container.appendChild(div);
        // 3、清空之前输入框的内容
        for (let i = 0; i < inputs.length; i++) {
      
      
            inputs[i].value = '';
        }

        // 4、把当前获取到的输入框的内容,构造成一个 HTTP POST 请求,通过 ajax 发给服务器
        let body = {
      
      
            "from": from, // 在 JSON 中,key 要带引号,但是在 JS 中,对象这里的 key 可以带引号,也可以不带
            to: to,
            message: msg
        };
        $.ajax({
      
      
            type: "post",
            url: "message",
            contentType: "application/json;charset=utf8",
            data: JSON.stringify(body),
            success: function(body) {
      
      
                alert("消息提交成功!");
            },
            error: function() {
      
      
                alert("消息提交失败!");
            }
        });
    }
</script>

访问:127.0.0.1:8080/message_wall/messageWall.html

抓包看响应:

HTTP/1.1 200
Content-Type: application/json;charset=utf8
Content-Length: 2
Date: Sun, 22 May 2022 12:00:17 GMT
Keep-Alive: timeout=20
Connection: keep-alive

[]

当页面加载的时候,就能够看到有一个这个 ajax 请求,这个请求就是从服务器获取到消息列表的~~
此处看到这个响应是空着的!!!
刚才不是明明往服务器上提交了三条记录嘛??

在这里插入图片描述

刚刚这个代码是把消息保存到了这个 List 中(List 就是内存),一旦程序重启,内存中的数据就会丢失~~

因此,上述逻辑,只能保证页面刷新后数据不丢失,而不能保证服务器重启后数据不丢失~~
页面刷新的概率—定是更大的!!

当然,要想更加彻底的解决这个问题,就需要把数据保存到硬盘上 (可以是文件,也可以是数据库)

输入提交:

在这里插入图片描述

当前确实可以证明,即使页面关闭了,再次打开之后,之前提交的消息是不丢失的~~ (页面加载的时候,从服务器获取到了消息列表,并显示在了浏览器页面中)


4、数据存入文件

针对上面的问题, 如果把数据保存在文件中,那么重启服务器也不会丢失数据了

修改 MessageServlet 代码:

  • 删掉之前的 messages 成员.
  • 创建新的成员 String filePath, 表示要存储的文件的路径.
  • 新增 load 方法, 用来从文件中读取内容. (会在页面加载的时候调用 load)
  • 新增 save 方法, 用来往文件中写入内容. (会在提交留言的时候调用 save)
  • 文件格式按照 行文本 的方式存储. 每个记录占用一行, 每个记录的字段之间(from, to, message) 使用 \t 分隔

文件格式形如:

在这里插入图片描述

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    
    
	// 用于保存所有的留言
    // private List<Message> messages = new ArrayList<Message>();
    // 用于转换 JSON 字符串
    private ObjectMapper objectMapper = new ObjectMapper();
    // 数据文件的路径
    private String filePath = "d:/messages.txt";
    
    public List<Message> load() {
    
    
        List<Message> messages = new ArrayList<>();
        System.out.println("从文件读取数据");
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
    
    
            while (true) {
    
    
                String line = bufferedReader.readLine();
                    if (line == null) {
    
    
                    break;
                }
                String[] tokens = line.split("\t");
                Message message = new Message();
                message.from = tokens[0];
                message.to = tokens[1];
                message.message = tokens[2];
                messages.add(message);
            }
		} catch (IOException e) {
    
    
            // 首次运行的时候文件不存在, 可能会在这里触发异常.
            e.printStackTrace();
        }
        System.out.println("共读取数据 " + messages.size() + " 条!");
        return messages;
    }
    
    public void save(Message message) {
    
    
        System.out.println("向文件写入数据");
        // 使用追加写的方式打开文件
        try (FileWriter fileWriter = new FileWriter(filePath, true)) {
    
    
            fileWriter.write(message.from + "\t" + message.to + "\t" +
            message.message + "\n");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
    
    // 获取所有留言
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        List<Message> messages = load();
        resp.setContentType("application/json;charset=utf-8");
        String respString = objectMapper.writeValueAsString(messages);
        resp.getWriter().write(respString);
    }
    
    // 新增留言
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        resp.setContentType("application/json;charset=utf-8");
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        save(message);
        resp.getWriter().write("{ \"ok\": 1 }");
    }
}

5、数据存入数据库

使用文件的方式存储留言固然可行,但是并不优雅
我们还可以借助数据库完成存储工作

5.1、引入 mysql 依赖

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

5.2、改写 MessageServlet 代码

class Message {
    
    
    public String from;
    public String to;
    public String message;
}

// 和前面约定的 前后端交互接口 相匹配
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    
    
    private ObjectMapper objectMapper = new ObjectMapper();

//    private List<Message> messages = new ArrayList<>();
//    改成数据库,不需要整个变量

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 处理提交信息请求
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
//        // 最简单的保存放法就是保存到内存中
//        messages.add(message);
        // 通过 ContentType 告知页面,返回的数据是 json 格式
        // 有了这样的声明,此时 jquery ajax 就会自动帮我们把字符串转成 js 对象
        // 如果没有,jquery ajax 只会当成字符串来处理
        save(message);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write("{ \"ok\": true }");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 获取到消息列表 将消息列表的内容整个的都返回给客户端即可
        // 此处需要使用 ObjectMapper 把 Java 对象,转成 JSON 格式字符串
        List<Message> messages = load();
        String jsonString = objectMapper.writeValueAsString(messages);
        System.out.println("jsonString: " + jsonString);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(jsonString);
    }

    private void save(Message message) {
    
    
        // 把一条数据保存到数据库中
    }

    private List<Message> load() {
    
    
        // 从数据库中获取所有数据
    }
}

5.3、创建 DBUtil 类

用 JDBC 基本流程:

  1. 创建数据源
  2. 和数据库建立连接
  3. 构造 sql 语句
  4. 执行 sql 语句
  5. 如果是查询语句,需要遍历结果集,如果是插入 / 删除 / 修改,则不需要
  6. 关闭连接,释放资源

创建数据库:

create table message(`from` varchar(1024), `to` varchar(1024), message varchar(4096));

from 是 sql 中的关键字,当关键字作为表名 / 列名的时候,需要加上反引号` (键盘左上角的那个键)

当前 getConnection 是否会在多线程环境下执行??

  • getConnection 会在 doGet / doPost 中调用~~
  • 多个请求触发的多个 doXXX 方法,是否是多线程环境调用呢?
    是的!!!

在这里插入图片描述

public class DBUtil {
    
    
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/java102?characterEncoding=utf8&useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "11111";

    private volatile static DataSource dataSource = null; // volatile

    private static DataSource getDataSource() {
    
    
        // 多线程安全
        if (dataSource == null) {
    
    
            synchronized (DnsUrl.class) {
    
    
                if (dataSource == null) {
    
     // 懒汉模式
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource) dataSource).setURL(URL);
                    ((MysqlDataSource) dataSource).setUser(USERNAME);
                    ((MysqlDataSource) dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }

    // 代码和数据库服务器建立连接 import java.sql.Connection;
    public static Connection getConnection() throws SQLException {
    
    
        return getDataSource().getConnection();
    }

    // 释放资源
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
    
    
        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();
            }
        }
    }
}

5.4、sava 方法

    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 {
    
    
            DBUtil.close(connection, statement, null);
        }
    }

5.5、load 方法

    private List<Message> load() {
    
    
        // 从数据库中获取所有数据
        List<Message> messages = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
    
    
            connection = DBUtil.getConnection();
            String sql = "select * from message";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
    
    
                Message message = new Message();
                message.from = resultSet.getString("from");
                message.to = resultSet.getString("to");
                message.message = resultSet.getString("message");
                messages.add(message);
            }
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        } finally {
    
    
            DBUtil.close(connection, statement, resultSet);
        }
        return messages;
    }

启动,访问:127.0.0.1:8080/message_wall/messageWall.html

在这里插入图片描述

MySQL:

mysql> select * from message;
+--------------+-----------+------------+
| from         | to        | message    |
+--------------+-----------+------------+
| 海棉宝宝     | 章鱼哥    | 早上好~    |
+--------------+-----------+------------+

5.6、总结 MVC

开发一个表白墙 (一个简单网站) 基本步骤:

  1. 约定前后端交互的接口~ (请求是啥,响应是啥)

  2. 开发服务器代码

    • 先编写Servlet能够处理前端发来的请求
    • 编写数据库代码,来存储 / 获取关键数据
  3. 开发客户端代码

    • 基于 ajax 能够构造请求以及解析响应
    • 能够响应用户的操作 (点击按钮之后,触发给服务器发送请求的行为)

大部分的这种 “网站” 类的程序,都是类似的情况~~

MVC:

  • Controller (控制器,处理请求之后的关键逻辑)
  • Model (操作数据存取的逻辑)
  • View (给用户展示的界面)

View —— Controller —— Model


猜你喜欢

转载自blog.csdn.net/qq_56884023/article/details/125785439
今日推荐