目录
一、Servlet API 详解
1. HttpServletRequest
1.1 HttpServletRequest 方法
1.2 getParameter
2.HttpServletResponse
2.1 HttpServletResponse 方法
2.2 代码示例: 设置状态码
2.3 代码示例: 重定向
二、表白墙
1.准备工作
2.约定前后端交互接口
2.1 接口一:页面获取当前所有的留言消息
2.2 接口二:提交新消息给服务器
3.调整前端页面代码
4.数据持久化
4.1 数据存入数据库
上节 Servlet 我们学习了 Tomcat 提供的 API;并且编写一个 Hello World 基本步骤:1️⃣创建项目(maven)2️⃣引入依赖(servler)3️⃣创建目录4️⃣编写代码(webapp/WEB-INF/web.xml)5️⃣打包程序(waven package)6️⃣部署程序(把 war 拷贝到 webapps 目录中)7️⃣验证代码;最后我们学习了 Servlet 中的三个核心类
HttpServletRequest 是一个 HTTP 请求:Tomcat 通过 Socket API 读取 HTTP 请求(字符串),就会按照 HTTP 协议的格式把字符串解析成 HttpServletRequest
String getProtocol()
|
返回请求协议的名称和版本
|
String getMethod()
|
返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT
|
String getRequestURI()
|
从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分
|
String getContextPath()
|
返回指示请求上下文的请求 URI 部分
|
String getQueryString()
|
返回包含在路径后的请求 URL 中的查询字符串
|
Enumeration getParameterNames()
|
返回一个 String 对象的枚举,包含在该请求中包含的参数的名称
|
String getParameter(String name)
|
以字符串形式返回请求参数的值,或者如果参数不存在则返回 null
|
String[] getParameterValues(String name)
|
返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null
|
Enumeration getHeaderNames()
|
返回一个枚举,包含在该请求中包含的所有的头名
|
String getHeader(String name)
|
以字符串形式返回指定的请求头的值
|
String getCharacterEncoding()
|
返回请求主体中使用的字符编码的名称
|
String getContentType()
|
返回请求主体的 MIME 类型,如果不知道类型则返回 null
|
int getContentLength()
|
以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1
|
InputStream getInputStream()
|
用于读取请求的 body 内容. 返回一个 InputStream 对象
|
1️⃣ 其中 getRequestURI 不是 URL:URL 唯一资源定位符;URI 唯一资源标识符
2️⃣ query string 查询字符串:例如 http://餐厅:18/熏肉大饼?葱=少放,其中 葱=少放 就是查询字符串
3️⃣Enumeration getParameterNames() 和 String getParameter(String name) 是获取请求中的字符串:上述中 query string 中的键值对
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//继承 HttpServlet
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder result = new StringBuilder();
result.append(req.getProtocol());
result.append("
");
result.append(req.getMethod());
result.append("
");
result.append(req.getRequestURI());
result.append("
");
result.append(req.getQueryString());
result.append("
");
result.append(req.getContextPath());
result.append("
");
//在响应中设置上 body 的类型,方便浏览器进行解析
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(result.toString());
}
}
getParameter 是最常用的 API 之一,是前端和后端传递数据非常常见的需求
1️⃣通过 query string 传递
约定:前端通过 query string 传递 username 和 password
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;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//前端通过 url 的 query string 传递 username 和 password 两个属性
String username = req.getParameter("username");
if (username == null) {
System.out.println("username 这个 key 在 query string 中不存在!");
}
String password = req.getParameter("password");
if (password == null) {
System.out.println("password 这个 key 在 query string 中不存在!");
}
System.out.println("username" + username + ", password" + password);
resp.getWriter().write("ok");
}
}
在 URL 中 query string 如果是包含 中文/特殊字符,务必要使用 urlencode 的方式转码;如果是直接写 中文/特殊字符,就会存在很大风险;如果不转码,在有些浏览器/http服务器下对中文支持不好的花,会出现问题
2️⃣通过 body (from) 传递
相当于 body 里存的数据的格式,和 query string 一样,但是 Content-Type 是 application/x-www-form-urlencoded,此时也是通过 getParameter 来获取到键值对
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;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//给请求设置编码方式
req.setCharacterEncoding("utf8");
//前端通过 body,以 form 表单的格式,把 username 和 password 传给服务器
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username == null) {
System.out.println("username 这个 key 在 body 中不存在!");
}
if (password == null) {
System.out.println("password 这个 key 在 body 中不存在!");
}
System.out.println("username" + username + ", password" + password);
resp.getWriter().write("ok");
}
}
由于是 post 请求,在网页中不好表现,我们打开 postman
3️⃣通过 body (json) 传递(最常用的传递方式)
json 也是键值对格式的数据,但是 Servlet 自身没有内置 json 解析功能,因此就需要借助其他第三方库;用来处理 json 的第三方库有很多,常见的如 fastjson、gson、jackson...,我们使用 jackson 来解析
引入依赖:
com.fasterxml.jackson.core
jackson-databind
2.15.0
import com.fasterxml.jackson.databind.ObjectMapper;
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;
class User {
public String username;
public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
// 使用 jackson, 最核心的对象就是 ObjectMapper
// 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成一个 json 格式字符串.
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过 post 请求的 body 传递过来一个 json 格式的字符串.
User user = objectMapper.readValue(req.getInputStream(), User.class);
System.out.println("username=" + user.username + ", password=" + user.password);
resp.getWriter().write("ok");
}
}
readValue 里面做的事情:
- 解析 json 字符串,转换成若干个键值对
- 根据第二个参数 User.class(.class 是类对象,就是这个类的图纸),去找到 User 里的所有的 public 的属性(或者有 public getter setter 的属性),依次遍历......
- 遍历属性,根据属性的名字取上述准备好的键值对里查询,看看这个属性名字是否存在对应的 value ,如果存在就把 value 复制到该属性中
HttpServletResponse 表示一个 HTTP 响应
Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到 HttpServletResponse 对象中;然后 Tomcat 就会把这个HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器.
void setStatus(int sc)
|
为该响应设置状态码
|
void setHeader(String name, String value)
|
设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则覆盖旧的值
|
void addHeader(String name, String value)
|
添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对
|
void setContentType(String type)
|
设置被发送到客户端的响应的内容类型
|
void setCharacterEncoding(String charset)
|
设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8
|
void sendRedirect(String location)
|
使用指定的重定向位置 URL 发送临时重定向响应到客户端
|
PrintWriter getWriter()
|
用于往 body 中写入文本格式数据
|
OutputStream getOutputStream()
|
用于往 body 中写入二进制格式数据
|
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;
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(404);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("返回 404 响应!");
}
}
抓包结果:
2.3 代码示例: 自动刷新
通过 header 实现自动刷新效果:给 HTTP 响应中,设置 Refresh——时间
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;
//通过 header 实现自动刷新效果:给 HTTP 响应中,设置 Refresh——时间
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 每隔 1s 自动刷新一次.
resp.setHeader("Refresh", "1");
resp.getWriter().write("time=" + System.currentTimeMillis());
}
}
代码案例:用户访问这个供暖这个路径的时候,自动重定向到搜狗主页
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;
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 用户访问这个路径的时候, 自动重定向到 搜狗主页 .
//resp.setStatus(302);
//resp.setHeader("Location", "https://www.sogou.com");
resp.sendRedirect("https://www.sogou.com");
}
}
部署程序, 通过 URL http://127.0.0.1:8080/hello_servlet/redirectServlet 访问, 可以看到, 页面自动跳转到 搜狗主页 了.
抓包结果:
表白墙前端页面代码:message.html
表白墙
表白墙
输入内容后点击提交, 信息会显示到下方表格中
谁:
对谁:
说:
1️⃣创建一个 maven 项目
2️⃣引入依赖:servlet、jackson
com.fasterxml.jackson.core
jackson-databind
2.15.0
javax.servlet
javax.servlet-api
3.1.0
provided
3️⃣创建必要的目录 webapp、WEB-INF、web.xml
web.xml 中的代码:
Archetype Created Web Application
4️⃣把之前实现的表白墙前端页面拷贝到 webapp 目录中
5️⃣配置 Smart Tomcat
6️⃣运行程序就可以访问前端页面
写代码之前需要明确前后端交互端口
什么时候发送请求❓❓
1️⃣页面加载完毕之后,需要给服务器发送请求,获取当前的留言数据都有什么
2️⃣用户点击提交的时候,就需要告诉服务器,当前用户发送了的消息是啥
在交互的过程中,有涉及到关键的问题:请求具体是什么样子❓❓响应具体是什么样子❓❓这些都需要程序猿来设计,这就叫“约定前后端交互端口”
此处给出一份典型的约定方式(并不是唯一的方式)
约定
1️⃣请求:GET/message
2️⃣响应:HTTP/1.1 200 OK
Content-Type:application/json
[
{
from: "从哪里来",
to: "到哪里去",
message: "消息是啥"
},
{
from: "从哪里来",
to: "到哪里去",
message: "消息是啥"
},
......
]
json 中使用[ ] 表示数组,[ ] 中的每个元素是一个 { } json 对象;每个对象里又有三个属性 from、to、message
1️⃣请求:POST/message
Content-Type:application/json
[
{
from: "从哪里来",
to: "到哪里去",
message: "消息是啥"
},
{
from: "从哪里来",
to: "到哪里去",
message: "消息是啥"
},
......
]
2️⃣响应:HTTP/1.1 200 OK
处理 "获取所有留言消息":创建 Message 类、创建 MessageServlet 类
import com.fasterxml.jackson.databind.ObjectMapper;
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.util.ArrayList;
import java.util.List;
class Message {
// 这几个属性必须设置 public !!!!
// 如果设置 private, 必须生成 public 的 getter 和 setter !!!
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List messageList = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 "获取所有留言消息"
// 需要返回一个 json 字符串数组. jackson 直接帮我们处理好了格式.
String respString = objectMapper.writeValueAsString(messageList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respString);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 "提交新消息"
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
System.out.println("消息提交成功! message=" + message);
// 响应只是返回 200 报文. body 为空. 此时不需要额外处理. 默认就是返回 200 的.
}
}
表白墙
表白墙
输入内容后点击提交, 信息会显示到下方表格中
谁:
对谁:
说:
此时通过浏览器的URL:127.0.0.1:8080/message_wall/messageWall.htm 访问服务器即可看到
此时我们每次提交的数据都会发送给服务器. 每次打开页面的时候页面都会从服务器加载数据. 因此及时关闭页面, 数据也不会丢失.
但是数据此时是存储在服务器的内存中 ( private List
messages = new ArrayList (); ), 一旦服务器重启, 数据仍然会丢失
把数据存储到银盘上,才是让数据更让好的持久化的方法:1️⃣数据存储到文件里2️⃣数据存入数据可中
1️⃣引入 JDBC 依赖:
mysql
mysql-connector-java
5.1.49
2️⃣建库建表
create table messages (`from` varchar(255), `to` varchar(255), `message` varchar(2048));
3️⃣修改代码
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;
import static java.lang.System.load;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2023-06-02
* Time: 19:56
*/
class Message {
// 这几个属性必须设置 public !!!!
// 如果设置 private, 必须生成 public 的 getter 和 setter !!!
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List messageList = new ArrayList<>();
//private List messageList = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 "获取所有留言消息"
// 需要返回一个 json 字符串数组. jackson 直接帮我们处理好了格式.
List messageList = load();
String respString = objectMapper.writeValueAsString(messageList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respString);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 "提交新消息"
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
save(message);
System.out.println("消息提交成功! message=" + message);
// 响应只是返回 200 报文. body 为空. 此时不需要额外处理. 默认就是返回 200 的.
}
// 这个方法用来往数据库中存一条记录
private void save(Message message) {
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("2222");
try {
Connection connection = dataSource.getConnection();
String sql = "insert into message values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
statement.executeUpdate();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 这个方法用来从数据库查询所有记录
private List load() {
List messageList = new ArrayList<>();
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("2222");
try {
Connection connection = dataSource.getConnection();
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet 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");
messageList.add(message);
}
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
return messageList;
}
}
通过上述代码就已经写出一个很简单的网站;未来写的复杂网站都是这一套逻辑
1️⃣约定前后端交互接口
2️⃣实现服务器代码(通常回操作数据库)
3️⃣实现客户端代码(通常会使用 ajax 构造请求,并使用一些 js 的 webapi 操作页面内容)