大家好,欢迎来到IT知识分享网。
一 : Servlet的定义
Servlet 是一种实现动态页面的技术. 是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app.
Java Servlet 是运行在 Web服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页 .
Servlet用于开发动态页面 , 根据用户输入内容的不同 , 来返回出不同的页面结果 .
什么是动态页面 , 什么是静态页面呢 ?
- 静态页面也就是内容始终固定的页面. 即使用户不同/时间不同/输入的参数不同 , 页面内容也不会发生 ;
变化. (除非网站的开发人员修改源代码, 否则页面内容始终不变).- 动态页面指的就是 用户不同/时间不同/输入的参数不同, 页面内容会发生变化 .
二 : Servlet的任务
- 允许程序猿注册一个类, 在 Tomcat 收到某个特定的 HTTP 请求的时候, 执行这个类中的一些代码.
- 帮助程序猿解析 HTTP 请求, 把 HTTP 请求从一个字符串解析成一个 HttpRequest 对象.
- 帮助程序猿构造 HTTP 响应. 程序猿只要给指定的 HttpResponse 对象填写一些属性字段, Servlet就会自动安装 HTTP 协议的方式构造出一个 HTTP 响应字符串, 并通过 Socket 写回给客户端.
- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。
三 : 写一个简单的Servlet程序
一共需要七大步骤 .
2.1创建项目
maven叫做”构建工具” , 针对代码进行依赖管理 , 编译 , 打包 , 验证 , 部署等功能 , 可以视为是针对复杂项目进行管理的一个解决方案 .
maven自身的功能是很多的 , 我们主要使用 :
- 管理依赖 : 想使用某个第三方库,就使用maven把这个库下载下来并导入到项目中 .
- 打包 : 把代码编译好,把.class 文件打成压缩包(.jar 或者.war) .
2.2 引入依赖
此处谈到的依赖 , 是编写Servlet程序所需要的依赖(Servlet的jar包) , 需要把这个jar下载导入到项目中 .
下载步骤 :
- 打开 Maven的中央仓库
- 找到我们所需要的内容 :
- 把这段文字 , 粘贴到pom.xml里面 .
注意 : 首次使用 , 会标红 , 这是因为在首次使用时 , maven会从中央视仓库下载对应的jar包 , 这需要一定的时间 . 你也可是点击刷新 , 主动触发下载 !
2.3 创建目录
将下面的内容复制粘贴到web.xml中 .
<!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 中的动态资源.
2.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("/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代码就是车厢 . 只有个车头 , 可以自己跑 ; 但是车厢直接不能跑 , 得挂在车头上 .
2.5 打包
借助maven , 一键式完成 !
再次双击package进行打包 !
2.6 部署
把刚才得到的war包 , 拷贝到Tomcat的webapps目录中 !
2.7 验证
让浏览器构造一个HTTP Get请求 !
注意目录结构:
总结 :
注意 : Spring Boot可以简化这里的流程 , 现在先介绍一种中间的方案 , 即借助IDEA中的一个插件 , Smart Tomcat , 来简化打包和部署 .
smart tomcat不是tomcat , 其功能就是能够在IDEA中调用tomcat , 我们就无需手动双击tomcat的启动脚本来运行 , 而是直接在IDEA中一点就能运行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 的定位
上面的实现是在tomcat的基础上实现的 .
HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作. 如下图所示 :
更详细的交互过程见下图 :
四 : Servlet API 详解
重点关注三个类 !
4.1HttpServlet
4.1.1核心方法
- init只会在该Servlet类第一次被使用的时候调用到 , 相当于是用来初始化 ;
- destroy是在Servlet对象被销毁的时候才会调用到 , 仅调用一次 , 相当于收尾工作 . 这个方法不一定能执行到 , 仅在tomcat是通过8005端口进行关闭时会执行destroy , 如果是直接杀死tomcat进程 , 则不会调用 .
- service在每次收到请求(无论什么方法)时都会调用到service , 默认父类的service里面就会根据方法调用doGet/doPost .
最常用的 : doXXX系列!!!
这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过程).
4.1.2构造POST请求
前面我们通过在浏览器里输入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>Document</title> </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>Document</title> <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>Document</title> <style> .one{
font-size: 50px; color: aqua; } </style> </head> <body> <div class="one"></div> <button id="doGet">get</button> <button id="doPost">post</button> <button id="doPut">put</button> <button id="doDelete">delete</button> <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 .
4.2 HttpServletRequest
这个类就提供了一组方法 , 让我们能够获取到HTTP请求中的这些信息 .
4.2.1 核心方法
4.2.2实例
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("<br>"); stringBuilder.append(req.getMethod());//返回请求的HTTP方法的名称 stringBuilder.append("<br>"); stringBuilder.append(req.getRequestURI());//返回请求的URL的一部分 stringBuilder.append("<br>"); stringBuilder.append(req.getQueryString());//返回包含在路径后的请求URL中的查询字符串 stringBuilder.append("<br>"); Enumeration<String> headersnames = req.getHeaderNames(); while(headersnames.hasMoreElements()){
String name = headersnames.nextElement(); stringBuilder.append(name+":"+req.getHeader(name)); stringBuilder.append("<br>"); } //设置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抓包的结果 :
还可以通过form构造请求 :
<!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 , 只需要掌握两个操作 :
- 把json格式的字符串转化为Java对象 ;
- 把Java对象转成json字符串 .
对应到一个类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和类里的属性不一致, 会发生什么?
此时出现内部服务器错误 .
4.3 HttpServletResponse
4.3.1 核心方法
注意 : Request是get系列的方法, Response则是一些set系列的方法.
简单演示上述API :
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()+""); } }
显示时间戳, 每隔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; @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.1准备工作
- 创建好项目 ;
- 构建好目录 ;
- 引入依赖 ;
- 把留言板项目拷贝到项目中 .
这个html应该放在webapp目录下 .
此时我们就可以换一种打开方式了 .
5.2 约定好前后端交换的接口
5.3 实现提交数据
编写前端请求代码(第3部分)
//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>
编写后端响应代码(第4部分)
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 , 来查看结果 :
编写后端请求代码(第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); }
整体代码
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); } }
编写前端响应代码(第2部分)
/* 这个函数在页面加载时,从服务器获取到当前的消息列表 并显示到页面上 */ 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里的内容就会丢失 ! 如何解决这个问题呢 ?
- 存文件 , 使用IO流来读文件/写文件 .
- 数据库 , 使用MySQL + JDBC编程 .
此处我们让服务器把数据保存在MySQL服务器上 .
5.4将数据保存在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(""); } 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<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); 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; } }
允许效果如下 :
内容任然存在 !
中断程序的运行 , 并再次启动 :
再次刷新页面 ,内容仍然存在 !
如果你发现结果不符合预期 , 怎么办 ?
起手式–抓包 !!!
1.一个是抓页面加载,看看GET /message方法请求和响应是啥样的 .
- 如果请求没问题,响应有问题,说明是后端问题 .
- 如果请求有问题,响应没问题,说明是前端问题.(检查发送ajax的代码)
- 如果请求响应都没问题,但是页面不能正确显示,还是前端问题~~(检查ajax的回调)
2.还可以再抓包POST看看有没有问题 .
3.还可以看看数据库里有没有正确数据.
如果数据库有数据,说明大概率是GET的时候出问题了.如果数据库没数据,说明大概率是POST的时候出问题了!!!
顺便记录一下笔者在这里出现的问题 :
1.没有加载jQuery .
2. 数据库密码写错了 .
你可能会问了 , 你这数据库密码不就泄露了吗 ?
我只能说 , 银行卡里没有余额,那掩盖密码也是无用的 .
六 : Cookie和Session
6.1 工作方式回顾
Cookie是浏览器给HTTP协议提供的一个持久化存储数据的方案.
典型应用 : 保存用户的身份信息 !
注意 : Cookie是在客户端存的 , Session是在服务器存的 , Sessionid是在客户端和服务器都存了 .
注意 : 实现登录和身份验证是Cookie的一种典型的应用 , 此时需要Cookie和Session搭配工作 , 其他场景则不一定需要搭配Session . Cookie里是可以存多个键值对的 , 具体存什么 , 是程序猿自己约定的 . 比如你想存登录的时间 , 就可以以LoginTime为key , 以登录的时间戳为value .
6.2 Servlet对Cookie和Session提供的API
6.2.1 HttpServletRequest 类中的相关方法
6.2.2 HttpServletResponse 类中的相关方法
6.2.3 HttpSession 类中的相关方法
一个 HttpSession 对象里面包含多个键值对. 我们可以往 HttpSession 中存任何我们需要的信息.
6.2.4 Cookie 类中的相关方法
每个 Cookie 对象是一个键值对
6.3 代码示例—实现用户登录
功能 : 登录页可输入用户名和密码 , 服务器验证正确性 . 如果正确跳转到主页 , 并在主页中显示出当前用户的身份信息 , 且显示出当前用户的登录的访问次数 . (当用户第一次登录成功后 , 就无须重新登录了) .
主页代码 : 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("")){
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>upload</title> </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全部介绍完毕 !
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126062.html