【项目-前后端交互-项目】表白墙【servlet实践】

news/2024/11/24 9:00:05/

【项目—前后端交互 案例】表白墙

    • 代码示例: 服务器版表白墙
        • 1. 准备工作
        • 2. 约定前后端交互接口
        • 3. 实现服务器端代码
            • 创建 Message 类
            • 创建 MessageServlet 类
        • 4. 调整前端页面代码
        • 5. 数据存入文件.
        • 6. 数据存入数据库
          • 1) 在 pom.xml 中引入 mysql 的依赖
          • 2) 创建数据库, 创建 messages 表
          • 3) 创建 DBUtil 类
          • 4) 修改 load 和 save 方法, 改成操作数据库
        • 代码总结
            • 1. pom.xml
            • 2. web.xml
            • 3. 表白墙.html
            • 4. DBUtil(数据库)
            • 5. Message
            • 6. MessageServlet

先学习servlet

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

结合上述 API, 我们可以把之前实现的表白墙程序修改成服务器版本. 这样即使页面关闭, 表白墙的内容也不会丢失.

1. 准备工作

  1. 创建 maven 项目.
  2. 创建必要的目录 webapp, WEB-INF, web.xml
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app><display-name>Archetype Created Web Application</display-name>
</web-app>
  1. 调整 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>表白墙服务器版</artifactId><version>1.0-SNAPSHOT</version><dependencies><!-- 加入 servlet 依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><!-- servlet 版本和 tomcat 版本有对应关系,切记 --><version>3.1.0</version><!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 --><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.3</version></dependency></dependencies><packaging>war</packaging><build><finalName>MessageWall</finalName></build>
</project>
  1. 把之前实现的表白墙前端页面拷贝到 webapp 目录中.

2. 约定前后端交互接口

所谓 “前后端交互接口” 是进行 Web 开发中的关键环节.
具体来说, 就是允许页面给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应

  1. 获取全部留言
    在这里插入图片描述
    我们期望浏览器给服务器发送一个 GET /message 这样的请求, 就能返回当前一共有哪些留言记录. 结果以 json 的格式返回过来.

  2. 发表新留言

请求: body 也为 JSON 格式.

在这里插入图片描述
我们期望浏览器给服务器发送一个 POST /message 这样的请求, 就能把当前的留言提交给服务

3. 实现服务器端代码

创建 Message 类
public class Message {public String from;public String to;public String message;
}
创建 MessageServlet 类
@WebServlet("/message")
public class MessageServlet extends HttpServlet {// 用于保存所有的留言private List<Message> messages = new ArrayList<Message>();// 用于转换 JSON 字符串private ObjectMapper objectMapper = new ObjectMapper();// 获取所有留言@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {resp.setContentType("application/json;charset=utf-8");String respString = objectMapper.writeValueAsString(messages);resp.getWriter().write(respString);}// 新增留言@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {resp.setContentType("application/json;charset=utf-8");Message message = objectMapper.readValue(req.getInputStream(), 
Message.class);messages.add(message);resp.getWriter().write("{ \"ok\": 1 }");}
}
  • ObjectMapper 的 readValue 方法也能直接从一个 InputStream 对象读取数据.
  • ObjectMapper 的 writeValueAsString 方法也能把一个对象数组直接转成 JSON 格式的字符串

4. 调整前端页面代码

修改 “表白墙.html”

  1. 拷贝之前封装好的 ajax 函数
// 把之前封装的 ajax 函数拷贝过来
function ajax(args) {// ...... 代码内容参考 HTTP 协议章节
}
  1. 新加 load 函数, 用于在页面加载的时候获取数据
