Servlet 是一种实现动态页面的技术. 是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app.
Java Servlet 是运行在 Web服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页 .
Servlet用于开发动态页面 , 根据用户输入内容的不同 , 来返回出不同的页面结果 .
什么是动态页面 , 什么是静态页面呢 ?
- 静态页面也就是内容始终固定的页面. 即使用户不同/时间不同/输入的参数不同 , 页面内容也不会发生 ;
变化. (除非网站的开发人员修改源代码, 否则页面内容始终不变).- 动态页面指的就是 用户不同/时间不同/输入的参数不同, 页面内容会发生变化 .
一共需要七大步骤 .
maven叫做"构建工具" , 针对代码进行依赖管理 , 编译 , 打包 , 验证 , 部署等功能 , 可以视为是针对复杂项目进行管理的一个解决方案 .
maven自身的功能是很多的 , 我们主要使用 :
此处谈到的依赖 , 是编写Servlet程序所需要的依赖(Servlet的jar包) , 需要把这个jar下载导入到项目中 .
下载步骤 :
注意 : 首次使用 , 会标红 , 这是因为在首次使用时 , maven会从中央视仓库下载对应的jar包 , 这需要一定的时间 . 你也可是点击刷新 , 主动触发下载 !
<!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>
webapp 目录就是未来部署到 Tomcat 中的一个重要的目录. 当前我们可以往 webapp 中放一些静态资源, 比如 html , css 等.
在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动态资源.
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("/hello")
public class HelloServet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello world");
resp.getWriter().write("hello world");
}
}
doGet()方法不是咱们自己手动调用的 , 而是Tomcat自动调用的 , Tomcat 会自动的识别出合适的时机,来自动调用doGet方法 , —定是通过GET请求触发的!!! doGet做的工作,就是Tomcat收到请求之后,到返回响应之前,中间的过程 !
@WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内 .
上述代码没有main方法 , 不能单独执行,main方法在Tomcat内 , 上述代码需要部署到Tomcat中,由Tomcat进行调用.main好比汽车的发动机 , 我们写的Servlet代码就是车厢 . 只有个车头 , 可以自己跑 ; 但是车厢直接不能跑 , 得挂在车头上 .
借助maven , 一键式完成 !
把刚才得到的war包 , 拷贝到Tomcat的webapps目录中 !
让浏览器构造一个HTTP Get请求 !
总结 :
注意 : Spring Boot可以简化这里的流程 , 现在先介绍一种中间的方案 , 即借助IDEA中的一个插件 , Smart Tomcat , 来简化打包和部署 .
smart tomcat不是tomcat , 其功能就是能够在IDEA中调用tomcat , 我们就无需手动双击tomcat的启动脚本来运行 , 而是直接在IDEA中一点就能运行tomcat了 .
此处中文也不再乱码了 !
smart tomcat的工作原理 , 和前面的手动拷贝部署不太一样 , 是让tomcat直接加载了我们代码中的webapp目录 , 这时候就跳过了打包 + 拷贝的过程 , 但是也起到了部署的效果 !
常见错误集合 :
- 404 : 大概率是URL写错了 , 也可能是webapp未正确加载(比如web.xml写错了) .
- 405 : method not allowed , 发送GET请求但未实现doGet() , 或实现了doGet() , 但未把super.doGet这个代码删掉 ;
- 505 : 服务器内部错误 , 即服务器挂了 , 这说明你的代码中抛出了异常 .
- 返回空白页面 , 大概率是未执行write()方法 .
- 无法访问此网站 , 大概率是tomcat没有正确启动 .
上面的实现是在tomcat的基础上实现的 .
HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作. 如下图所示 :
重点关注三个类 !
最常用的 : doXXX系列!!!
这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过程).
前面我们通过在浏览器里输入url构造了GET请求 , 那POST请求该如何构造呢 ?
form可以支持get和post , 在此处我们可以使用ajax .
加载 : jQuery
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("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet!");
resp.getWriter().write("doGet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost!");
resp.getWriter().write("doPost!");
}
}
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>Documenttitle>
head>
<body>
<script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
<script>
$.ajax({
type:'post',
url:'hello',
//url:'/hello_servlet/hello',
success:function(body){
console.log(body);
}
});
script>
body>
html>
此处我们只在控制台看到了响应 , 在控制台打印了"doPost!" , 这是因为ajax拿到的响应数据,是根据回调函数的方法处理的 , 可以显示 , 也可以不显示 ; 而直接通过浏览器输入url得到的响应内容是直接被浏览器渲染到页面上的 .
路径问题
如何把内容打印到页面上呢 ?
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("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet!");
resp.getWriter().write("doGet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost!");
resp.getWriter().write("doPost!");
}
}
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>Documenttitle>
<style>
.one{
font-size: 50px;
color: aqua;
}
style>
head>
<body>
<div class="one">div>
<script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
<script>
$.ajax({
type:'post',
url:'hello',
success:function(body){
//console.log(body);
let div = document.querySelector('.one');
div.innerHTML = body;
}
});
script>
body>
html>
根据当前的请求"doPost" , 决定调用我们自己代码中的doGet方法!!!
在进行这部分操作时 , 一开始出现了下面的问题 , 显示报错信息为 :
无法在地址[localhost]和端口[8005]上创建服务器关闭套接字(基本端口[8005]和偏移量[0]).
经过查阅资料 , 发现这是端口占用导致的问题 . 解决方案如下 :
- 查看端口占用
在windows命令行窗口下执行:netstat -aon|findstr “8080”
端口“8080”被PID(进程号)为16160的进程占用 .
- 查看端口“8080”被哪个应用占用,,继续执行下面命令:tasklist|findstr “16160”
3.关闭进程(此处按进程名关闭进程) : taskkill /im java.exe
扩展 : 关闭进程的方式 :
- 按进程号关闭进程 taskkill /pid 2152
多个时格式为:taskkill /pid 2152 /pid 1284 / …- 按进程名关闭进程 如要关闭notepad.exe,格式为:taskkill /im notepad.exe
指定多个时格式为:taskkill /im notepad.exe /im iexplorer.exe / im…
如果是要关闭所有的,则使用通配符*,即:taskkill /im *.exe- 有提示的关闭进程 taskkill /t /im notepad.exe
taskkill /t /pid 2152
这个效果是提示后在使用者确定后关闭,有提示框。- 强行终止进程 taskkill /f /im notepad.exe
taskkill /f /pid 2152
被拒绝时 , 尝试使用命令行 taskkill /f /t /im java.exe,可以关闭,亲测可用 .
通过按钮触发ajax请求的发送 :
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("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet!");
resp.getWriter().write("doGet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost!");
resp.getWriter().write("doPost!");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPut!");
resp.getWriter().write("doPut!");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doDelete!");
resp.getWriter().write("doDelete!");
}
}
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>Documenttitle>
<style>
.one{
font-size: 50px;
color: aqua;
}
style>
head>
<body>
<div class="one">div>
<button id="doGet">getbutton>
<button id="doPost">postbutton>
<button id="doPut">putbutton>
<button id="doDelete">deletebutton>
<script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
<script>
let doGetBtn = document.querySelector('#doGet');
doGetBtn.onclick = function(){
$.ajax({
type:'get',
url:'hello',
success:function(body){
let div = document.querySelector('.one');
div.innerHTML = body;
}
});
}
let doPostBtn = document.querySelector('#doPost');
doPostBtn.onclick = function(){
$.ajax({
type:'post',
url:'hello',
success:function(body){
//console.log(body);
let div = document.querySelector('.one');
div.innerHTML = body;
}
});
}
let doPutBtn = document.querySelector('#doPut');
doPutBtn.onclick = function(){
$.ajax({
type:'put',
url:'hello',
success:function(body){
//console.log(body);
let div = document.querySelector('.one');
div.innerHTML = body;
}
});
}
let doDeleteBtn = document.querySelector('#doDelete');
doDeleteBtn.onclick = function(){
$.ajax({
type:'delete',
url:'hello',
success:function(body){
//console.log(body);
let div = document.querySelector('.one');
div.innerHTML = body;
}
});
}
script>
body>
html>
注意文件位置 :
Q : 当前使用ajax构造出了上述几种请求.但是还是比较麻烦.毕竟要写代码 . 是否有办法,不写代码,也能构造出任意的http请求呢?
A : 此处还可以使用第三方工具,来构造HTTP请求. (类似功能的工具种类繁多),咱们在工作中一个非常常用的,叫做 PostMan(各种请求都能构造 , 真香) . Postman本来是chrome的一个插件.现在已经是独立的程序了 . 可以去官网下载安装 !
Q : 面试题 : 谈谈HttpServlet的生命周期 ?
A :
- 首次使用 , 先调用一次init ;
- 每次收到请求 , 调用service , 在service内部通过具体方法来决定调用哪个doXXX代码 ;
- 销毁之前调用destroy .
这个类就提供了一组方法 , 让我们能够获取到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;
import java.util.Enumeration;
@WebServlet("/requestServlet")
public class RequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(req.getProtocol());//返回请求协议的名称和版本
stringBuilder.append("
");
stringBuilder.append(req.getMethod());//返回请求的HTTP方法的名称
stringBuilder.append("
");
stringBuilder.append(req.getRequestURI());//返回请求的URL的一部分
stringBuilder.append("
");
stringBuilder.append(req.getQueryString());//返回包含在路径后的请求URL中的查询字符串
stringBuilder.append("
");
Enumeration<String> headersnames = req.getHeaderNames();
while(headersnames.hasMoreElements()){
String name = headersnames.nextElement();
stringBuilder.append(name+":"+req.getHeader(name));
stringBuilder.append("
");
}
//设置Content Type
resp.setContentType("test/html;charset=utf8");
resp.getWriter().write(stringBuilder.toString());
}
}
更多的情况下 , 需要通过API拿到请求中传来的必要的参数 !!! 假如教务系统上 , 需要获取某个同学的信息 , 给服务器发个请求 :
/studentInfo?classId=368&studentId=20
此时服务器就需要知道传过来的信息是什么 .
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 Student{
public int classId;
public int studentId;
}
@WebServlet("/studentInfo")
public class StudentInofoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假设客户端发来的请求形如 /studentInfo?classId=368&studentId=20
// 就可以通过 getParameter 方法来拿到这两个 id 的值.
resp.setContentType("text/html,charset=utf8");
String queryString = req.getQueryString();
System.out.println(queryString);
String classId = req.getParameter("classId");
String studentId = req.getParameter("studentId");
System.out.println("classId: " + classId + "studentId: " + studentId);
resp.getWriter().write("classId: " + classId + "studentId: " + studentId);
}
}
getParameter的效果就是拿到query string中的键值对 , 根据key获取value(key和value都是String类型的) .
前端除了通过query string来传参 , 还有其他的传参方式 , 比如可以通过post请求 , 通过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;
import java.util.function.DoublePredicate;
class Student{
public int classId;
public int studentId;
}
@WebServlet("/studentInfo")
public class StudentInofoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 约定 body 使用 application/x-www-form-urlencoded 这种格式来传参.
// 这个格式就形如 classId=368&studentId=05
// 这个格式和 query string 相同!!! 只是数据是在 body 中!
// 针对这种格式, 获取到值的方式, 仍然是 getParameter !!!!
resp.setContentType("text/html,charset=utf8");
String queryString = req.getQueryString();
System.out.println(queryString);
String classId = req.getParameter("classId");
String studentId = req.getParameter("studentId");
System.out.println("classId: " + classId + " studentId: " + studentId);
resp.getWriter().write("classId: " + classId + " studentId: " + studentId);
}
}
通过Postman构造POST请求 :
使用Fiddler抓包的结果 :
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>通过form构造请求title>
head>
<body>
<form action="studentInfo" method="post">
<input type="text" name="classId">
<input type="text" name="studentId">
<input type="submit" name="提交">
form>
body>
html>
如果 POST 请求中的 body 是按照 JSON 的格式来传递, 那么获取参数的代码就要发生调整.
如果我们自己写代码按照字符串解析的方式获取到这里的键值对 , 是很费劲的 , 所以我们选择使用第三方库 . Java世界中有很多处理Json的第三方库 , 此处我们使用Jackson . (Spring官方推荐使用且内部集成了) .
打开 maven中央仓库
将依赖下载到本地 : (点击刷新)
此时 , maven就把这个库给下载到了本地 .
对于jackson , 只需要掌握两个操作 :
对应到一个类ObjectMapper .
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.function.DoublePredicate;
class Student{
public int classId;
public int studentId;
}
@WebServlet("/studentInfo")
public class StudentInofoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理json格式的请求
ObjectMapper objectMapper = new ObjectMapper();
//使用readValue把json字符串转成Java对象
Student student = objectMapper.readValue(req.getInputStream(),Student.class);
System.out.println(student.classId+","+student.studentId);
resp.getWriter().write(student.classId+","+student.studentId);
}
}
加入json中的key和类里的属性不一致, 会发生什么?
此时出现内部服务器错误 .
Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到
HttpServletResponse 对象中. 然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket 写回给浏览器.
注意 : Request是get系列的方法, Response则是一些set系列的方法.
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 StatusCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置相应的状态码
resp.setStatus(500);
}
}
代码示例 : 自动刷新
给响应报文header设置Refresh属性 , 跟上的value表示让浏览N秒之后刷新 .
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 {
resp.setHeader("Refresh","1");
resp.getWriter().write(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);//3xx表示重定向, 默认用302
resp.setHeader("Location","https://www.baidu.com");
/*
另一种简单的写法
resp.sendRedirect("https://www.baidu.com");
*/
}
}
效果回顾 :
当前这些数据只是在浏览器的内存中保存着 , 一旦浏览器重启或刷新页面 , 数据就消失了 .
解决方案就是把数据保存在服务器上 .
此时我们就可以换一种打开方式了 .
//5.[新步骤]需要把刚才输入框里取到的数据, 构造成 POST 请求, 交给后端服务器!
let messageJson = {
"from" : from,
"to" : to,
"message" : message
};
$.ajax({
type:"post",
url:'message',//或者写url='/MessageWall/message'
contentType:"application/json;charset=utf8",
data:JSON.stringify(messageJson),
success:function(body){
alert("提交成功!");
},
error:function(){//在服务器返回的状态码不是2xx时触发这个error
alert("提交失败!");
}
});
整体代码
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>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.container {
width : 800px;
margin : 10 px auto;
}
.container h2 {
text-align: center;
margin: 30px 0px;
}
.row {
height: 50px;
display: flex;
justify-content: center;
margin-top: 5px;
line-height: 50px;
}
.row span {
height: 50px;
width: 100px;
line-height: 50px;
}
.row input {
height: 50px;
width: 300px;
line-height: 40px;
font-size: 30px;
color: cadetblue;
}
.row button {
width: 400px;
height: 50px;
color: #000;
background-color: aquamarine;
border: none;
border-radius: 10px;
}
.row button:active {
background-color: grey;
}
style>
head>
<body>
<div class="container">
<h2>留言板h2>
<div class="row">
<span>谁span>
<input type="text" id="from">
div>
<div class="row">
<span>对谁span>
<input type="text" id="to">
div>
<div class="row">
<span>说什么span>
<input type="text" id="message">
div>
<div class="row">
<button>提交button>
div>
<script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
<script>
let container = document.querySelector('.container');
let fromInput = document.querySelector('#from');
let toInput = document.querySelector('#to');
let messageInput = document.querySelector('#message');
let button = document.querySelector('button');
button.onclick = function() {
//1.把用户输入的内容获取到
let from = fromInput.value;
let to = toInput.value;
let message = messageInput.value;
if(from == '' || to == '' || message == ''){
return;
}
//2.构造一个div,把这个div插入到.container的末尾
let newDiv = document.createElement('div');
newDiv.className = 'row';
newDiv.innerHTML = from + "对" + to + "说" + message;
//3.把div挂在container里面
container.appendChild(newDiv);
//4.把之前输入框中的内容清空
fromInput.value = '';
toInput.value = '';
messageInput.value = '';
//5.[新步骤]需要把刚才输入框里取到的数据, 构造成 POST 请求, 交给后端服务器!
let messageJson = {
"from" : from,
"to" : to,
"message" : message
};
$.ajax({
type: 'post',
contentType: 'application/json;charset=utf8',
url: 'message',// 绝对路径的写法url: '/MessageWall/message',
data: JSON.stringify(messageJson),
success: function(body) {
alert("提交成功!");
},
error: function() {// 会在服务器返回的状态码不是 2xx 的时候触发这个 error.
alert("提交失败!");
}
});
}
script>
body>
html>
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;
//对应前端传来的请求body
//保证这几个属性的名字都和json里的key对应
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();
private List<Message> messageList = new ArrayList<>();
//负责实现让客户端提交数据给服务器
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.把body的json数据解析出来
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.把这个对象保存起来,最简单的方法,就是存到内存中.
messageList.add(message);
System.out.println("message : " + message);
//3.返回保存成功的响应
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write("{\"ok\" : 1}");
}
//负责实现让客户端从服务器拿到数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
使用Fiddler , 来查看结果 :
//负责实现让客户端从服务器拿到数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//把对象转成json格式的字符串,此处messageList是一个List,直接被转成json数组
String respString = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respString);
}
整体代码
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;
//对应前端传来的请求body
//保证这几个属性的名字都和json里的key对应
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();
private List<Message> messageList = new ArrayList<>();
//负责实现让客户端提交数据给服务器
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.把body的json数据解析出来
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.把这个对象保存起来,最简单的方法,就是存到内存中.
messageList.add(message);
System.out.println("message : " + message);
//3.返回保存成功的响应
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write("{\"ok\" : 1}");
}
//负责实现让客户端从服务器拿到数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//把对象转成json格式的字符串,此处messageList是一个List,直接被转成json数组
String respString = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respString);
}
}
/*
这个函数在页面加载时,从服务器获取到当前的消息列表
并显示到页面上
*/
function load(){
$.ajax({
type:'get',
url:'message',
success:function(body){
/*
此处得到的body是一个js对象的数组了
本来服务器返回的是JSON格式的字符串,ajax会自动根据
Content-Type对响应的body进行解析,解析成js对象
*/
let container = document.querySelector('.container');
for(let message of body){
let newDiv = document.createElement('div');
newDiv.className = 'row';
newDiv.innerHTML = message.from + "对" + message.to + "说" + message.message;
container.appendChild(newDiv);
}
}
});
整体代码
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>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.container {
width : 800px;
margin : 10 px auto;
}
.container h2 {
text-align: center;
margin: 30px 0px;
}
.row {
height: 50px;
display: flex;
justify-content: center;
margin-top: 5px;
line-height: 50px;
}
.row span {
height: 50px;
width: 100px;
line-height: 50px;
}
.row input {
height: 50px;
width: 300px;
line-height: 40px;
font-size: 30px;
color: cadetblue;
}
.row button {
width: 400px;
height: 50px;
color: #000;
background-color: aquamarine;
border: none;
border-radius: 10px;
}
.row button:active {
background-color: grey;
}
style>
head>
<body>
<div class="container">
<h2>留言板h2>
<div class="row">
<span>谁span>
<input type="text" id="from">
div>
<div class="row">
<span>对谁span>
<input type="text" id="to">
div>
<div class="row">
<span>说什么span>
<input type="text" id="message">
div>
<div class="row">
<button>提交button>
div>
<script src="https://code.jquery.com/jquery-3.6.1.min.js">script>
<script>
let container = document.querySelector('.container');
let fromInput = document.querySelector('#from');
let toInput = document.querySelector('#to');
let messageInput = document.querySelector('#message');
let button = document.querySelector('button');
button.onclick = function() {
//1.把用户输入的内容获取到
let from = fromInput.value;
let to = toInput.value;
let message = messageInput.value;
if(from == '' || to == '' || message == ''){
return;
}
//2.构造一个div,把这个div插入到.container的末尾
let newDiv = document.createElement('div');
newDiv.className = 'row';
newDiv.innerHTML = from + "对" + to + "说" + message;
//3.把div挂在container里面
container.appendChild(newDiv);
//4.把之前输入框中的内容清空
fromInput.value = '';
toInput.value = '';
messageInput.value = '';
//5.[新步骤]需要把刚才输入框里取到的数据, 构造成 POST 请求, 交给后端服务器!
let messageJson = {
"from" : from,
"to" : to,
"message" : message
};
$.ajax({
type: 'post',
contentType: 'application/json;charset=utf8',
url: 'message',// 绝对路径的写法url: '/MessageWall/message',
data: JSON.stringify(messageJson),
success: function(body) {
alert("提交成功!");
},
error: function() {// 会在服务器返回的状态码不是 2xx 的时候触发这个 error.
alert("提交失败!");
}
});
}
/*
这个函数在页面加载时,从服务器获取到当前的消息列表
并显示到页面上
*/
function load(){
$.ajax({
type:'get',
url:'message',
success:function(body){
/*
此处得到的body是一个js对象的数组了
本来服务器返回的是JSON格式的字符串,ajax会自动根据
Content-Type对响应的body进行解析,解析成js对象
*/
let container = document.querySelector('.container');
for(let message of body){
let newDiv = document.createElement('div');
newDiv.className = 'row';
newDiv.innerHTML = message.from + "对" + message.to + "说" + message.message;
container.appendChild(newDiv);
}
}
});
}
//函数调用写在这里,表示在页面加载时进行执行
load();
script>
body>
html>
1.一开始加载页面 .
过程解释 :
接下来的问题是 , 当服务器从重启时 , List里的内容就会丢失 ! 如何解决这个问题呢 ?
此处我们让服务器把数据保存在MySQL服务器上 .
1.引入MySQL依赖 .
从中央仓库导入即可 .
- 创建数据库数据表 .
可以把上述操作写到一个单独的文件中 , 以备后用 .
- 调整后端代码 .
a. 和数据库建立连接 , 单独搞一个DBUtil类 , 来实现数据库的建立连接 ;
b. 封装数据库操作 , 通过JDBC来完成数据库的操作 .
DBUtil.java
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import javax.swing.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 期望通过这个类来完成数据库建立连接的过程
* 建立连接需要使用DataSource,且一个程序有一个DDataSource实例即可.
*/
public class DBUtil {
private static DataSource dataSource = null;
private static DataSource getDataSource() throws SQLException {
if(dataSource == null){
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql:://127.0.0.1:3306/MessageWall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("111111");
}
return dataSource;
}
public static Connection getConnection() throws SQLException{
return getDataSource().getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
MessageServletl.java
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;
//对应前端传来的请求body
//保证这几个属性的名字都和json里的key对应
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();
//private List messageList = new ArrayList<>();
//负责实现让客户端提交数据给服务器
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.把body的json数据解析出来
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.把这个对象保存起来,最简单的方法,就是存到内存中.
//messageList.add(message);
save(message);
System.out.println("message : " + message);
//3.返回保存成功的响应
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write("{\"ok\" : 1}");
}
//负责实现让客户端从服务器拿到数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//把对象转成json格式的字符串,此处messageList是一个List,直接被转成json数组
List<Message> messageList = load();
String respString = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respString);
}
/**
* 把当前的消息保存到数据库中
* */
private void save(Message message){
Connection connection = null;
PreparedStatement statement = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL语句
String sql = "insert into walldata values(?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
//3.执行SQL语句
int ret = statement.executeUpdate();
if(ret != 1){
System.out.println("插入失败!");
} else {
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4.关闭连接
DBUtil.close(connection,statement,null);
}
}
/**
* 从数据库查询到记录
*/
private List<Message> load(){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
List<Message> messageList = new ArrayList<>();
try {
//1.建立连接
connection = DBUtil.getConnection();
//2.构造SQL
String sql = "select * from walldata";
statement = connection.prepareStatement(sql);
//3.执行SQL
resultSet = statement.executeQuery();
//4.遍历结果集
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 e) {
e.printStackTrace();
} finally {
//5.释放资源
DBUtil.close(connection,statement,resultSet);
}
return messageList;
}
}
允许效果如下 :
此时查询数据库中的walldata表 , 已经保存了这条数据 !
再次刷新页面 :
中断程序的运行 , 并再次启动 :
再次刷新页面 ,内容仍然存在 !
如果你发现结果不符合预期 , 怎么办 ?
起手式–抓包 !!!
1.一个是抓页面加载,看看GET /message方法请求和响应是啥样的 .
2.还可以再抓包POST看看有没有问题 .
3.还可以看看数据库里有没有正确数据.
如果数据库有数据,说明大概率是GET的时候出问题了.如果数据库没数据,说明大概率是POST的时候出问题了!!!
顺便记录一下笔者在这里出现的问题 :
1.没有加载jQuery .
你可能会问了 , 你这数据库密码不就泄露了吗 ?
我只能说 , 银行卡里没有余额,那掩盖密码也是无用的 .
Cookie是浏览器给HTTP协议提供的一个持久化存储数据的方案.
典型应用 : 保存用户的身份信息 !
注意 : Cookie是在客户端存的 , Session是在服务器存的 , Sessionid是在客户端和服务器都存了 .
注意 : 实现登录和身份验证是Cookie的一种典型的应用 , 此时需要Cookie和Session搭配工作 , 其他场景则不一定需要搭配Session . Cookie里是可以存多个键值对的 , 具体存什么 , 是程序猿自己约定的 . 比如你想存登录的时间 , 就可以以LoginTime为key , 以登录的时间戳为value .
一个 HttpSession 对象里面包含多个键值对. 我们可以往 HttpSession 中存任何我们需要的信息.
每个 Cookie 对象是一个键值对
功能 : 登录页可输入用户名和密码 , 服务器验证正确性 . 如果正确跳转到主页 , 并在主页中显示出当前用户的身份信息 , 且显示出当前用户的登录的访问次数 . (当用户第一次登录成功后 , 就无须重新登录了) .
主页代码 : IndexServlet.java
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.servlet.http.HttpSession;
import java.io.IOException;
//用这个Servlet返回主页信息
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
if(session == null){
//用户未登录,跳转到登录页面,要去用户重新登录
resp.sendRedirect("login.html");
return;
}
//已经成功登录
String username = (String) session.getAttribute("username");
Integer visitCount = (Integer) session.getAttribute("visitCount");
visitCount = visitCount + 1;
session.setAttribute("visitCount",visitCount);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户为: " + username + "访问次数 :" + visitCount);
}
}
登录页代码 LoginServlet.java
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.servlet.http.HttpSession;
import java.io.IOException;
//用来处理登录请求
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
//读取请求中的参数,判定当前用户信息是否正确
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || username.equals("") || password == null || password.equals("") ){
//返回提示
resp.getWriter().write("用户名或密码不完整!登录失败");
return;
}
if(!username.equals("baizong") || !password.equals("123456")){
resp.getWriter().write("用户名或密码错误!登录失败!");
return;
}
//登录成功,创建一个会话,把用户信息填写到session里
HttpSession session = req.getSession(true);
session.setAttribute("username","baizong");
Integer visitCount = (Integer) session.getAttribute("visitCount");
if(visitCount == null){
session.setAttribute("visitCount",0);
}
resp.sendRedirect("index");
}
}
login.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>
</head>
<body>
<form action="login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
实现效果 :
存储结构 :
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Part part = req.getPart("MyFile");
//获取到文件的真实名字
System.out.println(part.getSubmittedFileName());
//获取文件大小
System.out.println(part.getSize());
//获取文件的类型
System.out.println(part.getContentType());.
//把文件写入服务器这边的磁盘中
part.write("C:\\Users\\86177\\Desktop\\result.jpg");
resp.getWriter().write("upload ok!");
}
}
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>uploadtitle>
head>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="MyFile">
<input type="submit" value="上传">
form>
body>
html>
最终效果 :
至此 , Servlet及其API全部介绍完毕 !