目录
一、创建Servlet项目
二、约定前后端交互接口
三、前端代码
四、后端代码
五、效果演示
结合Servlet API ,实现一个服务器版本表白墙。实现的这个表白墙,就通过服务器来保存这里的消息数据,进而做到 “持久化” 存储。
一、创建Servlet项目
1、目录结构:
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、pom.xml
需要添加的依赖:
<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>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
完整的 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>2023_4_30_Servlet</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.12.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
</project>
二、约定前后端交互接口
对于表白墙来说,主要提供两个接口:
- 告诉服务器,当前留言了一条什么样的数据
- 从服务器中获取到,当前都有什么留言数据
1、告诉服务器,当前留言了一条什么样的数据
当用户点击提交按钮时,就会给服务器发送一个 HTTP 请求,让服务器把这个消息给存下来。
约定好客户端发送的是什么样的 HTTP 请求,服务器返回一个什么样的请求。
请求:
POST/message
{
from:" 黑猫 ",
to:" 白猫 ",
message:" 喵 "
}
(这里的请求格式是可以改变的)
响应:
HTTP/1.1 200 OK
{
ok:true
}
2、从服务器中获取到,当前都有什么留言数据
当页面加载,就需要从服务器获取到曾经存储的消息内容,并显示。
请求:
GET/message
响应:
HTTP/1.1 200 OK
Content-Type:application/json
[
{
from:" 黑猫 ",
to:" 白猫 ",
message:" 喵 "
} ,
{
from:" 猫 ",
to:" gou猫 ",
message:" 喵 "
} ...
]
三、前端代码
在前端代码实现页面的基础上,进行添加。
因为要发送 POST 请求,所以先引入 jquery,以便使用 jquery ajax 来构造POST 请求。
<!-- 引入jquery,导入 jquery 库的连接,以便后面能使用 jquery ajax -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
加入 ajax 的代码,此处要加入的逻辑有两个部分
- 点击按钮提交的时候,ajax 要构造数据发送给服务器
- 页面加载的时候,从服务器获取消息列表,并在界面上直接显示
构造POST请求:
// 4.把当前获取到的输入框的内容,构造成一个 HTTP POST 请求,通过 ajax 发送给服务器
let body = {
from: from,
to: to,
message: msg
};
$.ajax({
type: "post",
url: "message",
contentType:"application/json; charset=utf8",
// 把 body 转成 json 字符串格式的对象
data: JSON.stringify(body),
success: function(body){
alert("消息提交成功!");
},
error: function(){
alert("消息提交失败!");
}
});
注意:
在 JSON 中,key 是要带上引号的。
在 JS 中,对象这里的 key 可以带引号,也可以不带引号。(正常是要带的,但是JS为了写起来方便,允许这里省略引号)但是如果 key 中有一些特殊符号,比如空格、- 这种,就必须带上引号。
let body = {
from: from,
to: to,
message: msg
};
返回曾经消息:
function getMessage() {
$.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';
container.appendChild(div);
}
}
})
}
// 加上函数调用
getMessage();
对象 和 JSON 字符串之间的转换:
Java :
objectMapper.readValue :把 json 字符串转成 对象
objectMapper.writeValueAsString :把 对象 转成 json 字符串
JS :
JSON.parse :把 json 字符串 转成 对象
JSON.stringify :把 对象 转成 json 字符串
整体的前端代码:
<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
}
h3 {
text-align: center;
padding: 30px 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: 300px;
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: gray;
}
</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>
<!-- 引入jquery,导入 jquery 库的连接,以便后面能使用 jquery ajax -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
// 加入 ajax 的代码,此处要加入的逻辑有两个部分
// 点击按钮提交的时候,ajax 要构造数据发送给服务器
// 页面加载的时候,从服务器获取消息列表,并在界面上直接显示
function getMessage() {
$.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';
container.appendChild(div);
}
}
})
}
// 加上函数调用
getMessage();
// 当用户点击 submit, 就会获取到 input 中的内容, 从而把内容构造成一个 div, 插入到页面末尾.
let submitBtn = document.querySelector('#submit');
submitBtn.onclick = function() {
// 1. 获取到 3 个 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';
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,
to: to,
message: msg
};
$.ajax({
type: "post",
url: "message",
contentType:"application/json; charset=utf8",
// 把body 转成字符串格式的对象
data: JSON.stringify(body),
success: function(body){
alert("消息提交成功!");
},
error: function(){
alert("消息提交失败!");
}
});
}
</script>
</body>
</html>
四、后端代码
要实现 “ 持久化 ” 保存数据,则将数据都放在数据库中。
1、创建数据库
创建数据库 MessageWall,创建表 messages。表的属性有 from、to、message。
2、实现 DButil 类
注意这里使用了单例模式中的懒汉模式。
- ① 正确的位置加锁
- ② volatile
- ③ 双重 if 判定
public class DBUtil {
//创建 MysqlDataSource 实例,设置 URL,USERNAME,PASSWORD 等属性
//提供 getConnection 方法,和 MySQL 服务器建立连接
//提供 close 方法,用来释放必要的资源
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 = "12345";
private volatile 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() throws SQLException {
return getDataSource().getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
注意:
URL 中,
jdbc:mysql://127.0.0.1:3306/MessageWall?characterEncoding=utf8&useSSL=false
标红位置是自己所建的数据库的名称。
3、实现 MessageServlet 类
首先创建类 Message :
class Message {
public String from;
public String to;
public String message;
}
创建 MessageServlet 类,继承自 HttpServlet,重写 doPost方法 以及 doGet 方法。(因为在约定接口时,接口一规定传来的请求时 POST 请求,接口儿规定传来的请求时 GET 请求)
doPost方法中:
先将请求信息转换成 Message 对象,再将 Message 对象的内容存入数据库。然后设置了响应的内容类型为 json格式 和 字符集为utf-8 。然后再向 body 中写入约定的 POST响应 的内容。
//通过这个关键类,去解析Json对象/构造Json对象
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理提交消息请求
//req.getInputStream():先从流对象中读取到数据内容,
//Message.class:把字符串(JSON)转成 Message 对象
//把结果赋值到 message 对象里
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//存数据
save(message);
//要返回Json格式的Json数据
//通过ContentType来告知页面说,当前返回的数据是Json格式
//有了这样的声明,此时 jquery ajax 就会自动的帮我们把 json字符串 转成 js 对象
//如果没有,jquery ajax 就只是当成 json字符串 来处理
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write("{\"ok\":true}");
}
//将数据存入数据库中
private void save(Message message) {
//把一条消息保存到数据库中
Connection connection = null;
PreparedStatement statement = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造 SQL 语句进行插入
String 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.执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4.关闭
//因为是插入语句,所以没有result
DBUtil.close(connection,statement,null);
}
}
doGet方法中:
先从数据库中取出之前存储的信息存到链表中,将 Message对象 转换成 json字符串。然后设置响应的内容格式是 json,设置字符集为 utf-8。然后写入作为GET方法响应的body中。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取消息列表
//把消息列表获取到,List中内容返回到响应里就可以了
//只要把消息列表中的内容整个的都返回给客户端即可
//此处需要使用 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 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 messages";
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 e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return messages;
}
整体代码:
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 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;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Created with IntelliJ IDEA.
* Description:
* User: WangWZ
* Date: 2023-04-30
* Time: 18:46
*/
class Message {
public String from;
public String to;
public String message;
}
@WebServlet("/message")
//改用数据库存储,实现存和取两个方法---save 和 load
public class MessageServlet extends HttpServlet{
//通过这个关键类,去解析Json对象/构造Json对象
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理提交消息请求
//req.getInputStream():先从流对象中读取到数据内容,
//Message.class:把字符串(JSON)转成这样的 Message 对象
//把结果赋值到 message 对象里
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
save(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 {
//获取消息列表
//把消息列表获取到,List中内容返回到响应里就可以了
//只要把消息列表中的内容整个的都返回给客户端即可
//此处需要使用 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) {
//把一条消息保存到数据库中
Connection connection = null;
PreparedStatement statement = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造 SQL 语句进行插入
String 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.执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4.关闭
//因为是插入语句,所以没有result
DBUtil.close(connection,statement,null);
}
}
//从数据库中取出数据
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 messages";
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 e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,statement,resultSet);
}
return messages;
}
}
五、效果演示
此时数据库:
无论页面刷新还是服务器重启,数据都不会消失。(“持久化”存储数据)