// 从服务器加载数据, 显示在界面上
function load() {ajax({url: 'message',method: 'GET',callback: function (data, status) {// 先把字符串格式的 body 转成 对象数组let messages = JSON.parse(data);// 把每个消息都构造一个 HTML 标签for (let message of messages) {var row = document.createElement('div');row.className = 'row';row.innerHTML = message.from + '对' + message.to + '说: ' +
message.message;// 3. 把构造好的元素添加进去var container = document.querySelector('.container');container.appendChild(row);}}});
}
// 调动 load 执行数据加载
load();
  1. 修改原来的点击事件回调函数. 在点击按钮的时候同时给服务器发送消息.
// 给点击按钮注册点击事件
var submit = document.querySelector('.submit');
submit.onclick = function () {// ...... 前面的代码略, 参考 JavaScript(WebAPI) 章节.// 给服务器发送消息ajax({method: "POST",url: "message",contentType: "application/json; charset=utf-8",body: JSON.stringify({ from: from, to: to, message: message }),callback: function (data, status) {if (status == 200) {console.log("提交消息成功!");} else {console.log("提交消息失败! " + status);}}});
}

此时在浏览器通过 URL http://127.0.0.1:8080/MessageServlet/表白墙.html 访问服务器, 即可看到

此时我们每次提交的数据都会发送给服务器. 每次打开页面的时候页面都会从服务器加载数据. 因此及时关闭页面, 数据也不会丢失.

但是数据此时是存储在服务器的内存中 ( private List messages = new ArrayList(); ), 一旦服务器重启, 数据仍然会丢失

5. 数据存入文件.

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

修改 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();}}// 获取所有留言@Overrideprotected 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);}// 新增留言@Overrideprotected 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 }");}
}

此时即使重启服务器, 留言数据也不会丢失了.

6. 数据存入数据库

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

1) 在 pom.xml 中引入 mysql 的依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.45</version>
</dependency>
2) 创建数据库, 创建 messages 表
set character_set_database=utf8;
set character_set_server=utf8;
create database if not exists MessageWall;
use MessageWall;
drop table if exists messages;
create table messages (`from` varchar(255), `to` varchar(255), `message` 
varchar(2048));
3) 创建 DBUtil 类

DBUtil 类主要实现以下功能:

  • 创建 MysqlDataSource 实例, 设置 URL, username, password 等属性.
  • 提供 getConnection 方法, 和 MySQL 服务器建立连接.
  • 提供 close 方法, 用来释放必要的资源.
// 负责和数据库建立连接
public class DBUtil {private static final String URL = "jdbc:mysql://127.0.0.1:3306/MessageWall?
characterEncoding=utf8&useSSL=false";private static final String USERNAME = "root";private static final String PASSWORD = "";private static DataSource dataSource = null;private static DataSource getDataSource() {if (dataSource == null) {synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();((MysqlDataSource)dataSource).setUrl(URL);((MysqlDataSource)dataSource).setUser(USERNAME);((MysqlDataSource)dataSource).setPassword(PASSWORD);}}}return dataSource;}public static Connection getConnection() {try {return getDataSource().getConnection();} catch (SQLException e) {e.printStackTrace();}return null;}public static void close(Connection connection,PreparedStatement statement,ResultSet resultSet) {try {if (resultSet != null) {resultSet.close();}if (statement != null) {statement.close();}if (connection != null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}
}
4) 修改 load 和 save 方法, 改成操作数据库
private List<Message> load() {List<Message> messages = new ArrayList<>();// 1. 和数据库建立连接Connection connection = DBUtil.getConnection();PreparedStatement statement = null;ResultSet resultSet = null;try {// 2. 拼装 SQLString sql = "select * from messages";statement= connection.prepareStatement(sql);// 3. 执行 SQLresultSet = statement.executeQuery();// 4. 遍历结果集合while (resultSet.next()) {Message message = new Message();message.from = resultSet.getString("from");message.to = resultSet.getString("to");message.message = resultSet.getString("message");messages.add(message);}} catch (SQLException e) {e.printStackTrace();} finally {// 5. 释放必要的资源DBUtil.close(connection, statement, resultSet);}return messages;
}
private void save(Message message) {// 1. 和数据库建立连接Connection connection = DBUtil.getConnection();PreparedStatement statement = null;try {// 2. 拼装 SQLString sql = "insert into messages values(?, ?, ?)";statement = connection.prepareStatement(sql);statement.setString(1, message.from);statement.setString(2, message.to);statement.setString(3, message.message);// 3. 执行 SQLstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {// 4. 释放必要的资源DBUtil.close(connection, statement, null);}
}

重新部署程序, 此时使用数据库之后也可以保证即使服务器重启, 数据也不丢失.

在这里插入图片描述

代码总结

