Servlet API code example: server version confession wall

1. Server version confession wall

The confession wall implemented saves the message data here through the server , thereby further achieving "persistent" storage~~

1. Determine the interactive interface

When implementing this program, you need to first consider how to interact between the client and the server~

Agree on the interface for front-end and back-end interaction~~

(Customized application layer protocol)
Since it is a server, the server must provide some services. What kind of services are provided specifically? And how should these services be triggered? All need to be considered carefully~~

For confession walls, there are two main interfaces:

1). Tell the server what kind of data the current message is~~ (When the user clicks the submit button , an HTTP request will be sent to the server to let the server save the message)

It is agreed that in order to achieve this effect, the client should send what kind of HTTP request, and the server should return what kind of HTTP response~

ask:

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

According to the above ideas, there are countless ways to agree on the request format!!! The method can be changed, the path can be changed, and the name of the parameter can also be changed...

There are so many ways to agree, which one to adopt?
Any one is fine!!! As long as you can identify one of them and strictly implement it when writing front-end/back-end code , it’s ok!!

This is exactly the meaning of our agreement on the front-end and back-end interfaces!!!

Custom protocol!! (This is exactly restricting the freedom of programmers and cannot write random code)

In our future actual development work, especially when two programmers, the front-end and the back-end, work together to implement a function, this behavior of agreeing on the front-end and back-end interactive interfaces is crucial. It must be done before the development action is taken. Something to be done~~

We agree to use:

ask:

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

response:

HTTP/1.1 200 OK
    
{
    
    
    ok: true
}

2). Obtain from the server what message data currently exists~~ (When the page is loaded, you need to obtain the content of these messages that have been stored from the server)

ask:

GET /message

Response: array in JSON format, each element is a JSON object

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

2. Backend code

  • After determining the interface, you can write the code
  • You need to write both back-end and front-end code
  • (In actual work, two people are often responsible for the back-end and the front-end, and they are developing in parallel) - Joint debugging

Backend development:

Make preparations first

  1. Create project (maven)
  2. Introduce dependencies (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. Create a directory

  2. Write code

// 和前面约定的 前后端交互接口 相匹配
@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. Packaging and deployment (using smart tomcat)

Insert image description here

  1. verify

Insert image description here

Complete code:

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

Start the server and use Postman to construct the request

GET:
Insert image description here

POST:

Insert image description here

Construct PSOT three times, then return to GET:

Insert image description here


3. Front-end code

Now that the back-end logic has been written, let’s write the front-end code~~
You can copy the pages you wrote earlier.

Based on the previous code, add ajax operation~

Insert image description here

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

In JSON , the key must be in quotes , but in JS , the key in the object can be in quotes or not.

With or without it, it is all string type~~
(Normally, it is required, but js allows you to omit the quotation marks in order to make it easier for everyone to write)

If there are some special symbols in the key , such as spaces - this kind of ~~ must be quoted at this time

Conversion between objects and JSON strings:

  • Java:
    • objectMapper.readValue converts json string into object
    • objectMapper.writeValueAsString converts objects into json strings
  • JS:
    • JSON.parse converts json string into object
    • JSON.stringify converts objects into json strings

Complete code:

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

Visit: 127.0.0.1:8080/message_wall/messageWall.html

Capture the packet to see the response:

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

[]

When the page is loaded, you can see that there is an ajax request. This request is to get the message list from the server~~You can see that
the response here is empty!!!
Didn't you just submit three messages to the server? Record??

Insert image description here

The code just saved the message into this List (List is memory ). Once the program is restarted , the data in the memory will be lost~~

Therefore, the above logic can only guarantee that the data will not be lost after the page is refreshed , but it cannot guarantee that the data will not be lost after the server is restarted~~
The probability of page refresh must be greater!!

Of course, if you want to solve this problem more thoroughly, you need to save the data to the hard disk (can be a file or a database)

Enter submission:

Insert image description here

It can indeed be proven that even if the page is closed and opened again, the previously submitted messages will not be lost~~ (When the page is loaded, the message list is obtained from the server and displayed in the browser page)


4. Save data to file

In response to the above problem, if the data is saved in a file, the data will not be lost if the server is restarted.

Modify the MessageServlet code:

  • Delete the previous messages member.
  • Create a new member String filePath , which represents the path of the file to be stored.
  • Added a new load method to read content from the file. (load will be called when the page is loaded)
  • A new save method is added to write content to the file. (save will be called when submitting a message)
  • The file format is stored as line text. Each record occupies one line, and the fields (from, to, message) of each record are separated by \t

The file format is as follows:

Insert image description here

@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. Store data in database

It is possible to use files to store messages, but it is not elegant.
We can also use a database to complete the storage work.

5.1. Introduce mysql dependency

    <!-- 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. Rewrite the MessageServlet code

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. Create DBUtil class

Basic process of using JDBC:

  1. Create data source
  2. Establish a connection with the database
  3. Construct sql statement
  4. Execute sql statement
  5. If it is a query statement, it is necessary to traverse the result set. If it is insert/deletion/modification, it is not required.
  6. Close the connection and release resources

Create database:

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

from is a keyword in sql. When the keyword is used as a table name/column name, you need to add backtick ` (the key in the upper left corner of the keyboard)

Will the current getConnection be executed in a multi-threaded environment??

  • getConnection will be called in doGet / doPost~~
  • Are the multiple doXXX methods triggered by multiple requests called in a multi-threaded environment?
    Yes!!!

Insert image description here

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 method

    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 method

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

Start, visit: 127.0.0.1:8080/message_wall/messageWall.html

Insert image description here

MySQL:

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

5.6. Summary of MVC

Basic steps to develop a confession wall (a simple website):

  1. Agree on the interface for front-end and back-end interaction~ (what is the request, what is the response)

  2. Develop server code

    • First write a Servlet that can handle requests from the front end.
    • Write database code to store/retrieve key data
  3. Develop client code

    • Able to construct requests and parse responses based on ajax
    • Able to respond to user operations (after clicking the button, trigger the behavior of sending a request to the server)

Most of these "website" programs are in a similar situation~~

MVC:

  • Controller (controller, key logic after processing the request)
  • Model (logic for operating data access)
  • View (interface displayed to the user)

View —— Controller —— Model


Guess you like

Origin blog.csdn.net/qq_56884023/article/details/125785439