目录
🎁1 HttpServletRequest
💥1.1 通过 query string 来进行传递
🐷1.2 通过 body (form) 来进行传递
🛸1.3 通过 body(json) 来进行传递
🍘2. HttpServletResponse
👶2.1 为响应设置状态码 200
🍝2.2 header 实现自动刷新效果
🌏2.3 跳转到 搜狗页面
🚑3. 表白墙
🏳🌈3.2 小结
1 HttpServletRequest
前端给后端传递数据是非常常见的需求。HttpServletRequest 这个类,主要用来获取到请求的各个方面的信息,尤其是前端传来的自定义数据,如下文介绍的 query string、post body(form)、post body(json) 等。getParameter 是 Servlet 最常用的接口之一。
1.1 通过 query string 来进行传递
以下代码,前端通过 query string 传递 username 和 password 。query string 中的键值对是程序员自定义的!在 URL 中,键值对是通过字符串来表示的,tomcat 收到请求之后,自动把 url 中的键值对转换成 Map,方便人们查询。
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 的键值对中,value 的值是中文可以吗?
在 URL 中,query string 如果包含中文或者特殊字符,务必使用 urlencode 的方式进行转码!!!如果直接写中文或者特殊符号,会存在非常大的风险!!!!在不转码的情况下,有些浏览器或者 http 服务器下,对中文支持不好的话,就会出现问题。
比如 张三,就需要转码成 %E5%BC%A0%E4%B8%89%0A
1.2 通过 body (form) 来进行传递
body 里存的数据格式,就和 query string 一样,但其 Content-Type 是 application/x-www-form-urlencoded,此时也是通过 getParameter 来获取键值对!
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 前端通过 body,以 form 表单的格式,把 username 和 password 传给服务器
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null){
System.out.println("username 这个 key 在 query string 中不存在!");
}
if(password == null){
System.out.println("password 这个 key 在 query string 中不存在!");
}
System.out.println("username = " + username +", password = "+ password);
resp.getWriter().write("ok!");
}
由于是个一个 Post 请求,使用 Postman 来构造。
如果 post 请求,发送的也是中文,结果会怎么样?
出现了乱码,那怎么样才能传入中文呢?
只需在 doPost 方法内,添加以下代码即可,指定传入参数的字符编码(给请求):
req.setCharacterEncoding("utf8");
1.3 通过 body(json) 来进行传递
这是未来开发中,最常用的开发方式。
json 中的数据是以键值对格式存在,由于 Servlet 不能解析 json,因此需要引入第三方库。这样子的第三方库有许多,常见的有:fastjson、gson、jackson 等,其中 jackson 属于 spring 官方指定产品。本篇文章中,使用 jackson。
Maven 中央仓库中,搜索 jackson,点开 Jackson Databind:
随便点开一个版本:
复制粘贴以下这段代码到 pom.xml 中:
打开 IDEA 中 maven 面板,刷新即可完成 jackson 的引入。
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 所做的事:
1. 解析 json 字符串,转换成若干键值对
2. 根据第二个参数 User.class,去找到 User 里所有 public 的属性,依次遍历(.class 是类对象,记载一个类里的各种信息)
3. 遍历属性,根据属性的名字,去已经解析的键值对里查询(通过反射这个机制完成,反射类似于照镜子,.class 的信息是人,通过照镜子,看到镜子里的自己)。查询属性名字是否存在对应的 value ,如果存在,就把 value 赋值给该属性。
2. HttpServletResponse
HttpServletResponse 这个类,表示一个 HTTP 响应。
方法 | 描述 |
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 中写入二进制格式数据 |
直接给例子感受一下吧:
2.1 为响应设置状态码 200
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(200);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("返回 200 响应!");
}
}
2.2 header 实现自动刷新效果
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("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 每隔 1 s 自动刷新一次
resp.setHeader("Refresh", "1");
resp.getWriter().write("time = "+System.currentTimeMillis());
}
}
2.3 跳转到 搜狗页面
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://sogou.com");
}
}
doGet 中的两行代码还可以简写成:
resp.sendRedirect("https://sogou.com");
介绍了 HttpServlet 和上面两个类之后,我们就具备写一个网站的基础了。下面的一节中,就带着同学们一起来写一个简单的网站——表白墙。
3. 表白墙
在 JavaScript 这篇文章的最后,给了一个表白墙的例子。这个案例存在以下问题:1. 随着网页一关,数据会丢失。2. 数据只能在本地看。
解决上述问题,只需引入后端服务器即可。每次当用户打开页面时,服务器加载当前已经提交过的表白墙数据;每当用户新增一个表白时,让数据提交给服务器,让服务器持久化保存。
首先将写好的前端代码放在 Webapp 目录下,与 WEB-INF 是同级的:
这样就可以通过以下方式访问:
接下来就可以写后端代码了,但在那之前,需要明确前后端交互接口,即网页给服务器发什么样的请求。请求具体怎么设定,规定一下字段、格式等;服务器给网页返回什么样的响应。响应具体怎么样设定,规定字段和格式等。
在表白墙这个例子中,前后端交互接口规定如下:
接口一:页面获取当前所有的留言信息
请求:
GET / message
响应:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
from:"从哪里来的",
to:"到哪里去",
message:"消息是啥"
},
{
from:"从哪里来的",
to:"到哪里去",
message:"消息是啥"
},
{
from:"从哪里来的",
to:"到哪里去",
message:"消息是啥"
},
]
json 中使用 [ ] 表示数组,[ ] 中的每个元素,是一个 { } json 对象,而每一个对象中,又有三个属性,分别是 from、to 以及 message。
接口二:提交新消息给服务器
请求:
POST / message
Content-type: application/json
{
from:"从哪里来",
to:"到哪里去",
message:"消息是啥"
}
响应:
HTTP/1.1 200 OK
先写一个大概的:
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.*;
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 LoveWall extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List<Message> 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 的
}
}
利用 postman 验证上述程序的可行性:
在前端代码中,加入以下内容:
<script>
function submit(){
......
......
// 4. 通过 ajax 构造 post 请求,把这个新的消息提交给服务器
// 此处的 body 是一个 js 对象
// js 对象通过 {} 的方式来表示
// {} 里面是一组键值对
// 键值对中的键,固定就是 String 类型!
let body = {
from:from,
to:to,
message:msg
};
jQuery.ajax({
type:'post',
url:'message',
contentType:"application/json;charset:utf8",
data:JSON.stringify(body),
success:function(body){
// 响应成功返回之后,调用的回调函数
console.log("消息发送给服务器成功!");
}
});
}
....
// 在页面加载的时候,希望从服务器获取所有的信息,并显示在网页中
jQuery.ajax({
type:'get',
url:'message',
// body 是收到响应的正文部分,是 json 数组
// 由于响应的 Content-Type 是 application/json,此时收到的 body 会被 jquery 自动地从 字符串
// 转换成 js 对象数组,此处就不需要手动进行 JSON.parse 了
// 此处的 body 已经是一个 JSON.parse 之后得到的 js 对象数组了
success:function(body){
// 标记获取已有信息放置的地方
let container = document.querySelector('.container');
for(let message of body){
let rowDiv = document.createElement('div');
rowDiv.className = "row";
rowDiv.innerHTML = message.from + "对"+message.to+"说"+message.message;
container.appendChild(rowDiv);
}
}
});
</script>
JS 中使用 JSON 这个特殊对象,完成对象和 json 字符串的转换:
JSON.stringify 把 js 对象转换成 json 格式字符串
JSONparse 把 json 格式字符串转成 js 对象。
在服务器不关闭的情况之下,多次刷新页面,之前提交的内容依旧存在:
根据前面的代码,我们知道服务器的数据是保存在一个 ArrayList 中的,即数据保存在内存中的。当服务器关闭再开启,内存数据就没有了。所有,为了让数据更好的持久化,最好把数据写到硬盘上,要么写到文件里,要么写到数据库中。下面便介绍后一种方式。
再次来到 Maven 中央仓库,引入 MySQL 依赖:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
mysql> create database lovewall charset utf8;
Query OK, 1 row affected (0.01 sec)
mysql> use lovewall;
Database changed
mysql> create table message(`from` varchar(100),`to` varchar(100),message varchar(1024));
Query OK, 0 rows affected (0.07 sec)
修改之前的代码如下:
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.*;
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 LoveWall extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
// 使用数据库来存储数据,那么就不需要以下这行代码了:
// private List<Message> messageList = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理 “获取所有留言信息”
// 需要返回一个 json 字符串数组,jackson 直接帮我们处理好了格式
List<Message> 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 List<Message> load() {
List<Message> messageList = new ArrayList<>();
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/lovewall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123");
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;
}
// 这个方法用来往数据库中存一条记录
private void save(Message message){
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/lovewall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123");
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();
}
}
}
mysql> select * from message;
+------------+------+----------+
| from | to | message |
+------------+------+----------+
| 金刚葫芦娃 | 蛇精 | 还我爷爷 |
| 地球 | 火星 | hello |
+------------+------+----------+
2 rows in set (0.00 sec)
这样一来,关闭服务器再打开时:
3.2 小结
写网站的套路总结如下:
1. 约定前后端交互接口
2. 实现服务器代码(通常会操作数据库)
3. 实现客户端代码(通常使用 ajax 构造请求,使用一些 js 的 webapi 操作页面内容)