目录
掌握如下三个类,就可以完成Servlet的大部分开发了
- HttpServlet
- HttpServletRequest
- HttpServletResponse
URI:唯一资源标识符
URL:唯一资源定位/地址符
HttpServlet
HttpServlet继承这个类,重写里面的方法,目的就是为了把咱们子集定义的代码,“插入到”tomcat中,HttpServlet的常用方法如下:
方法名称
|
调用时机
|
init
|
在
HttpServlet
实例化之后被调用一次,初始化
|
destory
|
在
HttpServlet
实例不再使用的时候调用一次,释放资源
|
service
|
收到
HTTP
请求的时候调用
|
doGet
|
收到
GET
请求的时候调用
(
由
service
方法调用
)
|
doPost
|
收到
POST
请求的时候调用
(
由
service
方法调用
)
|
doPut/doDelete/doOptions/...
|
收到其他请求的时候调用
(
由
service
方法调用
)
|
- 直接干掉Tomcat进程,完全来不及调用destory的;
- 通过8005管理端口,给Tomcat发送一个“停机”指令,这个时候是能够执行destory的。
init / destory / service 这三个方法都不需要手动调用,会被tomcat在合适的时机,自动调用,咱们写好代码,让别人来帮忙调用,这种方式就叫做 “框架” ,也就是一个程序的主体部分,都已经被其他大佬们写完了,有些细节内容,允许咱们插入咱们自己写的自定义的逻辑.
HttpServletRequest
方法
|
描述
|
String getProtocol()
|
返回请求协议的名称和版本。
|
String getMethod()
|
返回请求的
HTTP
方法的名称,例如,
GET
、
POST
或
PUT
。
|
String getRequestURI()
|
从协议名称直到
HTTP
请求的第一行的查询字符串中,返回该
请求的
URL
的一部分。
|
String getContextPath()
|
返回指示请求上下文的请求
URI
部分。
|
String getQueryString()
| 返回包含在路径后的请求 URL 中的查询字符串。 |
InputStream
getInputStream()
|
用于读取请求的
body
内容
.
返回一个
InputStream
对象
.
|
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
。
|
上述的方法都是get系列方法(都是读方法),没有set系列(没有写方法),当前拿到的HttpServletRequest,这些数据的内容已经确定下来了,程序员是不应该修改的.
前端将数据交给后端
除了query string之外,还可以通过http请求的body来传递参数(POST)。
(1)直接通过form表单
(body的格式就是query string的格式)
Content-Type:application/x-www-form-urlencoded
(2)直接使用json
(body的格式就是json)
Content-Type:application/json
这三种方式本质上是等价的,都是把键值对数据交给服务器,前两种方法servlet天然支持的,json这种方法需要引入第三方库。
在java中,json的第三方库是非常多的,这里我们使用jackson(jackson是spring官方推荐的库,也被spring集成起来了)
步骤如下:
1)下载导入jackson到项目中,通过maven
Maven Repository: Search/Browse/Explore (mvnrepository.com)
(2)使用jackson
一个类两个方法
ObjectMapper
- 把json字符串,映射成java的一个对象(read方法)
- 把一个java对象,映射成json字符串(write方法)
Request request = objectMapper.readValue(req.getInputStream(),Request.class);1.核心工作就是把json字符串,映射成java 对象,参数就是json字符串(json字符串是在http的body中的,就需要通过HttpServletRequest中的getInputStream来获取到)
此处把这个流对象直接传给readValue,readValue内部就会读取InputStream中的所有数据(http请求的body,也就是json字符串),进一步尝试解析
2.按照json的格式,进行解析,把json字符串,解析成map(键值对)
3.把map转换成对象,在方法的第二个参数
如下示例:
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.io.ObjectInputValidation;
class Request()
{
public String username;
public String password;
}
class Response{
public boolean ok;
}
@WebServlet("/json")
public class JsonParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
Request request = objectMapper.readValue(req.getInputStream(),Request.class);
System.out.println("username="+request.username);
System.out.println("password="+request.password);
Response response=new Response();
response.ok=true;
//把响应对象转成json字符串
String respJson=objectMapper.writeValueAsString(response);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respJson);
}
}
HttpServletResponse
核心方法
方法
|
描述
|
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
中写入二进制格式数据
|
错误页面
resp.sendError(404,"这个页面是一个错误页面")
设置网页自动刷新时间
resp.setHeader("refresh","1");
构造重定向相应
resp.setStatus(302); resp.setHeader("Location","https://www.baidu.com");
header需要有一个Location属性,描述要跳转到哪里 ,除了这种写法外,还有如下另一种方法:
resp.sendRedirect("https://www.baidu.com");
使用ajax,需要先引入JQeury第三方库
链接如上,选择如下链接
在script标签引入jquery
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
js发起http请求
// 把用户填写的内容,发送给服务器,让服务器来保存
// $ 是jquery提供的全局变量,ajax是$的一个方法
// ajax的参数是一个js对象,可以有很多属性
let body={
"from":from,
"to":to,
"message":message
}
//上述body是一个js对象,还需要转换成json字符串
let jsonString = JSON.stringify(body)
$.ajax({
type:'post',
url:'message',
contentType:'application/json; charset=utf8',
data:jsonString,
//这里的body与上面的body不是同一个,是响应报文的正文
success:function(body){
// 这个回调就是收到响应之后要执行的代码了
}
});
此处success回调函数,不是立即执行的,而是在浏览器收到服务器返回的成功这样的响应的时候,才会执行function ,这个函数的第一个参数,是响应数据的body中的内容。
服务器端对js发起的http请求进行处理
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;
class Message{
// 确保java代码中类的属性名字和json中的属性名字保持一致,才能够自动填充
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<Message> messageList = new ArrayList<>();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用jackson读取前端发来的数据,把这个数据保存到服务器这边
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
System.out.println("请求中收到的message:"+ message);
//保存数据最简单的是直接内存中存储,使用集合类
//但是这样做一旦重启服务器,一切数据都没有了,一般存储到数据库中
messageList.add(message);
//返回一个响应
resp.setStatus(200);
resp.getWriter().write("ok");
}
}
这里启动服务器后,不能直接访问/message,需要访问.html路径
当浏览器要向服务器获取资源, 服务器方使用List类型的数组存储历史数据,转成的json字符串就是一个json数组,jackson自动遍历List里的每个元素,把每个元素分别转成json字符串。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(200);
resp.setContentType("application/json;charset=utf-8");
String respJson = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respJson);
}
上述代码setStatus和setContentType必须在getWriter前面,否则不会生效
$.ajax{
type:'get',
url:'message',
success:function(body){
//处理服务器返回的响应数据。(json格式的数组)
}
}
//当响应中,header带有ContentType:"application/json",JQuery就会自动把json字符串解析成js对象了,如果没有带ContentType:"application/json"就需要通过js代码JSON.parse方法手动把json字符串转成js对象
前端获取后端数据,添加到当前页面的末尾,代码示例:
$.ajax{
type:'get',
url:'message',
success:function(body){
//处理服务器返回的响应数据。(json格式的数组)
//由于响应中已经有ContentType:"application/json"了,就不需要使用parse方法手动转换了
//body = JSON.parse(body);
//拿到 container这个元素
let containerDiv = document.querySelector('.container');
//处理服务器返回的响应数据。(json格式的数组了)
for(let i=0;i<body.length;i++)
{
//body是一个数组,此时的message也就是js对象了
//这个message对象里有三个属性:from、to、message
let message = body[i];
//根据message对象构建html片段,把这个片段给显示到网页上
//createElement 方法就能构造一个html标签
//此时就得到了<div></div>
let div = document.createElement('div');
//还需要给这个div设置一个属性
div.className = 'row';
//设置内容
div.innerHTML = message.from + " 对 " + message.to + " 说:" +message.message;
//将这个div添加到containerDiv末尾
containerDiv.appendChild(div);
}
}
前后端交互:引入数据库(存储数据)
1)引入数据库依赖
Maven仓库链接
Maven Repository: Search/Browse/Explore (mvnrepository.com)
搜索MySQL,选择如下:
这里选择5.1.47版本
复制如下代码
复制到pom.xml里
2)建库建表
create database if not exists message_wall charset utf8;
use message_wall;
-- 删表的目的是为了防止之前数据库里有一样的表
drop table if exists message;
use message_wall;
create table message(`from` varchar(1024),`to` varchar(1024),message varchar(1024));
3)编写数据库代码
// 1. 创建数据源
private DataSource dataSource = new MysqlDataSource();
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3307/message_wall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("root");
// 2. 建立连接
Connection connection = dataSource.getConnection();
// 3. 构造 SQL
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);
// 3. 执行 SQL
statement.executeUpdate();
// 4. 回收资源
statement.close();
connection.close();
整体代码
MessageServlet.java
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;
class Message {
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();
// 引入数据库, 此时 messageList 就不再需要了.
// private List<Message> messageList = new ArrayList<>();
private DataSource dataSource = new MysqlDataSource();
@Override
public void init() throws ServletException {
// 1. 创建数据源
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3307/message_wall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("root");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 读取前端发来的数据, 把这个数据保存到服务器这边.
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
System.out.println("请求中收到的 message: " + message);
// 最简单的办法, 直接在内存中保存. 可以使用一个集合类, 把所有收到的 message 都存到内存中.
// 很明显, 保存到内存, 并非是一个非常合理的办法. 后续一旦重启服务器, 数据丢失了.
// 相比之下, 把这个数据持久化存储到数据库中, 更科学的.
// messageList.add(message);
// 插入数据库
try {
save(message);
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 返回一个响应
resp.setStatus(200);
resp.getWriter().write("ok");
// resp.setContentType("application/json");
// resp.getWriter().write("{ ok: true }");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过这个方法来处理当前获取消息列表的 get 请求. 不需要解析参数, 直接返回数据即可.
resp.setStatus(200);
resp.setContentType("application/json; charset=utf8");
// 从数据库查询
List<Message> messageList = null;
try {
messageList = load();
} catch (SQLException e) {
throw new RuntimeException(e);
}
String respJson = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respJson);
}
private void save(Message message) throws SQLException {
// 通过这个方法把 message 插入到数据库中
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
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);
// 3. 执行 SQL
statement.executeUpdate();
// 4. 回收资源
statement.close();
connection.close();
}
private List<Message> load() throws SQLException {
// 通过这个方法从数据库读取到 message.
// 1. 建立连接
Connection connection = dataSource.getConnection();
// 2. 构造 SQL
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
// 3. 执行 sql
ResultSet resultSet = statement.executeQuery();
// 4. 遍历结果集合
List<Message> messageList = new ArrayList<>();
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);
}
// 5. 回收资源
resultSet.close();
statement.close();
connection.close();
// 6. 返回 messageList
return messageList;
}
}
message_wall.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>
<style>
/* * 通配符选择器, 是选中页面所有元素 */
* {
/* 消除浏览器的默认样式. */
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 600px;
margin: 20px auto;
}
h1 {
text-align: center;
}
p {
text-align: center;
color: #666;
margin: 20px 0;
}
.row {
/* 开启弹性布局 */
display: flex;
height: 40px;
/* 水平方向居中 */
justify-content: center;
/* 垂直方向居中 */
align-items: center;
}
.row span {
width: 80px;
}
.row input {
width: 200px;
height: 30px;
}
.row button {
width: 280px;
height: 30px;
color: white;
background-color: orange;
/* 去掉边框 */
border: none;
border-radius: 5px;
}
/* 点击的时候有个反馈 */
.row button:active {
background-color: grey;
}
</style>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body>
<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 id="submit">提交</button>
</div>
<!-- <div class="row">
xxx 对 xx 说 xxxx
</div> -->
</div>
<script>
// 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示.
// 点击的时候, 获取到三个输入框中的文本内容
// 创建一个新的 div.row 把内容构造到这个 div 中即可.
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
let rowDiv = document.createElement('div');
rowDiv.className = 'row message';
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
// 4. 把用户填写的内容, 发送给服务器. 让服务器来保存.
// $ 是 jquery 提供的全局变量. ajax 就是 $ 的一个方法.
// ajax 的参数是一个 js 对象, 可以有很多属性
let requestBody = {
"from": from, // from 变量里的值, 就是第一个输入框的内容, "张三"
"to": to, // to 变量的值, 就是第二个输入框的内容, "李四"
"message": msg // msg 变量的值, 就是第三个输入框的内容, "我喜欢你很久了"
};
// 上述 body 是一个 js 对象, 还需要转成 json 字符串.
let jsonString = JSON.stringify(requestBody);
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: jsonString,
success: function(responseBody) {
// 这个回调就是收到响应之后要执行的代码了.
// 前端使用 console.log 打印日志到控制台. (chrome 开发者工具的控制台)
console.log("responseBody: " + responseBody);
}
});
}
// 直接在 script 里面写的 js 代码, 就是在页面加载时被执行到的.
// 发起一个 get 请求, 从服务器获取到数据
// get 请求不需要 body, 也就不需要上述 data 和 contentType 属性了.
$.ajax({
type: 'get',
url: 'message',
success: function(body) {
// 由于响应中已经有 Content-Type: application/json 了, 就不需要使用 parse 方法手动转换了.
// body = JSON.parse(body);
// 拿到 container 这个元素
let containerDiv = document.querySelector('.container');
// 处理服务器返回的响应数据. (json 格式的数组了)
for (let i = 0; i < body.length; i++) {
// body 是一个数组, 此时 message 也就是 js 对象了.
// 这个 message 对象里, 有三个属性, from, to, message
let message = body[i];
// 根据 message 对象构建 html 片段, 把这个片段给显示到网页上.
// createElement 方法就能构造出一个 html 标签.
// 此时就得到了 <div></div>
let div = document.createElement('div');
// 还需要往里面设置一个 属性 , class="row" (设置这个属性, 是为了让 css 能够给这个元素设置一些样式)
// 此时就得到了 <div class="row"></div>
div.className = 'row';
// 给这个 div 里设置内容
// 此时就得到了 <div class="row">张三 对 李四 说: 我喜欢你很久了</div>
div.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;
// 把 div 添加到 containerDiv 的末尾
containerDiv.appendChild(div);
}
}
});
</script>
</body>
</html>
利用Cookie和Session实现登录逻辑
Cookie是客户端存储数据的机制
Session是服务器存储数据的机制(不算持久化存储)
1.登录页面(html)
2.通过一个Servlet处理上述的登录请求
通过这个Servlet读取用户名和密码,并且验证是否登录成功。
如果登陆成功,就会给当前用户,创建一个会话 ,并且把得到的sessionid,通过cooki返回给客户端,客户端就把cookie保存起来了。
3.网站主页,通过另一个servlet生成的动态页面
在这个页面中,就会把刚才这里的用户数据给显示到页面上。
getSession(true)
这个方法,就是根据请求的cookie中的sessionid,查询服务器的hash表,找到对应的session对象。如果cookie中没有sessionid(首次登录的时候,就是没有的)或者sessionid没有查找到对应的session对象,就可以创建出一个session对象出来。
参数为true,允许不存在时自动创建;false不能创建,直接返回null
//可以给会话中保存一些自定义的数据,通过Attribute的方式来保存 HttpSession session = req.getSession(true); //此处Atrribute也是键值对,这里的内容存储什么都可以 session.setAttribute("username",username); session.setAttribute("loginTime",System.currentTimeMillis()); //此处相当于登录成功,让页面跳转网站首页 resp.sendRedirect("index");
此处的getSession会创建新会话
1)生成sessionid和HttpSession对象
2)把上述sessionid和HttpSession对象保存到内存hash表中
3)把sessionid设置到响应报文的header中的 Set-Cookie字段
浏览器拿到响应,就会把这个Set-Cookie的内容保存到浏览器的Cookie中 。