HTTP协议就是 HTTP客户端 和 HTTP服务器之间用来进行通信的协议.
HTTP客户端就是浏览器(当然也有别的)
HTTP服务器,则是有很多种实现
而tomcat则是在Java圈子中,最知名的,最广泛使用的HTTP服务器.
一、Tomcat如何使用
另外,下面的那个webapp就是app应用也就是一个"网站",一个tomcat上可以部署多个网站,此处就被称为"webapps",这里包含的很多目录,都试做一个独立的网站,后面我们写的代码都是放在这个"webapps"里部署的.
启动Tomcat:
这个就是tomcat的启动程序
看到这个就说明启动成功了!!!
此时打开Tomcat的欢迎界面:
在地址栏输入127.0.0.1:8080
如果没有进入成功说明8080默认端口被占用,需要关闭在8080端口运行的程序.
tomcat最大作用就是把咱们写好的网站给部署上去,前端+后端
我们这里就拿之前写的博客项目代码示例:
拷贝之前的博客代码到webapp
然后这么搜索就可以了!!!(记住一定是按照路径进行访问)
这种访问方式和之前的直接右键最本质的区别是:这种环回ip访问是通过网络进行的访问,是跨主机的!!!
但是因为电脑是没有外网ip的所以只能在局域网,或者环回ip中使用,想要获得外网ip最简单的方法就是直接租一个服务器.
二、基于Tomcat的后端开发编程(Servlet)
网站后端运用的是http服务器,但是我们的Tomcat已经把一些常用的http相关的底层操作给封装好了,而我们只需要使用Tomcat给的api即可.
这些Tomcat给java提供的进行网页开发的原生的api被称为Servlet.
Servlet的定义:
这里再说一下动态页面:
网页分为两种:
动态页面:内面内容随着输入参数的不同而改变.(浏览器搜索结果页面,也就是html+数据)
静态页面:页面内容总是恒定不变的.(浏览器搜索页面,也就是只有html)
现在我们来写我们的第一个Servlet程序:
写个hello world
我们预期写个servlet程序,部署到tomcat上,通过浏览器访问,的到hello world字符串.
这个hello world共有7个步骤:
1.创建项目
此处我们要创建一个maven项目
maven是一个叫做工程管理工具,它能够干得事情:
规范目录结构
管理依赖(使用了啥第三方库之类的,都能处理好)
构建
打包
测试
......
这里我们主要使用它的管理依赖以及打包的功能.
使用Maven创建项目.注意Maven直接就是idea内置的!!!
点开目录,我们能够看到一些程序.
2.引入依赖
引入servlet对应的jar包
Maven Repository: javax.servlet » javax.servlet-api » 3.1.0 (mvnrepository.com)
在这个网址,可以直接拷贝上面的代码放到
直接将Maven中的内容粘贴到dependencies中,这个标签就相当于是我们自己写的,如果有多个依赖都可以进行粘贴.第一次粘贴可能是红的,我们可以进行包的下载,下完了后也就不红了.
可以说这里我们引入的依赖就是maven仓库的坐标,以后获取的方法以及引用就是从这个坐标中获取了!!!
3.创建目录
虽然Maven已经自动帮助我们创建了一些目录了,但是还不够.此处是需要使用maven开发一个web程序,但是还不够.
a.在main目录下,(和java,resources并列),创建一个webapp目录.
单词要一致,不能拼写错误.
b.在webapp下创建WEB-INF目录
c.再在WEB-INF目录下创建一个web.xml文件
此时我们的目录结构也就完成了!!!
注意:此处的目录名字以及结构都不能够发生错误.
d.给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>
注意:这个东西不要死记硬背,这个东西就放在手边,想要的时候直接进行复制.
当前写的Servlet程序相比以往写的代码来说,最大的区别就是没有main方法,而main方法在程序中就相当于发动机,如果想要让一辆没有发动机的车跑起来,就需要给他分配个车头,带着它走.而在这里我们的车头就是Tomcat,而写好的servlet程序就是车厢.咱们把写好的servlet代码放到webapps目录下,而哪些是需要tomcat拉着走的车厢呢?怎么识别?就是看咱们现在复制的这串web.xml
所以说现阶段不需要理解其中的含义,就当做一个影响标准就好!!!
.
注意:在这里飘红并不代表错误,因为idea能够准确的识别java代码,但是对其他代码不能够完全进行识别.(单纯就是idea识别的不准)
4.编写代码
在main目录中的java文件夹创建一个类,然后继承HttpServlet类.
HttpServlet是servlet api里提供的现成的类,写servlet代码一般都是继承这个HttpServlet类.
然后重写doGet方法:
我们这个doGet方法,不需要我们自己手动调用,而是交给Tomcat去调用.Tomcat收到get请求.就会触发doGet方法.
Tomcat会构造好两个参数:req和resp
这里的req是TCP socket中读出来的字符串,按照HTTP协议解析出来得到的对象.
这个对象里的属性是啥?
就是和HTTP请求报文格式相对应的!!!
这里我们的resp对象是一个空的对象.这就是程序员要干得事情了.程序员需要在doGet,根据请求req,结合我们业务逻辑构造出一个resp对象来.(也就是干了服务器根据请求计算响应的工作,其中的"resp"本质上是一个输出型参数)
这里我们的surprt.doGet()只是返回了一个错误页面,和我们要求的意愿是不符的,所以一定要删除!!!
doGet的目的就是根据请求计算响应.
req是Tomcat针对请求已经解析好的字符串.
resp则是一个空的对象(就是new了一个对象,里面的属性没设置需要程序员自行设定)
此处我们的"hello world"代码这只是打印一个"hello world",不需要使用到req
这里进行的write操作其实就是网resp的body部分尽心写入.等resp对象构造好了,Tomcat会统一转成HTTP响应的格式,再写socket.
接下来,添加注解
这个是Tomcat为我们实现的注解,注解的作用就是针对这个类/方法进行额外的"解释说明"赋予这个类/方法额外的功能/含义.
此处的注解的作用就是将我们的这个HelloServlet这个类 和一个HTTP请求具体的路径进行关联.
doGet是Tomcat收到GET请求的时候就会调用.具体要不要调用doGet,会得看GET请求的路径是啥.不同的路径会触发不同的代码(比如这里的/hello就关联到HelloServlet类上)
此处的注解就相当于一个"导购员",它会根据收到的请求的种类选择具体执行哪个类.
5.打包程序
也就是把程序编译好(得到一些.class文件)
然后再把这些.class达成一个压缩包
jar包就是一种.class压缩包.
但是我们此处是要打成一个war包,jar只是一个普通的java程序,而war则是Tomcat专属的用来描述webapps的程序.一个war就是一个webapp
这样就是打包成功了!!!
打包成功后包就在左侧的target目录下.
默认情况下,系统打的是jar包,如果想要打war包,则还需要微调一下.
在底下的pom.xml中加入一个packaging的标签:
这就是描述我们打的包的格式.
另外,还可以设置打的war包的名字:
如果不写就是生成一个比较麻烦的名字.
然后重新进行打包
此时就生成了一个全新的war包!!!
打开它在文件夹中的位置.
6.部署操作
包刚才打包好的war拷贝到Tomcat的webapps目录中即可.
无论你的Tomcat是在当前的电脑上还是不同的电脑上,都是这样进行拷贝.(其他主机或者云服务器都是这样)
然后启动Tomcat:
7.验证
打开浏览器输入url,检查一下我们写好的这个代码.
然后根据webapps里存储的路径进行访问:
第一级路径是hello_Servlet,第二级路径是我们上面在代码中利用注释确定的/hello这个路径
最终我们的hello world就出来了!!!
小结:
在刚才的浏览器地址栏输入url之后,浏览器就造就了一个对应的HTTP GET请求,发给了Tomcat.Tomcat 就根据它的第一级路径,确定了webapp.根据第二级路径确定了调用的是哪个类.
再然后通过Get还是Post确定调用的是HelloServlet的哪个方法(doGet,doPost......)
此时Tomcat就完成了对应的操作了!!!
上述步骤,是使用Servlet最朴素的步骤,当然也可以通过一些操作来进行简化:
在第5以及6步中,可以使用IDEA的Tomcat插件,把Tomcat集成到IDEA中,就省去手动打包,手动部署的过程,只需要按一下运行,就可以自动打包部署了.
但是,一般来说,手动打包部署应用在上线操作中,而自动打包部署应用在开发环境中.使用环境不同,所以说都得会!!!
IDEA中提供了一些api可以让程序员开发插件,对IDEA的功能进行扩展.
在这里进行插件的下载.
首次使用这个插件需要进行配置:
点击这个
然后添加一个Tomcat
这里主要需要进行填写的是Tomcat的绝对路径:
点击确定,此时我们的右上角就有了一个配置文件:
点击三角号运行即可.
此时显示运行失败了,仔细读一下报错信息,原因是我们的8080端口被占用了(很常见的问题)
netstat -ano|findstr 8080//这是查找8080端口是什么进行在占用
然后针对进程id进行关闭:
taskkill -pid 24168 -f
此时就启动成功了.
然后打开刚才的路径进行检验:
还是可以运行!!!
这里解释一点:smart Tomcat不是自动把war包给拷贝了,(webapps历史不变的)是通过另外一种方式来启动的tomcat.
Tomcat支持启动的时候显示指定一个特定的webapp目录.(相当于是让tomcat加载单个的webapp)没有加载的过程,也没有打包的过程,也没有拷贝的过程.
而这个页面里面没有欢迎页面:
如果你直接进行一手欢迎页面的访问,就会404
注意:这里"127.0.0.1:8080"后面的一级路径如果使用插件,是项目名称.如果不适用插件的话,采用手动部署的方式,就是war包名称.
三、访问出错
出现404:
当前的路径写错了
确认路径没有出错需要确认三个地方:
地址栏中没有出错
context path没有出错
servlet path没有出错
2. webapp没有被正确部署
web.xml有没有写对
出现405:
如果都到了个get方法,但是代码中没有doGet就会405
super.doGet没有删除:
出现500:
本质上就是代码出错了,而且日志上会告诉你哪一行出错了!!!
出现空白页面:
就是没有返回客户端信息.
无法访问此网站:
也就是Tomcat没有启动.
检测方法:查看端口是不是冲突了!!!
四、Servlet中常用的类
主要掌握三个类即可:
HttpServlet
HttpServletRequest
HttpServletResponse
1.HttpServlet
a.init
tomcat首次用到了和该类相关的请求时自动实例化(类似于之前的懒汉模式)
这里我们的HelloServlet2类继承了HttpServlet类,所以自然也自动实现了init(在Tomcat收到请求关联到了这里的"hello"路径的时候会调用init默认是没有东西的)
当然也是冲动重写的:
在页面调用成功的时候,控制台就会调打印 init.
b.destory
这个方法会在HttpServlet实例不再使用的时候调用一次,但是在Tomcat运行的全过程中,HttpServlet都在使用,所以只有当服务器程序结束的时候才会调用.
看这里,调用已经结束:
结束程序调用destroy.
注意:这里的destroy只会在正常结束程序的时候进行调用,但是如果你采用直接杀进程的方式结束程序,那也会有可能来不及调用这个方法.(所以不太推荐使用不太靠谱)
c.service
这个方法会在收到HTTP请求的时候进行调用.
如我们的doGet方法就是其中一个
这三个方法使我们在使用HttpServlet的时候最为常用的方法:
为此还引出了一道比较著名的面试题:
Servlet的生命周期是怎么回事(具体来说就是什么东西什么阶段要干啥(调用哪个方法)):
程序开始的时候,执行init
每次收到请求,执行service
每次销毁之前,执行destroy
之后就是像doGet之类的方法了:
在这里我们将所有方法列举出来:
然后运行程序
发现这里直接使用url进行访问触发的只是doGet方法.
那我们剩下的请求该如何触发呢?
方法一:ajax
首先我们要在webapps的目录下创建一个html的前端文件
然后最好使用vscode打开
然后再引入jquery cdn引入库.并使用ajax方法
注意:
此处是相对路径写法,也可以写作称为绝对路径:
然后打开浏览器(开发者控制台):
我们发现也是可以正常调用所有方法的请求的!!!
方法二:postman
这里我使用get方法进行发送收到的也是doGet
如果改成Post方法进行发送,获取到的请求也是doPost.以此类推(我们就可以使用postman构造出各种各样的请求)
2.HttpServletRequest
一个HTTP请求中包含的所有部分都会在这个类中体现出来.
而HTTP请求的这个对象是由Tomcat自动构造的.Tomcat其实监听窗口、接受连接、读取请求、解析请求、构造请求对象等一系列工作.
HttpServletRequest类中的常见方法:
其余的都是字面意思以及使用方法
最重要的两个方法:
query String是键值对结构,所以此处就可以通过getParameterNames( )方法来根据key获取到value
另外:
就是这些可以直接获取HTTP请求header部分内容的方法:
getHeader()就是可以直接获取header部分的值(以字符串形式)
后面的四个是因为有些内容(如字符集,长度等)比较重要,所以独自封装在了一个方法中以便调用.
而这个:
就是相当于把HTTP请求中的body给读了出来.
代码演示:
此时我们得到的这些结果就是这些方法得到的结果!!!
如果想要看得更加清楚,就需要让它换行显示:
接下俩就是获取header部分内容的方法了(重要):
这种代码风格属于迭代器遍历的方式,就是遍历完header里面最后一个元素为止.
这个就是header中的所有元素!!!
3.前端给后端传参
a.GET/query string
这里我们计划传送两个数字,一个是studentId一个是classId,也就是就收一个形如:
?studentId=10&classId=20的这样一个query string.
这里的Tomcat键值对会被Tomcat处理成形如Map这样的结构,后续就可以通过getParameter进行value值的获取了!!!
b.POST,from
对于前端是from表单这样格式的数据,后端还是使用getParameter来进行获取.
通过html的from表单就可以完成这部分表格的构造.
使用from表单,就可以构造一个Post请求,然后接收浏览器用户传输过来的数据:
这是具体的表现形式
点击提交之后,因为没有进行网页跳转的页面的设置所以会显示404
此时使用fiddler进行抓包:
这里body的大小就是我们query string的值.
而这里的applocation/xx就是指这是一个from表单构造出来的请求.
此时也可以进行后端代码的编写:
依然跟刚才的代码一致但是WebServlet改成了刚刚前端指定的那个网址
点击之后会自动跳转到这个页面.(这也是一个比较简单的前后端交互了!!!)
c.json格式
就是类似:
{
studentId:10,
classId:20
}
我们也可以使用json把body这个部分进行组织.前端可以通过ajax的方式构造出这个内容,当然更简单的是直接使用postman进行body的构造.
当然此时如果发送因为没有这里的postParameterServlet2页面所以说会读取失败,现在我们来看后端的写法(这里我们采用之前学过的getInputStream字节流进行读取):
至于读取长度就是header里面的content-Length使用getContentLength()方法进行这个json格式的长度来规定字节流读取的长度.然后这个数组编写为字符串形式:
这个代码的执行流程和上面的from表单传参效果是类似的.只是传输的数据格式不同:
from表单是classId=10&studentId=20
而json的格式是
{
studentId:10,
classId:20
}
这三种传参都是极为常见的.
但是还有个问题:
第三种方式只是把整个的json格式body从请求中读了出来,但是还没有将其中的key与value读出来.
此时就要使用第三方库来进行json格式数据的解析:jackson(有spring给他背书)
依旧是进入Maven 存储库:搜索/浏览/探索 (mvnrepository.com)maven库
搜索并下载2.14.1版本的jackson
然后将依赖并列到之前的<dependencies>里面:
此时就可以舍弃我们之前的字节流读取形式(直接使用jackson进行读取):
实例化一个objectMapper类进行jackson的使用,这里的readvalue就是把一个json格式的字符串转换成Java格式.
如果此处要使用readValue()方法,需要把要提取的key单放在一个类里面:
解释一下最后这个代码:
然后就可以进行打印了
readvalue():把json字符串转成java对象.
writevalue():把java对象转成json格式字符串.
4.HttpServletResponse
这两个方法就是设置http响应中报头的key值
而这两个是设置http响应中的content Type的:
也就是那个resp.setContentType("text/html");
第二个是设置响应的字符编码的.
如果没有resp.setCharacterEncoding("utf8")来设置字符集,浏览器就无法识别中文:
只有设置了字符集,浏览器才能够识别中文:
其实这个content Type与字符集也是可以设置在一起的:
然后是他:
他的作用就是让浏览器自动的跳转到一个新的地址(3XX的状态码)
在正常的网页中,我们实现页面跳转是主要就是只用这个方法
点击确认:
然后就自动跳转到了百度
使用fiddle进行抓包,就会发现我们的响应:
这样是个302的状态码,而这里的Location就是我们之前在后端中设置的响应地址.
理解了上述流程,我们就可以手动设置这个sendRedirect()方法:
选择设置302状态码,然后手动添加一个header:设置键值对.
效果跟刚才也是一致的.
五、实例应用
这里我们选择实现之前的表白墙程序:
这个表白墙程序有非常严重的问题:
如果关闭页面或者刷新页面,之前写的内容就会全部清空.
如果一个机器上的数据,第二台机器上是看不到的(这些数据都是在本地的浏览器中)
解决思路:
让服务器来存储用户提交的数据.有服务器来保存.当心的浏览器打开页面的时候,从服务器获取数据.
(也就是存档和读档功能)
首先先设计一下程序:
写web程序,务必要重点考虑前后端是如何交互的.也就是约定好后端交互的数据格式.
亲后端交互接口设计:
也就是规定请求是啥样响应是啥样,浏览器啥时候发的这个请求,浏览器按照啥样的格式来解析.
首先我们要确定哪些环节设计到了前后端的交互:
点击提交.浏览器把表白信息发到服务器这里
请求:
POST/message
{
from:"张三",
to:"李四",
message:"你好"
}
响应:
HTTP/1.1 200 OK
页面加载.浏览器从服务器获取到表白信息.
请求:
GET/message
响应:HTTP/1.1 200 OK
而message的格式我们就采取json的格式进行组织:
[
{
from:"张三",
to:"李四",
message:"你好"
}
{
from:"张三",
to:"李四",
message:"你好"
}
]
注意:此处的约定没有什么强制要求,都可以进行更改.
这时就设计完成了,可以进行代码的编写了:
首先创建一个新的项目,引入Servlet依赖(引入3.1.0):
然后进行Jackson依赖的引入(引入2.14.2):
这个程序我们采用数据库mysql进行数据的存储,所以也要引入mysql的依赖:
选取5.1.49:
接下来是创建目录:
主要就是在main目录下创建一个webapp目录,然后再webapp目录下创建WEB-INF目录,然后在WEB-INF目录下穿件web.xml文件.
注意:一步都不能错!!!
接下来就是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>
这段直接复制进web.xml里!!!
下面就是编写代码了:
在java文件夹下进行代码的创建:
注意这里的WebServlet需要和之前我们在代码设计中约定的一致(所以使用/message)
首先是连个方法,一个从服务器获取数据,一个是向服务器提交数据:
我们先来编写post的代码(依照之前的约定):
首先收到的是一个json格式的数据,所以要先使用jackson进行数据的解析(上面有讲这里再说一遍):
首先定义一个类
然后还是之前那一套
此时呢,我们就把收到的json格式的数据转换成了一个字符串.
获取到了数据就需要保存了:
我们先不使用mysql来进行存储,先使用一个变量来存储:
记得进行状态码(不设定也是默认200).
此时doPost就完成了:它的工作就是把json数据解析然后存到List里面.
然后就是doGet的编写了:
根据我们的设计,返回的数据也是一个json格式,如果还要将List中存储的数据再转换成json格式(基于objectMapper的writeValue方法)然后进行发送.
objectMapper.writeValue(resp.getWriter(),messageList);
这一行代码同时完成了将java对象转换成json格式和将这个json字符串写到响应当中两步.
第一步也就是resp.getWriter规定了写出来的这个字符串要写到哪里去(流对象)
第二步也就是messageList将List中的数据转成了字符串格式.
如果分步写的话就是这个样子:
此时doGet就完成了!!!
另外,还可以严谨的设置一个content Type:
显示的告诉浏览器我们的数据格式是json格式的.
此时表白墙程序的后端就大功告成了!!!
阶段性成果可以使用postman测试一下:
注意:这里postman的作用是在模拟浏览器,浏览器用户填写数据后的交互,就相当于向服务器发送了一个post请求,然后再由后端的doPost方法进行接收
所以此时我们post后,我们的"浏览器"是不会有任何的响应的,只有切换到get进行数据的接收,才会看到结果:
接下来我们要编写前端代码:
将之前写的messageWall代码放到webapp文件夹里面.
要想使用前后端交互,需要使用ajax,要想使用ajax就需要首先引入jquery
看看我们之前前端的代码逻辑,现在我们需要新增一个提交功能,就是利用ajax将我们这里存储的数据提交到服务器就可以了:
现在的ajax还需要构造一个body,也就是包含from,to,message的js格式的body:
这几个对象,取决于上面的变量:
相当于读取了三个输入框的数值,而这里的输入框的值就是用户输入的内容.
而这里就有了一个问题,我们的网络传输能传输的只有字符串,而此时我们的这个body还是js对象,要想传输它还需要把它变成一个js字符串:
相较于java的需要一个jackson才能转换,html直接在标准库中内置了一个方法进行json字符串的转换.
另外,我们还可以约束一下文件传输的格式:
此时让我们来检查一下前端代码的完成程度:
提交一份数据
然后点开fillder
这是点击完提交后抓到的一个数据包.
主要关注一下我们的方法,url,body部分,content-type这些部分,如果和当初写前端程序之时设置的一致,就说明前端程序就没有错误了!!!(气候的所有项目都可以这样进行测试)
此时"存档"操作就已经完成了,我们现在要完成"读档操作":
此时我们的body拿到的就是一个js数组了,本来服务器返回的是一个json但是jquery能够自动识别json格式自动帮我们把json转换为了js,接下来我们只是需要遍历这个数组,把元素取出来打印即可.
此处的逻辑其实跟上面打印div的逻辑是一样的只不过这个是从get到的js数组中打印.
此处要指定好我们数据的来源不要混了.
我们要知道,读档操作是读取所有的数据,而存档操作是是只存一条数据,所以说这里的body代表了全部数据,而遍历body得到的message则是其中一组js数据,而message.from则是其中我们需要的那几个属性.(所以说上面说过这里读档功能读取的是一个数组,一个装满"message"的"body"数组)
这里还差一步我们containerDiv还没有设定:
此时表白墙程序前后端的程序算是全部写完了,看看效果:
首先构造两个数据,然后关闭这个页面查看它的读档功能:
能够正常读取内容!!!
使用fiddler也可以看到数组是可以正确传输的!!!
但是,此程序还有缺点,就是如果关闭服务器,那么在服务器中存储的内容也都将被清除!
要想要持久化保存,就需要写入文件中(硬盘):
直接使用流对象写入文本文件
借助数据库
首先第一步就是创建表:
表的具体内容:message(from,to,message)
首先创建一个数据库:
然后再数据库按照上述要求建一个表:
接下来我们创建一个DBUtil类作为工具类,实现mysql和服务器的连接(也就是JDBC的功能):
这里的DBUtil我们需要两个方法来实现:
建立连接类
断开连接类
但是呢,在建立连接之前首先要获取连接,也就是url,用户名,密码那一堆:
这个类是建立连接对象的实例化.
静态成员是跟随类对象的,类对象在整个进程中只有唯一的一份.静态成员也相当于是唯一的实例.(也就是之前多线程讲过的单例模式,饿汉模式)
另外,需要对new的对象进行初始化操作:(可以通过静态代码块进行初始化的编写)
之后编写断开连接类:
其实就是按照顺序进行这三个对象的关闭
接下来的建立连接类其实就是返回该dataSource的连接即可:
此时其实这个DBUtil类就编写完成了!!!
接下来就是用这个获取数据与存入数据的类替换之前的用来存放数据的List:
这是存放数据:
这是取出数据:
接下来就是save与load部分的编写了(基本上都是JDBC的操作了):
新添操作:
查询操作:
此时数据库代码就已经完成了!!!
这是一个表白墙程序就完美了!!!
此时重新启动数据库,然后再提交一条数据:
我们发现此时数据就会同时出现在数据库中.
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 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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-03-24
* Time: 22:34
*/
class Message{
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
//这个就可以直接换掉了
//private List<Message> messageList=new ArrayList<>();
//向服务器提交数据
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper=new ObjectMapper();
Message message=objectMapper.readValue(req.getInputStream(),Message.class);
//messageList.add(message);
save(message);
resp.setStatus(200);
}
//从服务器获取数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper=new ObjectMapper();
resp.setContentType("application/json;charset=utf8");
List<Message> messageList=load();
objectMapper.writeValue(resp.getWriter(),messageList);
}
//往数据库中存一条消息
private void save(Message message){
Connection connection=null;
PreparedStatement statement=null;
try {
//1.建立连接
connection=DBUtil.getConnection();
//2.构造sql语句
String sql="insert into message values(? ,? ,? )";
statement=connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
//3.执行sql
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4.关闭连接
DBUtil.close(connection,statement,null);
}
}
//从数据库中取所有数据
private List<Message> load(){
List<Message> messageList=new ArrayList<>();
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
//1.和数据库建立连接
connection=DBUtil.getConnection();
//2.构造sql语句
String sql="select * from message";
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;
}
}
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 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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-03-24
* Time: 22:34
*/
class Message{
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
//这个就可以直接换掉了
//private List<Message> messageList=new ArrayList<>();
//向服务器提交数据
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper=new ObjectMapper();
Message message=objectMapper.readValue(req.getInputStream(),Message.class);
//messageList.add(message);
save(message);
resp.setStatus(200);
}
//从服务器获取数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper=new ObjectMapper();
resp.setContentType("application/json;charset=utf8");
List<Message> messageList=load();
objectMapper.writeValue(resp.getWriter(),messageList);
}
//往数据库中存一条消息
private void save(Message message){
Connection connection=null;
PreparedStatement statement=null;
try {
//1.建立连接
connection=DBUtil.getConnection();
//2.构造sql语句
String sql="insert into message values(? ,? ,? )";
statement=connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
//3.执行sql
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4.关闭连接
DBUtil.close(connection,statement,null);
}
}
//从数据库中取所有数据
private List<Message> load(){
List<Message> messageList=new ArrayList<>();
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
//1.和数据库建立连接
connection=DBUtil.getConnection();
//2.构造sql语句
String sql="select * from message";
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;
}
}
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-03-27
* Time: 19:41
*/
public class DBUtil {
private static DataSource dataSource=new MysqlDataSource();
static {
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/messageWall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("111111");
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container{
width: 600px;
margin: 0 auto;
}
h1{
text-align: center;
}
p{
text-align: center;
color:#666;
}
.row{
/* 开启弹性布局 */
display: flex;
height:40px;
/* 水平方向居中 */
justify-content: center;
/* 竖直方向居中 */
align-items: center;
}
.row span{
width: 100px;
}
.row input{
width: 200px;
}
.row button{
width: 308px;
height: 30px;
color:white;
background-color: orange;
/* 去掉边框 */
border: none;
border-radius: 5%;
}
.row button:active{
background-color: #666;
}
</style>
<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>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
let containerDiv=document.querySelector('.container');
let inputs=document.querySelectorAll('input');
let button=document.querySelector('#submit');
button.onclick = function(){
let from = inputs[0].value;
let to = inputs[1].value;
let message = inputs[2].value;
if(from==''||to==''||message==''){
return;
}
let rowDiv=document.createElement('div');
rowDiv.className='row message';
rowDiv.innerHTML=from+'对'+to+'说:'+message;
containerDiv.appendChild(rowDiv);
for(let input of inputs){
input.value=' ';
}
let body ={
"from" : from,
"to" : to,
"message" : message,
}
$.ajax({
type:'post',
url:'message',
data:JSON.stringify(body),
contentType:"application/json; charset=utf8",
success:function(body){
console.log("文件传输成功!!!");
}
});
}
let revert=document.querySelector('#revert');
revert.onclick=function(){
//删除最后一条消息
let rows=document.querySelectorAll('.message');
containerDiv.removeChild(rows[rows.length-1]);
if(rows==' '){
return;
}
}
$.ajax({
type:'get',
url:'message',
success:function(body){
let containerDiv=document.querySelector('.container');
for(let message of body){
let rowDiv=document.createElement('div');
rowDiv.className='row message';
rowDiv.innerHTML=message.from+'对'+message.to+'说:'+message.message;
containerDiv.appendChild(rowDiv);
}
}
});
</script>
</body>
</html>
六、cookie
cookie是个啥:浏览器提供的可以持久化存储数据的机制.
cookie是从哪里来的:从服务器返回给浏览器的.服务器代码由程序员决定把啥样子的信息保存到客户端这边,通过HTTP响应的Set-cookie字段,把键值对写回去即可
cookie会在后续浏览器访问服务器的时候带回到请求的header中.(服务器可以通过cookie中的值确定连接服务器的这台主机的身份)
cookie存储在哪里:存储在浏览器(客户端)所在的主机硬盘中.浏览器会根据域名分别存储.
cookie有一个最为典型的应用场景:
表示用户的身份信息
也就是说一但我们登录成功一次,就会在cookie中持久的保存一份我们的身份信息,有了这个身份信息,淘宝就可以直接识别出我们的登录状态了!!!而每个cookie都有一个过期时间,就是过一段时间会删除身份cookie(可能是服务器删的也可能是浏览器删的).
服务器就把这些键值对称为session(会话)
把生成的唯一身份标识称为sessionId(会话Id)
注意理解cookie和session的区别:
关联:在网站的登录功能中,需要配合在一起使用.
区别:cookie是客户端的存储机制.session式服务器的存储机制.
cookie里面可以存储各种键值对(还可以存别的).session则是专门用来保存用户的身份信息
cookie完全可以单独使用,不搭配session(实现登录场景下)
session也可以不搭配cookie使用(手机app登录服务器,服务器也需要session,此时就没有cookie的概念)cookie跟浏览器强相关.
cookie是属于HTTP协议的一个部分
Session则是和HTTP无关(TCP,websocket...也可以用session)
这里我们模拟实现一个登录页面:
程序设计:
类似这种样子,点击按钮触发一个登录请求(LoginServlet)来验证密码是否正确.
如果登录成功,跳转到主页(另一个页面也是一个静态的html,也可以通过Servlet动态构造的页面)
这个例子里涉及到两个页面:
登录页面
主页面
涉及到两个Servlet:
处理登录的Servlet判断用户名和密码
构造主页面的Servlet
现在可以开始代码的编写了:
首先是创建文件:
根据设计创建.
编写前端部分:
首先把应该有的元素罗列在上面:
然后启动服务器看看效果:
也可以使用fiddler进行抓包:
发现所有值和之前缩写的前端代码都是对应的.就可以了
主要看这一个键值对:
它标志着这个前后端程序的交互是以from表单形式完成的!!!
之后进行后端内容的编写:
首先是之前form表单的跳转(action)的编写:
之后就是进行密码的验证(通常是用一张表直接进行用户名和密码的绑定,但是这里我们要练习的不是这一部分,所以就直接写死用户名和密码)
这里设置的逻辑是登录失败直接跳转到登录页面:
之后就是登录成功逻辑的编写:
首先是创建一个会话,这里的getSession(true)是在判断当前是否已经有对应的会话了(拿着请求中的cookie里的sessionId查一下hash表)如果没有找到sessionId就会重新创建会话,并插入到hash表中.如果查到了,就直接会返回查到的结果.
创建过程:
创建一个HttpSession对象
构造一个唯一的SessionId
把这个键值对插入到哈希表中
把sessionId设置到响应报文,Set-cookie字段中.
注意:这里的HttpSession自己也是一个键值对(且是程序员自己自定义的键值对),愿意存啥就存啥
使用setAttribute和getAttribute来进行键值对的存取
这个true和false的意思就是如果没有账号的话是否要创建新的
之后就是编写IndexServlet生成主页了:
首先,我们要判断用户的登录状态,如果还么登录,得先要求用户登录,如果已经登录了,则根据用户的用户名显示到页面上
如果未登录,就让用户返回登录页面重新登录:
如果已经登录了:
就可以取出我们之前存的username
注意:
此时程序就写完了,当我们输入正确的用户名与密码之时,就能够正常跳转页面了:
此时我们打开fillder看一看抓包:
后续的登录之后的操作就只需要你的sessionId了,不需要再另行登录创建.