1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>MessageWall</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><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.13.4.1</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency></dependencies></project>
2. web.xml
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app><display-name>Archetype Created Web Application</display-name>
</web-app>
3. 表白墙.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>
<style>.container {width: 400px;/* margin 外边距. 第一个数字上下外边距, 第二个数字表示水平外边距. 如果水平设置成 auto 表示元素就水平居中~~ */margin: 0 auto;}h1 {text-align: center;}p {text-align: center;color: #666;}.row {height: 40px;display: flex;/* 水平居中 */justify-content: center;/* 垂直居中 */align-items: center;}.row span {width: 100px;}.row input {width: 200px;height: 30px;}.row button {width: 306px;height: 40px;color: white;background: orange;border: none;}.row button:active {background-color: #666;}</style><div class="container"><h1>表白墙</h1><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>提交</button></div><!-- 需要构造出一个形如 --><!-- <div class="row">黑猫 对 白猫 说: 喵</div> -->
</div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>let container = document.querySelector('.container');let button = document.querySelector('button');button.onclick = function() {// 1. 获取到输入框的内容let inputs = document.querySelectorAll('input');let from = inputs[0].value;let to = inputs[1].value;let message = inputs[2].value;if (from == '' || to == '' || message == '') {alert('当前输入框内容为空!');return;}console.log(from + ", " + to + ", " + message);// 2. 能够构造出新的 div, 用来保存用户提交的内容let rowDiv = document.createElement('div');rowDiv.className = 'row';rowDiv.innerHTML = from + " 对 " + to + " 说: " + message;container.appendChild(rowDiv);// 3. 提交完之后, 清空输入框的内容for (let i = 0; i < inputs.length; i++) {inputs[i].value = '';}let data = {from: from,to: to,message: message};// 4. 点击发送按钮, 给服务器发个 post 请求.$.ajax({type: 'post',url: 'message',// url: '/java105/message',// 这里放的是 body 的内容data: JSON.stringify(data),contentType: "application/json; charset=utf8",success: function(body) {console.log("提交数据成功");}});}// 直接写在 script 标签里的代码, 都是在页面加载的时候执行的.// 来获取服务器的数据function getMessages() {$.ajax({type: 'get' ,url: 'message',success: function(body) {// body 就是响应的 body 内容, json 数组.// jquery 非常智能的帮我们把 json 数组给解析成了 js 对象数组// 但是有个前提条件, 就是响应的 Content-Type 得是 application/jsonlet container = document.querySelector('.container');for (let i = 0; i < body.length; i++) {let message = body[i];// 根据这个元素构造一个 div.row, 来容纳数据// <div class="row">//     黑猫 对 白猫 说: 喵// </div>let row = document.createElement('div');row.className = 'row';row.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;container.appendChild(row);}}});}getMessages();</script>
</body>
</html>
4. DBUtil(数据库)
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;// 使用这个类来封装 DataSource 的单例
public class DBUtil {private static volatile DataSource dataSource = null;public static DataSource getDataSource() {if (dataSource == null) {synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java105?characterEncoding=utf8&useSSL=false");((MysqlDataSource)dataSource).setUser("root");// 我这里的数据库是没密码的.((MysqlDataSource)dataSource).setPassword("");}}}return dataSource;}private DBUtil() {}
}
5. Message
public class Message {private String from;private String to;private String message;public String getFrom() {return from;}public void setFrom(String from) {this.from = from;}public String getTo() {return to;}public void setTo(String to) {this.to = to;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
6. MessageServlet
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;@WebServlet("/message")
public class MessageServlet extends HttpServlet {// 这个对象在多个方法中都需要使用private ObjectMapper objectMapper = new ObjectMapper();// 负责让页面获取到数据@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 显式声明当前的响应数据格式 不要让客户端去猜!!!resp.setContentType("application/json; charset=utf8");// 把 messageList 转成 json 字符串, 并且返回给页面就行了.List<Message> messageList = null;try {messageList = load();} catch (SQLException e) {e.printStackTrace();}resp.getWriter().write(objectMapper.writeValueAsString(messageList));}// 提交数据@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获取到 body 中的数据并解析Message message = objectMapper.readValue(req.getInputStream(), Message.class);// 把这个 message 保存一下. 简单的办法就是保存在内存中.// messageList.add(message);try {save(message);} catch (SQLException e) {e.printStackTrace();}resp.setStatus(200);System.out.println("提交数据成功: from=" + message.getFrom()+ ", to=" + message.getTo() + ", message=" + message.getMessage());}private List<Message> load() throws SQLException {// 从数据库查询数据// 1. 先有一个数据源DataSource dataSource = DBUtil.getDataSource();// 2. 建立连接Connection connection = dataSource.getConnection();// 3. 构造 SQLString sql = "select * from message";PreparedStatement statement = connection.prepareStatement(sql);// 4. 执行 SQLResultSet resultSet = statement.executeQuery();// 5. 遍历结果集合List<Message> messageList = new ArrayList<>();while (resultSet.next()) {Message message = new Message();message.setFrom(resultSet.getString("from"));message.setTo(resultSet.getString("to"));message.setMessage(resultSet.getString("message"));messageList.add(message);}// 6. 关闭连接statement.close();connection.close();return messageList;}private void save(Message message) throws SQLException {// 把数据保存到数据库中// 1. 先有一个数据源DataSource dataSource = DBUtil.getDataSource();// 2. 建立连接Connection connection = dataSource.getConnection();// 3. 构造 SQLString sql = "insert into message values(?, ?, ?)";PreparedStatement statement = connection.prepareStatement(sql);statement.setString(1, message.getFrom());statement.setString(2, message.getTo());statement.setString(3, message.getMessage());// 4. 执行 SQLint ret = statement.executeUpdate();System.out.println("ret = " + ret);// 5. 关闭连接statement.close();connection.close();}
}

http://www.ppmy.cn/news/68518.html

相关文章

JUC之集合类

JUC包提供了一些并发安全的集合类&#xff0c;用于在多线程环境下进行共享数据的操作&#xff0c;以解决多线程间的竞争条件和线程安全问题。 CopyOnWriteArrayList 相当于线程安全的ArrayList public class ListTest {public static void main(String[] arge){List<Strin…

uniapp 地图组件(map)的使用总结

总结一下本次在uniapp中使用map遇到的一些问题 文章分别是基础 定位图标 获取自身经纬度 通过经纬度获取当时城市信息 首先先看成品 首先引入map组件 <template><view class"content"><map style"width: 100%; height: 90vh;" :layer-…

【利用AI让知识体系化】3万多字让你我快速入门数据结构与算法

文章目录 第一章&#xff1a;介绍数据结构与算法1.1 数据结构的概念1.2 算法的概念1.3 数据结构与算法的关系1.4 为什么需要学习数据结构与算法 第二章&#xff1a;时间与空间复杂度2.1 什么是时间复杂度2.2 时间复杂度的算法分析2.3 什么是空间复杂度2.4 空间复杂度的算法分析…

唐朔飞计算机组成原理常见的寄存器(时不时就更新)

EINT中断允许触发器 如果是1代表开中断就是可以中断 如果0表示关中断 不能触发中断程序&#xff0c;1.响应中断的条件是当设备有请求并且 中断允许触发器为1的时候 时间是指令执行周期结束的时候。2.实现多重中断的条件是 中断允许触发器置为1并且 有更高的服务程序才能除法多重…

Selenium+Unittest自动化测试框架实战(框架源码都给你)

目录 前言 项目框架 首先管理时间 !/usr/bin/env python3 -- coding:utf-8 -- 配置文件 conf.py config.ini# 读取配置文件 记录操作日志 简单理解POM模型 管理页面元素 封装Selenium基类 创建页面对象 熟悉unittest测试框架 编写测试用例 执行用例 生成测试报…

日常开发中代码技巧(个人使用)

日常开发中代码技巧 String&#xff0c;StringBuffer判断非空 String判断非空 参考&#xff1a;https://blog.csdn.net/Echo_width/article/details/79653704 首先&#xff0c;区分空串和null串 1 空串""是长度为0的字符串&#xff0c;它有自己的串长度&#xff08;…

协众信息ui设计岗位招聘要求有哪些?

UI设计师的工作是接到使用者轮廓、线框草稿后&#xff0c;创造出好看、好用的画面&#xff0c;视情况会搭配VisualDesigner&#xff08;视觉设计师&#xff09;&#xff0c;不过偶尔还是需要自己做图&#xff0c;包含插图、icon绘制等&#xff1b;透过设计过的画面&#xff0c;…

怎样设置CRM目标?有什么作用?

实施CRM系统可以帮助企业提高客户保留率&#xff0c;增加收入&#xff0c;并推动业绩增长。然而&#xff0c;在实施CRM系统之前&#xff0c;必须设定明确的目标&#xff0c;与企业的整体战略保持一致。在这篇文章中&#xff0c;我们来讨论实施CRM目标是什么&#xff0c;如何设定…