1.Servlet 是什么
Servlet 是一种 Java 程序,用于在 Web 服务器上处理客户端请求和响应。Servlet 可以接收来自客户端(浏览器、移动应用等)的 HTTP 请求,并生成 HTML 页面或其他格式的数据,然后将响应发送回客户端。Servlet 在 Java EE(Java Enterprise Edition)平台上使用,是 Java Web 应用程序的重要组件之一。Servlet 提供了一种灵活的方法来开发 Web 应用程序,因为开发人员可以使用各种 Java 技术和库来实现 Servlet。常见的 Servlet 容器包括 Tomcat、Jetty、WebSphere 和 WebLogic 等。
2.如何使用Servlet
在Java中使用Servlet,先从一个hello world 开始
实现这个hello world 得经过七个步骤!!!
1.创建项目
此处创建的是一个Maven项目~~
Maven是一个构建工具~~
功能是帮我们去构建,测试,打包一个项目~
首次使用Maven项目,idea会从互联网上加载很多的~
一个Maven项目,首先会有一个pom.xml配置文件
这个文件就描述了Maven项目的各个方面的内容~
main里面放的是业务代码
java放的是业务中的Java代码
resources放的是程序依赖的资源~~
图片,图标,音频,视频…
test里面放的是测试代码
2.引入依赖
Servlet 是 Tomcat 提供的API(不是标准库)
Servlet不是标准库自带,就需要额外下载安装~~
(Tomcat 安装好了,是Tomcat运行时使用的,咱们此处是开发阶段,需要额外安装Servlet的jar包)
在中央仓库 https://mvnrepository.com/中搜索 “servlet”
选择3.1.0 下载
这个版本和Tomcat8.5匹配~
我们直接借助maven. 把中央仓库中提供的 xml 复制到项目的 pom.xml 中
给pom.xml中新增一个 < dependencies > 标签
这样就可以引用多个第三方依赖~~
只要把这一段代码往里一拷贝
IDEA就会自动调用maven,从中央仓库下载该jar包~
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>test-2023-10-9</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 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></dependencies></project>
3.创建目录
手动创建目录
这里的目录结构,目录位置,目录名字务必保证一字不差!!!
web.xml是给Tomcat看的
Tomcat 从 webapps 目录加载webapp,就是以web.xml为依据~
直接编写 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
</web-app>
4.开始写代码
来自于从maven中央仓库下载的jar包~
这个东西如果提示不出来,说明jar包没有正确加载
(1)继承HttpServlet父类,重写doGet方法
HttpServletRequest —> HTTP请求
Tomcat收到请求把这个请求按照HTTP协议的格式解析成了个对象
HttpServletResponse —> HTTP响应
此处响应对象,是一个空的对象
需要咱们在doget中,设置响应的一些数据(比如响应的body和header和状态码等等…)
只要把属性设置到这个resp对象中,Tomcat就会自动根据响应对象
构造一个HTTP响应字符串,通过socket返回给客户端~
doGet 要完成的工作,就是,根究请求,计算生成响应~~
对于一个服务器程序来说,基本工作流程.
- 读取请求并解析
- 根据请求计算响应
- 把响应写回给客户端
调用父类的doget
点击父类的doget可以看到
这里直接可以看到
返回一个错误页面,所以不把doget干掉,页面就会得到405!!!
(2)在doget中打印 hello world
resp.getWriter 得到了resp内部所有的writer对象(字符流)~~
既然是字符流,就可以使用write来写
此时写的数据,是写到HTTP响应的body中了~
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//super.doGet(req, resp);//这是在服务器的控制台中,打印了字符串(服务器看到了)System.out.println("hello world");//这个是给resp的body写入hello world 字符串,这个内容就会被HTTP响应返回给浏览器,显示到浏览器页面上resp.getWriter().write("hello world");}
}
(3)给HelloServlet加上一个注解
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//super.doGet(req, resp);//这是在服务器的控制台中,打印了字符串(服务器看到了)System.out.println("hello world");//这个是给resp的body写入hello world 字符串,这个内容就会被HTTP响应返回给浏览器,显示到浏览器页面上resp.getWriter().write("hello world");}
}
咱们这个代码写到这里就写完了,不需要写Main方法
上述代码,并非是独立运行,而是把这个代码插入到Tomcat中,由Tomcat去调用~~
5.打包代码
咱们的程序,不能直接独立运行,而是必须放到Tomcat上运行(部署)
部署的前提是先打包~~
对于一个规模比较大的项目,里面就会包含很多的Java文件,进一步的就会产生很多的class
所以,把这些class打成一个压缩包,在进行拷贝,是比较科学的~
咱们平时见到的压缩包,rar,zip…
在Java中,使用的压缩包,jar,war…
jar —> 普通的Java程序,打成jar
war —> 部署给Tomcat的程序,打成war
war和jar本质上没有什么区别,都是把一堆class文件给打包进去,但是war包是属于Tomcat的专属格式
里面会有一些特定的目录结构和文件,比如web,xml后续Tomcat就要识别这些内容,来加载webapp
双击package运行
打包操作做的事情:
- 检查代码中是否存在一些依赖,依赖是否下载好(这个事情都是maven负责的,之前引入了Servlet的依赖)
- 把代码进行编译,生成一堆.class文件
- 把这些.class文件,以及web.xml按照一定的格式进行打包~
这代表打包成功
当前打包,是打出来一个jar包,但不是war包~
为了打出来的war,需要调整pom.xml,描述打包生成的包格式~~
在project顶级标签下方,写一个< packing >标签,描述打包的类型是war~~
此处也可以修改一下打出来的包的文件名.
打好的war包,就是一个普通的压缩包,是可以使用解压缩工具(winrar)打开,看到里面的内容的.但是并不需要手动解压缩,直接把整个war交个Tomcat,Tomcat能够自动的解压缩
6.部署
把打好的war包,拷贝到Tomcat的webapps目录中~
启动Tomcat
7.验证程序
不是Tomcat一启动,咱们写的doget就能执行
doGet => 遇到GET请求,就做一些事情(执行该方法)
注意,也不是随便收到一个GET请求,就能执行doGet,前提是,请求的URL的路径要匹配!!
页面上有hello world
服务器日志上也有hello world
此处的路径是分两级的~
- hello_Servlet,称为context Path/Application Path标识了一个webapp(也就是这个webapp的目录名/war包名)
- hello,称为Servlet Path,标识当前请求要调用哪个Servlet类的doGet
简化流程
手动拷贝 war 包到 Tomcat 的过程比较麻烦. 我们还有更方便的办法.
此处我们使用 IDEA 中的 Smart Tomcat 插件完成这个工作.
插件(plugin)
IDEA功能非常多,非常强大~但即便如此,IDEA也无法做到"面面俱到"
为了支持这些特定的,小众的功能,就引入了"插件体系"
插件可以视为是对IDEA原有功能的扩充~程序员可以按需使用
点击下载安装Smart Tomcat
使用Smart Tomcat 插件可以简化打包部署工作(社区版使用的方法)
IDEA专业版来说,内置了Tomcat Servlet.(这个东西用起来更复杂,还是建议使用Smart Tomcat)
使用Smart Tomcat
首次使用稍微麻烦一点,需要配置一下.
首次使用,需要配置一下这个路径~
这个路径就是指向Tomcat安装目录的路径
点击加号添加路径
这个context Path是访问程序的两级路径中的第一级~~
如果我们的程序是拷贝war包到webapps中运行的,此时context Path是war包的名字
如果我们的程序是使用Smart Tomcat运行,Context Path 是在上述配置中,手动设置的~~默认是项目名字
点击三角号,程序运行
此时,Tomcat的日志,就在IDEA中显示了,不会在单独弹出cmd,因此乱码问题就解决啦~
Smart Tomcat 的运行方式和我们拷贝到webapps中,是存在本质区别的.
Smart Tomcat其实是使用了Tomcat另外一种运行方式
在运行Tomcat 的时候,通过特定的参数,来指定Tomcat加载某个特定目录中的webapp
因此上述过程,既不会打包,也不会拷贝~~
3.Servlet使用中常见的错误
1.404
404表示你浏览器访问的资源,在服务器上不存在~~
出现404的原因
1.你请求的路径写错了
用我们写的上述代码举例
- 少写了 Context Path
通过 /hello 访问服务器- 少写了 Servlet Path
通过 /ServletHelloWorld 访问服务器- Servlet Path 写的和 URL 不匹配
修改 @WebServlet 注解的路径
2.路径写对了,但是war包没有被正确加载出来~
用我们写的上述代码举例
- web.xml 写错了
清除 web.xml 中的内容- 有两个Servlet的Servlet Path相同
更改其中一个的Servlet Path
2.405
405 表示对应的 HTTP 请求方法没有实现
出现405的原因
1.发的请求的方法,和代码不匹配
比如代码请求写的是doPost,你发送的是GET请求~
2.虽然方法和代码匹配,但是忘记了删掉super.doXXX
3.500
往往是 Servlet 代码中抛出异常导致的.
出现500意味着你的服务器代码抛出异常了~仔细观察异常调用栈!
他会告诉你第几行代码出现问题了,上述错误告诉我们的就是第24行出现错误
4.出现空白页面
注释掉resp.getWriter().write("hello world");
就会出现空白页面
出现空白页面就要反思服务器是不是没有返回任何数据~
5.无法访问此网站
代表了Tomcat没有启动!!
4.Servlet API 详解
1.HttpServlet
咱们写一个Servlet程序,都是要继承这个HttpServlet类的~
我们就需要知道,哪些方法,是能够被重写的
也就是HttpServlet中都有哪些方法,都是干啥的~
init方法
HttpServlet被实例化之后,就会调用一次,使用这个方法来做一些初始化相关的工作.
HttpServlet被实例化 —> 首次收到匹配的请求的时候,会调用到!!
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overridepublic void init() throws ServletException {System.out.println("执行init");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//super.doGet(req, resp);//这是在服务器的控制台中,打印了字符串(服务器看到了)System.out.println("hello world");//这个是给resp的body写入hello world 字符串,这个内容就会被HTTP响应返回给浏览器,显示到浏览器页面上resp.getWriter().write("hello world");}
}
这个请求会触发HelloServlet类的doGet的执行~~
就会在调用doGet之前,先调用init~
我们多次刷新可以看到init只调用一次
destroy方法
这个方法是webapp被卸载(被销毁之前)执行一次,用来做一些收尾工作~~
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overridepublic void init() throws ServletException {System.out.println("执行init");}@Overridepublic void destroy() {System.out.println("执行destroy");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//super.doGet(req, resp);//这是在服务器的控制台中,打印了字符串(服务器看到了)System.out.println("hello world");//这个是给resp的body写入hello world 字符串,这个内容就会被HTTP响应返回给浏览器,显示到浏览器页面上resp.getWriter().write("hello world");}
}
destroy是否能执行,不太靠谱!!!
1.如果是通过9005的管理窗口,来停止服务器,此时destroy就能执行
2.如果是直接杀死进程的方式停止服务器,此时destroy执行不了~
service方法
每次收到路径匹配的请求都会执行.
doGet/doPost 其实是在service中被调用的/
一般不会重写service,只是重写doXXX就好了~
init 是初始情况下调用一次
destroy 是结束之前调用一次
service 是每次收到路径匹配的请求都调用一次
它们构成了Servlet的生命周期
2.HttpServletRequest
HttpServletRequest对应一个HTTP请求.
一个HTTP请求里有啥,它就有啥~~
1.方法
2.URL(host,query string…)
3.版本号
4.各种header
5.body
URI —> 唯一资源标识符.
URL —> 唯一资源定位符.
query string —> 查询字符串
Enumeration getParameterNames()
String getParameter(String name)
获取到请求中的参数(query string中的键值对)
query string 中的键值对,都是允许重复的(此时相当于一个key对应多个value~)
Enumeration getHeaderNames()
String getHeader(Stringname)
处理header
InputStream getlnputStream()
通过这个InputStream,进一步读取body内容
如果我们确实需要按照字符处理,手动转换一下即可~
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {StringBuilder result = new StringBuilder();result.append(req.getProtocol());result.append("<br>");result.append(req.getMethod());result.append("<br>");result.append(req.getRequestURI());result.append("<br>");result.append(req.getQueryString());result.append("<br>");result.append(req.getContextPath());result.append("<br>");//在响应中设置body的类型,方便浏览器进行解析resp.setContentType("text/html;charset=utf8");resp.getWriter().write(result.toString());}
}
result.append("====================================<br>");
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();String headerValue = req.getHeader(headerName);result.append(headerName + ": " + headerValue + "<br>");
}
getParameter
最常用的API之一~~
前端给后端传递数据,是非常常见的需求~~
1. 通过query string 传递
约定,前端通过query string 传递 username 和 password
@WebServlet("/getParameter")
我们注解时,务必加上/
如果不加/会怎么样?
如果是拷贝war包的方式
导致Tomcat不能正确加载该webapp,直接导致404
如果是Smart Tomcat,可能会引起Tomcat直接启动失败~
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {@Overrideprotected 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");}
}
query string 中的键值对,都是程序员自定义的!
2. 通过body(form)
相当于body里存的数据格式,就和query string一样,但是Context-Type 是 application/x-www-form-urlencoded
此时也是通过getParameter来获取到键值对~
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//前端通过body,以form表单的格式,把username 和 password 传递给服务器.String username = req.getParameter("username");if(username == null) {System.out.println("username 这个 key 在 body 中不存在!");}String password = req.getParameter("password");if(password == null) {System.out.println("password 这个 key 在 body 中不存在!");}System.out.println("username = " + username + ",password = " + password);resp.getWriter().write("ok");}
value里面传递中文可以吗?
username = 张三
在URL中 query String 如果是包含中文/特殊字符
务必需要使用urlencode的方式进行转码!!
如果直接写中文/特殊字符,会存在非常大的风险!!
给请求设置!
3. 通过body(json)
json 也是键值对格式的数据,但是Servlet自身没有内置json解析功能~
因此就需要借助其他的第三方库了(Jackson)!!
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();@Overrideprotected 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(.class 是类对象,就是这个类的图纸~)去找到User里的所有public的属性(或者有public getter setter 的属性) 依次遍历
3.遍历属性(通过反射完成的),根据属性的名字,去上述准备好的键值对里,查询,看看这个属性名字是否存在对应的value,如果存在,就把value赋值到该属性中
HttpServletRequest
使用这个类,主要就是用于获取到请求的各个方面的信息,尤其是前端传过来的自定义数据.
3.HttpServletResponse
HttpServletResponse 表示一个响应
@WebServlet("/status")
public class StatusServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setStatus(200);resp.setContentType("text/html;charset=utf8");resp.getWriter().write("返回 200 响应");}
}
通过header实现自动刷新的效果.
给HTTP响应中,设置Refresh:时间
@WebServlet("/refresh")
public class RefreshServlet extends HelloServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//每隔一秒刷新一次resp.setHeader("Refresh","1");resp.getWriter().write("time = " + System.currentTimeMillis());}
}
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//用户访问这个路径的时候,自动重定向到主页resp.setStatus(302);resp.setHeader("Location","https://www.sogou.com/");}
}
4.代码示例 ------ 表白墙
之前我们在前端写的表白墙,最大的问题:
1.页面重启,数据丢失了.
2.数据只是在本地的,别人看不到~
我们只需引入后端服务器即可!!
当用户打开页面的时候,就需要从服务器加载当前已经提交过的表白数据~
当用户新增一个表白的时候,也会把数据提交给服务器,让服务器持久化保存~
1. 准备工作
- 创建 maven 项目.
- 创建必要的目录 webapp, WEB-INF, web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
</web-app>
- 调整 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>test-2023-10-9</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 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><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version></dependency></dependencies></project>
- 把之前实现的表白墙前端页面拷贝到 webapp 目录中.
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>表白墙</title>
</head>
<body>
<div class="container"><h1>表白墙</h1><p>输入后点击提交, 会将信息显示在表格中</p><div class="row"><span>谁: </span><input class="edit" type="text"></div><div class="row"><span>对谁: </span><input class="edit" type="text"></div><div class="row"><span>说什么: </span><input class="edit" type="text"></div><div class="row"><input type="button" value="提交" class="submit"></div>
</div>
<style>* {margin: 0;padding: 0;}.container {width: 400px;margin: 0 auto;}h1 {text-align: center;padding: 20px 0;}p {color: #666;text-align: center;font-size: 14px;padding: 10px 0;}.row {height: 40px;display: flex;justify-content: center;align-items: center;}span {width: 100px;line-height: 40px;}.edit {width: 200px;height: 30px;}.submit {width: 304px;height: 40px;color: white;background-color: orange;border: none;}
</style>
<script>// 给点击按钮注册点击事件let submit = document.querySelector('.submit');submit.onclick = function () {// 1. 获取到编辑框内容let edits = document.querySelectorAll('.edit');let from = edits[0].value;let to = edits[1].value;let message = edits[2].value;console.log(from + "," + to + "," + message);if (from == '' || to == '' || message == '') {return;}// 2. 构造 html 元素let row = document.createElement('div');row.className = 'row';row.innerHTML = from + '对' + to + '说: ' + message;// 3. 把构造好的元素添加进去let container = document.querySelector('.container');container.appendChild(row);// 4. 同时清理之前输入框的内容for (var i = 0; i < 3; i++) {edits[i].value = '';}}
</script></body>
</html>
2.约定前后端交互接口
写后端代码之前,需要先明确一下,前后端交互接口~
明确出来,网页给服务器要发啥样的请求
服务器给网页返回啥样的响应.
1.页面加载完毕之后,需要给服务器发个请求,获取到当前的留言数据都有啥.
2.用户点击提交的时候,就需要告诉服务器,当前用户发了的信息是啥
在交互的过程中,又涉及到关键的问题:
请求,具体是啥样的??响应具体是啥样的??(都是需要程序员来设计的)
“约定前后端交互接口”
这里给出一份典型的约定方式(你也可以按照别的方式来约定,并不是唯一的方式)
接口一 : 页面获取当前所有的留言消息
请求:
GET / message
响应:
HTTP/1.1 200 OK
Content-Type : application/json
接口二 : 提交新消息给服务器
请求:
POST / message
Content-Type : application/json
响应:
HTTP/1.1 200 OK
3.实现服务器端的代码
对于接口二,服务器要做的事就是
解析请求中的body,转成Message对象
然后把这个Message对象给保存起来~~
class Message {//这几个数据必须设置成public//如果是private,必须生成public的getter()和setter() !!public String from;public String to;public String message;@Overridepublic 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<>();@Overrideprotected 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);}@Overrideprotected 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发送请求,就会得到
4.调整前端页面代码
<!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><!-- 引入 jquery --><script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script><!--用cdn引入js文件,但是程序报错,说“there is no locally stored library”。:在你报错的句子上面按Alt+Enter,会弹出一个小框,然后点击'Suppress for tag' 把文件下载到本地--><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>
</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"><button id="revert">撤销</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. 构造新 divlet 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. 通过 ajax 构造 post 请求, 把这个新的消息提交给服务器.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) {// 这是响应成功返回之后, 要调用的回调.console.log("消息发送给服务器成功!");}});}let revertButton = document.querySelector('#revert');revertButton.onclick = function() {// 删除最后一条消息.// 选中所有的 row, 找出最后一个 row, 然后进行删除let rows = document.querySelectorAll('.message');if (rows == null || rows.length == 0) {return;}containerDiv.removeChild(rows[rows.length - 1]);}// 在页面加载的时候, 希望能够从服务器获取到所有的消息, 并显示在网页中.$.ajax({type: 'get',url: 'message', // url 都是使用相对路径的写法. 相对路径意味着工作路径就是当前文件所在的路径.// 当前文件所在路径是 /message_wall/ , 因此此时构造的请求就是 /message_wall/messagesuccess: function(body) {// body 是收到的响应的正文部分. 如我们之前的约定, body 应该是 json 数组// 由于响应的 Content-Type 是 application/json, 此时收到的 body 会被 jquery 自动的把它从 字符串// 转成 js 对象数组. 此处就不需要手动的进行 JSON.parse 了.// 此处的 body 已经是一个 JSON.parse 之后得到的 js 对象数组了.// 就需要遍历这个 body 数组, 取出每个元素, 再依据这样的元素构造出 html 标签, 并添加到页面上.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>
</body>
</html>
Java中使用Jackson完成对象和json字符串等的转换
objectMapper.writeValue 用来把Java对象转成json格式字符串
objectMapper.readValue 用来把 json 格式字符串转成Java对象
JS中使用JSON这个特殊的对象,完成对象和字符串的转换.
JSON.stringfy 把 js 对象转成 json 格式字符串
JSON.parse 把 json 格式字符串 转成 js对象
根据css选择器,查找到页面中的元素~~
创建一个新的标签div
.classname innerHTML 都属于固定的属性名字
classname ----> class属性
这样我们刷新后可以看到输入内容后提交的信息不会消失!
如果服务器重启了,刚才的消息是否还在??
服务器保存的数据是在一个ArrayList中.(内存)
进程重启,内存的数据就没了~~
把数据存储在硬盘上,才是让数据更好的持久化的办法!!!
1.写到文件里
2.写到数据库里
5.数据存储到数据库中
1.引入jdbc的依赖
2.建库建表
回顾下数据库的基本操作
3.编写数据库代码
DataSource
Connection
PreparedStatement
ResultSet
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;/*** @projectName: message_wall* @package: PACKAGE_NAME* @className: MessageServlet* @author: 王嘉辉* @description:* @date: 2023/10/17 14:50* @version: 1.0*/
class Message {//这几个数据必须设置成public//如果是private,必须生成public的getter()和setter() !!public String from;public String to;public String message;@Overridepublic 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<>();@Overrideprotected 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);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//通过这个方式来处理"提交新消息"Message message = objectMapper.readValue(req.getInputStream(),Message.class);save(message);//messageList.add(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/java1?characterEncoding=utf8&useSSl=false");((MysqlDataSource)dataSource).setUser("root");((MysqlDataSource)dataSource).setPassword("0126");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.executeLargeUpdate();statement.close();connection.close();} catch (SQLException e) {e.printStackTrace();}}//这个方法用来往数据库中查询所有记录private List<Message> load() {List<Message> messageList = new ArrayList<>();DataSource dataSource = new MysqlDataSource();((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java1?characterEncoding=utf8&useSSl=false");((MysqlDataSource)dataSource).setUser("root");((MysqlDataSource)dataSource).setPassword("0126");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操作页面内容)
5.Cookie 和 Session
1.回忆Cookie
Cookie 是浏览器在本地存储数据的一种机制
1,Cookie从哪里来?
Cookie是从服务器来的
服务器在响应中带有Set-Cookie 字段,通过这个字段就可以把保存在浏览器本地的数据给返回回去
2.Cookie到哪里去?
后续浏览器访问服务器的时候,就会把当前本地的所有Cookie都通过http请求,给带过去
3.Cookie有啥用?
其中一种最典型的应用,就是使用Cookie保存当前用户的登录状态
在Cookie板寸用户身份标识,这样的应用场景中,此时身份标识如何分配,以及身份信息具体如何存储(session(会话))都是需要服务器的支持的.
(session(会话))
给当前的用户分配一个sessionid
同时记录下当前用户的一些身份信息(可以自定义的)
sessionid 就会被返回到浏览器的Cookie中,后续浏览器就会带着这个sessionid从而能够让服务器识别出当前的用户身份信息
2.相关方法
HttpServletRequest 类中的相关方法
getsession 有一个参数,boolean.
如果参数为false,getSession的行为是
1.读取请求中 Cookie 了的 sessionid
2.在服务器这边根据 sessionid 来查询对应的 Session 对象
3.如果查到了,就会直接返回这个 session 对象,如果没查到,返回 null
如果参数为true,getSession的行为是
1.读取请求中 Cookie 了的 sessionid
2.在服务器这边根据 sessionid 来查询对应的 Session 对象
3.如果查到了,就会直接返回这个 session 对象
4.如果没查到,就会创建一个 Session 对象,同时生成一个 sessionid
以 sessionid 为 key ,Session 对象为 value ,把这个键值对存储到服务器里的一个 hash 表中
同时把 sessionid 以 Set-Cookie 的方式返回给浏览器.
我们模拟实现一个登录功能~
首先,提供两个页面
-
登录页(包含两个输入框,输入用户名,密码还要有一个登录按钮)
点击登录按钮,就会发起一个http请求
服务器处理这个请求的时候就会验证用户名密码
如果用户名密码OK,就会跳转主页 -
主页,只是单纯的显示出当前用户的用户名(欢迎XXX)
其中,登录页,就是一个单纯的HTML
还需要写一个 Servlet,实现登录的时候的用户名密码校验
还要再写一个 Servlet 来生成主页.(主页里的内容是动态的,不能光一个HTML就完了)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><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>
form 会组织这里的数据以键值对的形式提交给服务器
其中 key 就是 input 的 name 属性.
其中 Value 就是 Input 用户输入的内容.
最终会构造成 post 请求,在 body 里以键值对(类似于 query string)的格式,进行组织.
服务器可以通过 getParameter 来获取到指定的 key 的 Value
编写LoginServlet 处理上述登录请求
登录请求形如:
POST/Login
Content-Type:application/x-www-form-urlencoded
username = zhangsan&password = 123
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 {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//1.从请求拿到用户名和密码//为了保证读出来的参数也能支持中文,要记得设置请求的编码方式是utf8resp.setCharacterEncoding("utf8");String username = req.getParameter("username");String password = req.getParameter("password");//2.验证用户名是否正确if (username == null || password == null || username.equals("") || password.equals("")) {resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前输入的用户名或密码不能为空!!");return;}//此处假定用户名只能是 zhangsan 或者 lisi ,密码都是123//正常的登录逻辑,验证用户名密码都是从数据库读取的if (!username.equals("zhangsan") && !username.equals("lisi")) {//用户名错误resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前输入的用户名或密码有误!!");return;}if(!password.equals("123")) {//密码错误resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前输入的用户名或密码有误!!");return;}//3.用户名和密码验证OK,接下来就创建一个会话//当前用户处于未登录的状态,此时请求的Cookie中没有Session//此处的getSession 是无法从服务器的哈希表中找到该Session对象//由于把参数设置成了true了,所以允许getSession 在查询不到的时候,创建新的 session 对象和 sessionid//并且会自动的把这个sessionid 和 Session 对象存储到哈希表中//同时返回这个session 对象,并且在接下来的响应中会自动把这个 sessionid 返回给客户端浏览器HttpSession session = req.getSession(true);//接下来我们可以让刚刚创建好的Session 对象存储到咱们自定义的数据,就可以在这个对象中存储用户的身份信息session.setAttribute("username",username);//4.登录成功之后,会自动跳转到主页resp.sendRedirect("index");}
}
session 对象本身,也可以视为一个哈希表
可以通过 setAttribute 来存储键值对.(key就是String,Value就是Object)
后续就可以通过getAttribute 根据key在获取到Value
编写生成主页的Servlet
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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//此处禁止创建会话,如果没找到,认为用户名是未登录的状态!!//如果找到了才认为是登录状态HttpSession session = req.getSession(false);if(session == null) {//未登录状态resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前用户未登录!!");return;}//此处查询到的Session对象就应该和刚才登陆成功后创建的Session对象是同一个对象String username = (String) session.getAttribute("username");if(username == null) {//虽然有会话对象,但是里面没有必要的属性,也认为是登录状态异常resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前用户未登录!!");return;}//如果上述检查都OK,接下来就生成一个动态页面resp.setContentType("text/html;charset=utf8");resp.getWriter().write("欢迎你! " + username);}
}