目录
1.Servlet运行原理
本篇内容学习在 Servlet 的代码中对应的 doGet 代码是如何被调用的? ?响应如何返回给浏览器?
首先要明确: Servlet是 HTTP应用层里进行的一些操作,属于上层用户态操作;
而底层依赖着内核态的操作:传输层、网络层、数据链路层、物理层,基础网络通信。
我们的实现是在 Tomcat 基础上运行的,浏览器给服务器发送请求时 ,Tomcat (HTTP Servlet)作为 HTTP 服务器(HTTP 协议作为一个应用层协议, 需要底层协议栈来支持), 就可以接收到请求.下图描述了该请求发送的过程:
在用户态: Tomcat是一个应用程序(基于java实现,属于Java进程)是用户态的一个普通进程;
在内核态:Tomcat是运行在JVM之上的,而servlet是Tomcat上层的内容,由Tomcat完成底层的工作,再交由servlet去做进一步的处理,用户在servlet体系下,去完成应用层的工作。(也即在servlet体系下,根据请求计算响应,通过servlet和Tomcat进行交互,Tomcat拿到数据后,和浏览器进行网络传输(封装分用))
打开一个浏览器(用户主机上的应用程序),浏览器也即是客户端的角色,当用户访问一个浏览器时,浏览器就会通过一系列操作构造一个HTTP请求,详细的过程如图:
(1)(发送请求)在客户端(用户主机上)的过程:
- 把应用层的 HTTP请求 交给传输层,将其封装成一个TCP数据报(TCP报头+HTTP请求)
- TCP数据报 交给网络,由IP协议进行封装成IP数据报(IP报头+TCP数据报)
- 把 IP数据报 交给数据链路层,由以太网协议封装成以太网数据帧(帧头+IP数据报+帧尾)
- 最后把以太网数据帧(二进制数据)交给物理层,通过把1、0转变成高/低电平(高低电磁波信号),再由网线、光纤等硬件设备将其传输出去
数据在传输过程中,会经过n次转发,最终才能到达目的地(网络传输)
(2)(接收请求并解析,计算响应)服务器上的过程:
- 先是服务器的物理层收到数据,当其转化为以太网数据帧时,向上传递给数据链路层
- 数据链路层接收到以太网数据帧,进行帧头、帧尾的去除操作,数据变成了IP数据段,再向上传给网络层
- 网络层接收到IP数据报,去除IP报头,变为TCP数据段,传递给传输层
- 传输层拿到数据后,去除TCP报头,数据变成了原始的HTTP请求,再将其向上传递给应用层的Tomcat服务器
- Tomcat接收到HTTP请求之后,针对HTTP协议做进一步的处理:
- 根据协议里的路径URL,第一级路径ContextPath 指定要访问哪个webapp(网站)
- 第二级路径ServletPath 指定我们要访问的是哪个HTTPServlet类
- 根据类里的doPost/doGet等方法,执行对应代码(也即是根据请求计算响应)
以上便是在浏览器访问页面的整个实现过程,涉及客户端和服务器之间的网络传输,之前的网络原理涉及的内容是应用层一下的各层之间的交互过程,而servlet这里,重点关注的是应用层Tomcat的交互
(3) 返回响应:
- doGet / doPost 执行完毕后, Tomcat 自动把 HttpServletResponse 对象转换成一个字符串(符合 HTTP 协议), 并通过 Socket 把这个响应发送出去.
- 响应数据在服务器的主机上通过网络协议栈层层 封装(也即是(1)发送请求的过程), 最终得到(以太网数据帧)二进制 bit 流, 通过物理层硬件设备转换成光/电信号传输出去.
- 承载信息的光/电信号通过互联网上的网络设备, 最终到达浏览器所在的主机
- 浏览器主机收到光/电信号, 又通过网络协议栈逐层进行分用, 层层解析, 最终还原成HTTP 响应, 并交给浏览器处理.
- 浏览器也通过 Socket 读到响应(字符串), 按照 HTTP 响应的格式来解析这个响应. 并把body 中的数据按照一定的格式显示在浏览器的界面上.
2.Tomcat伪代码
伪代码并不能真正的编译运行,只是为了便于理解,程序员编写的实现某个功能的大致逻辑体系,以助于更好地便于缕清思路,构造出完整的实现逻辑。
1、初始化 / 准备工作:加载 Servlet 实例(从制定目录webapp目录中找到要加载的类)
(加载分懒汉模式和饿汉模式,并非Tomcat启动start就调用,根据情况而定)
2、处理请求:根据 URL找到匹配的 Servlet 类,再调用 Servlet 里面对应的方法
(1)Tomcat初始化流程 的伪代码
class Tomcat {
// 用来存储所有的 Servlet 对象
private List<Servlet> instanceList = new ArrayList<>();
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
Class<Servlet>[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class<Servlet> cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
Servlet ins = cls.newInstance();
instanceList.add(ins);
}
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.init();
}
// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
(2)Tomcat 处理请求流程 的伪代码
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
// 直接使用我们学习过的 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
// 最终找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
(3)Servlet 的 service 方法的实现 的伪代码
class Servlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
......
}
}
- 1、init:对象创建好就执行初始化。用户也可重写init方法,来执行一些初始化的操作。
- 2、destroy:退出主循环,Tomcat 结束运行之前回调用。主要是用来 释放资源。
- 3、service:在处理请求的阶段来调用,每次来个请求都需要调用一次 service。
2.Servlet API详解
重点掌握HttpServlet、HttpServletRequest、HttpServletResponse这三个类
2.1HttpServlet
写 Servlet 代码时, 先创建类, 继承自 HttpServlet, 并重写其中的某些方法.使得Tomcat可以调用。 该过程和继承有关,会涉及到多态。如何理解多态???
- 集合类: List list = new ArrayList<>();
- 多线程:class Mythred extends Thread{ 重写 run方法 }
- Servlet 也是多态!因为我们自己写的代码也是通过继承重写的方式来实现的。
实际开发中,主要重写 doXXX 方法,方法的调用时机(称为 "Servlet 生命周期")
注意!!! HttpServlet 实例只在程序启动时创建一次. 而不是每次收到 HTTP 请求都重创建
面试题:Servlet的生命周期:
servlet包括怎么出现的、怎么消失的、以及中间的过程和状态
- servlet在实例化之后调用一次init
- servlet每次收到请求,调用一次sevice
- servletz在销毁之前,调用一次destory
变量生命周期:怎么出现的、怎么消失的
(1)构造一个post请求
浏览器中直接输入URL属于get请求;而post请求的构造需要利用form表单或者ajax(需要创建一个html文件,webapp目录下),注意!!!要想使用ajax,必须引入jQuery: jQuery引入
若不想引入依赖,可使用第三方工具postman,可以任意构造HTTP请求:
1)test.html
注意!! 使用ajax构造请求时路径不加/,访问test.html文件的路径是:/classServlet/test.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>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
$.ajax({
type: 'post',
url: 'method',
success: function(body) {
console.log(body);
}
});
</script>
</body>
</html>
2)methodServlet.java(构造post请求,实现dopost方法)
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("/method")
public class methodServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("post响应");
}
}
浏览页面:127.0.0.1:8080/classServlet/test.html
classServlet 是 contextPath
2.2 HttpServletRequest
Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并按照 HTTP 协议的格式把字符串解析成HttpServletRequest 对象.
注意!!!
String getRequestURI() :返回该请求的 URL 的一部分。
URL:全球资源定位器(Uniform Resource Locator)
URI:唯一资源标识符(Uniform Resource Identifier)
都表示网上唯一资源, URL 表示的是这个资源的位置,而 URI 表示的是这个资源的 ID。
getQueryString() 得到的是 完整的查询字符串,而下面三个方法把 查询字符串 解析为键值对:
- getParameterNames是得到所有key,以Enum枚举方式表示 ;
- getParameter根据key得到value;
- getParameterValues如果存在多个key相同的情况,得到value是一个数组的形式。
获取请求报头(键值对),servlet解析报头,得到键值对的结构getHeaderName:获取报头中的所有key,getHeader:根据key获取value
(1)打印请求信息
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.Enumeration;
@WebServlet("/showRequest")
public class showRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(req.getProtocol()); //返回请求协议的名称和版本
stringBuilder.append("<br>");
stringBuilder.append(req.getMethod()); //返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
stringBuilder.append("<br>");
stringBuilder.append(req.getRequestURI()); //,返回该请求的 URL 的一部分。
stringBuilder.append("<br>");
stringBuilder.append(req.getContextPath()); //返回指示请求上下文的请求 URI 部分。
stringBuilder.append("<br>");
stringBuilder.append(req.getQueryString()); //返回包含在路径后的请求 URL 中的查询字符串。
stringBuilder.append("<br>");
stringBuilder.append("<h3>header 部分</h3>");
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = req.getHeader(headerName);
stringBuilder.append(headerName + ":" + headerValue + "<br>");
}
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write(stringBuilder.toString());
}
}
得到如下页面:
(2)获取 GET 请求中的参数,使用getParameter
浏览器通过 query string 给服务器传递了两个参数, userId 和 classId,在服务器端就可以通过 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 getParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
// 假设浏览器传来一个请求: getParameter?userId=10&classId=20
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");http://
resp.getWriter().write("userId=" + userId +",classId=" + classId );
}
}
浏览器输入:127.0.0.1:8080/classServlet/getParameter?userId=10&classId=20,得到页面:
(3)获取 POST 请求中的参数
服务器如何获取参数(post请求body格式):
- x-www-form-urlencoded (使用form表单或postman在前端构造此请求)
- form-data
- json
1)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("/postGetParameter")
public class postGetParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//假设传过来的参数 userId=10&classId=20 服务器也是通过req.getPararmeter获取内容
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");http://
resp.getWriter().write("userId=" + userId +",classId=" + classId );
}
}
webapp目录下利用form表单完成的静态页面test.html:127.0.0.1:8080/classServlet/postGetParameter
<!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>Document</title>
</head>
<body>
<form action="postGetParameter" method="post">
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</body>
</html>
使用postman获取请求
2)json获取参数——借助第三方库Jackson(Spring官方推荐库)
在浏览器前端代码中,,通过js构造json格式的请求;后端代码中,通过Jackson来处理
<!--构造json格式的请求 不适用form,使用ajax-->
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
let userIdInput = document.querySelector('#userId');
let classIdInput = document.querySelector('#classId');
let button = document.querySelector('#submit');
button.onclick = function() {
$.ajax({
type: 'post',
url: 'postJson',
contentType: 'application/json',
data: JSON.stringify({
userId:userIdInput.value,
classId:classIdInput,
}),
success: function(body) {
console.log(body);
}
});
}
</script>
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 int userId;
public int classId;
}
@WebServlet("/postJson")
public class postJsonServlet extends HttpServlet {
//创建一个Jackson核心对象
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取body中的请求,使用ObjectMapper解析成需要对对象;
// readValue:把JSON格式字符串转为java对象
// 第一个参数:表示对那个字符串进行转换,可写成String对象,也可InputStream对象
// 第二个参数:把这个JSON格式字符串,转为java对象
User user = objectMapper.readValue(req.getInputStream(),User.class);
resp.getWriter().write("userId=" + user.userId +",classId=" + user.classId );
}
}
readValue如何完成转换的???
- 先把getInputStream对应的流对象里面的数据读取出来
- 针对这个JSON字符串进行解析,字符串------>键值对
- 遍历键值对,获取每一个key,根据key和类里面的属性名称对比一下,看是否有匹配的
- 若有匹配的属性,则把当前key对应的value赋值给User类的属性中
- 若无匹配,就跳过,取下一个key
反射:根据类对象获取里面的关键信息
使用ajax提交数据,默认不会跳转页面 ,在控制台中会显示服务器的相应数据
2.3 HttpServletResponse
Tomcat 把 HttpServletResponse 对象(servlet中利用doxx方法计算出来的响应)按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket 写回给浏览器.
(1) 服务器返回的状态码,只是告诉浏览器当前的状态,不影响浏览器正常显示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 setStateServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(404);
resp.getWriter().write("hello");
}
}
(2) 让浏览器每秒钟自动刷新一次. 并显示当前的时间戳.(自动刷新header.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;
@WebServlet("/autoRefresh")
public class AutoRefresh extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Refresh", "1");
resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
}
}
(3)构造重定向响应:返回一个重定向 HTTP 响应, 自动跳转到另外一个页面
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");
}
}
3.案例分析——实现服务器版本的表白墙
服务器内存版(非持久化存储)
实现一个服务器,需要考虑提供的什么服务?服务如何触发?如何实现交互??
对于本项目来说:表白墙主要提供两个接口
- 1. 告诉服务器当前的留言是什么数据
- 触发:用户点击提交时,会给服务器发送一个Http请求,服务器保存请求
- 2. 从服务器获取到,当前有哪些留言
- 触发:页面加载获取所有留言的数据
3.1代码编写
一个项目的创建流程:
(1)创建项目
(2)引入依赖:pom.xml文件内写入依赖
(3)构建目录
(4)编写代码
(5)打包部署
(2)引入依赖:pom.xml文件内写入依赖
<dependencies>
<!-- servlet依赖 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>
<!-- Jackson依赖 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.1</version>
</dependency>
</dependencies>
(4)编写代码和打包部署
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("/message")
public class MessageServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理提交消息请求
resp.getWriter().write("helloPost");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取消息列表
resp.getWriter().write("helloGet");
}
}
(5)实现代码:后端逻辑
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{ //创建一个类Message表示提交的一个请求记录
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();//通过ObjectMapper类解析(构造)JSON对象
List<Message> messageList = new ArrayList<>();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理提交消息请求
//1.从请求流对象中读取当前的数据内容,再把读取到的对象转成Message.class形式的对象
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.保存数据(保存到服务器内存),需提前创建一个对象用于保存数据
messageList.add(message);
//3.通过setContentType告知页面返回数据的格式是JSON
resp.setContentType("application/json;charset=utf8"); // jquery ajax自动把字符串转化为js对象
resp.getWriter().write("{\"ok\":true}");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取消息列表,把所有内容返回给客户端,,objectMapper把java对象转为JSON对象
String jsonString = objectMapper.writeValueAsString(messageList);
System.out.println("jsonString:"+jsonString);
resp.setContentType("application/json;charset=utf8"); // jquery ajax自动把字符串转化为js对象
resp.getWriter().write("helloGet");
}
}
使用postman进行连接检查:
(6)前端代码
<!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标签,引入CSS样式 -->
<style>
*{
/* 首先先去除浏览器样式 */
/* 将 内外边距设置为0,设置盒子模型为向内挤压 */
margin: 0;
padding: 0;
box-sizing: border-box;
}
.containner{
width: 100%;
}
h3{
/* 文本居中 */
text-align: center;
/* 上下边距为 20px,左右边距为0 */
padding: 20px 0;
font-size: 24px;
}
p{
text-align: center;
color: #666;
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: 17px;
}
.row input{
width: 300px;
height: 40px;
line-height: 40px;
font-size: 20px;
text-indent: 0.5em;
outline: none;
}
.row #submit{
width: 360px;
height: 40px;
font-size: 20px;
line-height: 40px;
margin: 0 auto;
color: white;
background-color: orange;
border: none;
border-radius: 15px;
outline: none;
}
/* 当鼠标点击按钮的时候,会改变按钮颜色 */
.row #submit:active{
background-color: grey;
}
</style>
<div class="container">
<h3>表白墙</h3>
<p>输入后点击提交,会将信息显示在表格中</p>
<br>
<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>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
//加入ajax代码,此处要加入的逻辑有两部分:
//1.点击提交按钮时,ajax要构造数据发送给服务器
//2.页面加载时,从服务器获取消息列表,并在界面上直接显示
function getMessages() {
$.ajax({
type: 'get',
url: 'message',
success: function(body) {
let newDiv = document.createElement('div');
for(let message of body) { //依次取数组中的每个元素
newDiv.innerHTML = from + "对" + to +"说:" + say;
newDiv.className = 'row';
let container = document.querySelector('.container'); // 将新建节点,挂在 container 这个节点下面
container.appendChild(newDiv);
}
}
});
}
//当用户点击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 say = inputs[2].value;
if(from == ''|| to == '' || say == ''){
// 用户填写的数据,并不完整。所以不提交。
return;
}
// 2、生成一个新的 div,内容就是 三个 input 文本框的内容拼接
// 再把这个 元素,挂在DOM树上。
let newDiv = document.createElement('div');
newDiv.innerHTML = from + "对" + to +"说:" + say;
newDiv.className = 'row';
// 将新建节点,挂在 container 这个节点下面
let container = document.querySelector('.container');
container.appendChild(newDiv);
//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",
data: JSON.stringify(body),
success: function(body) {
alert("消息提交成功");
},
error: function() {
alert("消息提交失败!");
}
});
}
</script>
</body>
</html>
127.0.0.1:8080/messageServlet/test.html
test.html前端页面的书写只能保证写入的数据能够正常显示出来,但不能保存,不能刷新,服务器重启也会导致页面写入的内容不见了
对象和JSON对象之间的转换:
Java:
objectMapper.readValue :json字符串转成对象
objectMapper.writeValueAsString:对象转成json字符串
JS:
JSON.parse:json字符串转成对象
JSON.stringify:对象转成json字符串
硬盘能持久存储,内存不能持久存储,也有一种介于硬盘和内存之间的设备(像内存一样访问快、也能像硬盘一样持久存储)
使用JDBC基本流程:
- 创建数据源
- 和数据库建立连接
- 构造SQL语句
- 执行SQL语句
- 如果是查询语句,需要遍历结果集,若是插入/删除/修改,则不需要
- 关闭资源,释放连接
为解决上述数据内容的丢失,引入数据库进行数据保存。保证页面刷新后,数据不丢失,但不能保证服务器重启后数据不丢失(把数据保存到硬盘上(可以是文件、也可是数据库))
(0)第一步在pom.xml文件中添加数据库依赖(确保和自己的版本对应)
(1)选中数据库java102,创建表message(from、to 是表中的关键字,需要加上反引号``)
(2)DButil - 数据库访问程序 DBUtil.java文件
主要是简化MessageMysqlServlet中的程序:
- 将 数据库连接,创建数据源,资源释放,具体实现细节给封装成一个类。
- 来供 MessageServlet 中的程序使用
import com.mysql.cj.jdbc.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/java102?characterEncoding=utf8&useSSL=false"; //连接数据库的地址
private static final String USERNAME = "root"; //用户名
private static final String PASSWORD = "123456"; //密码
private static DataSource dataSource = null; //创建实例dataSource
private static DataSource getDataSource() { //单例模式中的——懒汉模式
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 void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
是否存在线程安全的问题???
看当前调用的方法doXXX处于什么环境,当处于多线程环境下是存在线程安全的问题的
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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/messageMysql")
public class MessageMysqlServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();//通过ObjectMapper类解析(构造)JSON对象
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理提交消息请求
//1.从请求流对象中读取当前的数据内容,再把读取到的对象转成Message.class形式的对象
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.保存数据(保存到服务器内存),需提前创建一个对象用于保存数据
//3.通过setContentType告知页面返回数据的格式是JSON
resp.setContentType("application/json;charset=utf8"); // jquery ajax自动把字符串转化为js对象
resp.getWriter().write("{\"ok\":true}");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取消息列表,把所有内容返回给客户端,,objectMapper把java对象转为JSON对象
List<Message> messageList = load();
String jsonString = objectMapper.writeValueAsString(messageList);
System.out.println("jsonString:"+jsonString);
resp.setContentType("application/json;charset=utf8"); // jquery ajax自动把字符串转化为js对象
resp.getWriter().write(jsonString);
}
private void save(Message message) {
//把消息保存到数据库
Connection connection = null;
PreparedStatement statement = null;
//1.和数据库建立连接
try {
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "insert into message values(?,?,?)"; //3个“?“进行占位
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 {
DBUtil.close(connection,statement,null);
}
}
private List<Message> load() {
List<Message> messageList = new ArrayList<>();
//1.从数据库中获取所有信息
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null; //定义一个resultSet存放结果
try {
connection = DBUtil.getConnection();
String sql = "select * from message"; //查询语句
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
//2.遍历结果集
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);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return messageList;
}
}
开发一个简单网站的基本步骤:
- 约定前后端交互的接口
- 开发服务器代码
- 编写servlet能够处理前端发来的请求
- 编写数据库代码,存储/获取关键信息
- 开发客户端代码
- 基于ajax构造请求以及解析响应
- 响应用户操作(点击按钮之后,触发给服务器发送请求的行为)