JavaWeb
课程安排
1.HTTP、Tomcat、Servlet
2.Request(请求)、Response(响应)
3.会话技术(Cookie、Session)
4.Filter(过滤器)、Listener(监听器)
B/S 架构:Browser/Server,浏览器/服务器 架构模式,它的特点是,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web资源,服务器把Web资源发送给浏览器即可
好处:易于维护升级:服务器端升级后,客户端无需任何部署就可以使用到新的版本
静态资源:HTML、CSS、JavaScript、图片等。负责页面展现
动态资源:Servlet、JSP 等。负责逻辑处理
数据库:负责存储数据
HTTP协议:定义通信规则
Web服务器:负责解析 HTTP 协议,解析请求数据,并发送响应数据
一、maven
0.在csdn中 网址:
超级详细的 Maven 教程(基础+高级)_maven教程_汪了个王的博客-CSDN博客
1.什么是maven
Maven是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具。
2.maven的作用
1.依赖管理
方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题
2.统一项目结构
提供标准、统一的项目结构
3.项目构建
标准跨平台(Linux、Windows、MacOS)的自动化项目构建方式
3.maven的安装
首页:
Maven – Welcome to Apache Maven
下载页面:
或者你也可以选择之前的版本:
然后里面选择自己对应的版本下载即可:
下载之后解压到非中文、没有空格的目录,如下:
4.指定本地仓库
本地仓库默认值:用户家目录/.m2/repository。由于本地仓库的默认位置是在用户的家目录下,而家目录往往是在 C 盘,也就是系统盘。将来 Maven 仓库中 jar 包越来越多,仓库体积越来越大,可能会拖慢 C 盘运行速度,影响系统性能。所以建议将 Maven 的本地仓库放在其他盘符下。配置方式如下:
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>D:\software\maven-repository</localRepository>
本地仓库这个目录,我们手动创建一个空的目录即可。
记住:一定要把 localRepository 标签从注释中拿出来。
注意:本地仓库本身也需要使用一个非中文、没有空格的目录。
5.配置阿里云提供的镜像仓库
1.将原有的例子配置注释掉
<!-- <mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror> -->
2.加入自己的配置
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
6.配置环境变量
Maven 是一个用 Java 语言开发的程序,它必须基于 JDK 来运行,需要通过 JAVA_HOME 来找到 JDK 的安装位置。
可以使用下面的命令验证:
C:\Users\Administrator>echo %JAVA_HOME%
D:\software\Java
C:\Users\Administrator>java -version
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
然后新建环境变量:
配置环境变量的规律:
XXX_HOME 通常指向的是 bin 目录的上一级
PATH 指向的是 bin 目录
在配置 PATH:
通过 mvn -v
验证:
C:\Users\Administrator>mvn -v
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:47+08:00)
Maven home: D:\software\apache-maven-3.3.9\bin\..
Java version: 1.8.0_333, vendor: Oracle Corporation
Java home: D:\software\jdk1.8\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 11", version: "10.0", arch: "amd64", family: "dos"
7.IDEA集成Maven
7.1配置Maven环境(当前工程)
7.2配置Maven环境(全局)
7.3创建Maven项目
7.4IDEA 导入 Maven 项目
方式一
方式二
二、HTTP
CSDN中的网址:
【计算机网络】HTTP 协议详解_http协议解析_吞吞吐吐大魔王的博客-CSDN博客
0.概念
概念:HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
1.Http常见请求方式
1. get:从数据库查询数据,select
2. post:向服务器发送数据并保存数据,insert
3. put:修改数据,update
4. delete:删除数据
2.HTTP 协议特点
基于TCP协议:面向连接,安全
基于请求-响应模型的:一次请求对应一次响应
HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。
缺点:多次请求间不能共享数据。
优点:速度快
3.HTTP-请求数据格式
4.HTTP-响应数据格式
HTTP状态码汇总(常见)
在csdn中:HTTP状态码汇总(常见)_IT萌萌熊的博客-CSDN博客
状态码200:表示服务器响应成功
状态码301 :
状态码301和状态码302相似,不同的是状态码301往往代表的是永久性的重定向,值得注意的是,这种重定向跳转,从严格意义来讲不是服务器跳转,而是客户端跳转的。这个“跳”的动作是服务器是通过回传状态码301来下达给客户端的,让客户端完成跳转。
状态码302:
代表临时跳转
状态码304 :
服务器通过返回,可以告诉客户端请求资源成功,
状态码403:
状态码403代表请求的服务器资源权限不够,也就是说,没有权限去访问服务器的资源,或者请求的IP地址被封掉了。
状态码404:
状态码404代表服务器上没有该资源,或者说服务器找不到客户端请求的资源,是最常见的请求错误码。
状态码500:
状态码500代表程序错误,也就是说请求的网页程序本身报错了。在服务器端的网页程序出错。由于现在的浏览器都会对状态码500做一定的处理,所以在一般情况下会返回一个定制的错误页面。
三、Tomcat
0.在scdn中的网址 官网
学习Tomcat这一篇就够了_轻松的小希的博客-CSDN博客
官网:
1.简介
1.1概念:
Tomcat是Apache 软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。
JavaEE:Java Enterprise Edition,Java企业版。指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF
Tomcat 也被称为 Web容器、Servlet容器。Servlet 需要依赖于 Tomcat才能运行
2.作用
封装HTTP协议操作,简化开发
可以将web项目部署到服务器中,对外提供网上浏览服务
3.基本使用 配置端口 部署项目
4.IDEA中创建 Maven Web项目
4.1使用骨架
4.2不使用骨架
4.3 使用骨架和不使用骨架的好处
使用骨架的主要好处是:
- 快速启动:骨架提供了一个项目的基本结构和配置,可以节省创建项目的时间。
- 预定义的依赖项:骨架通常包含一些常用的依赖项,如JUnit、Log4j等,以便快速开始编写代码。
- 预定义的插件:骨架还可以包含一些常用的插件配置,如编译插件、打包插件等,以帮助构建和部署项目。
不使用骨架的主要好处是:
- 自定义配置:可以根据项目的需求自定义配置,而不受骨架的限制。
- 精简项目结构:不使用骨架可以避免一些不必要的配置和依赖项,使项目结构更加简洁。
5.在IDEA中使用 Tomcat – 集成本地 Tomcat
1.
2.
四、Servlet
0.Tomcat工作机制动画演示(点击动图可全屏观看)
https://img-blog.csdnimg.cn/2018120522281643.gif
1.快速入门
Servlet 快速入门
1. 创建 web项目,导入 Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
2. 创建:定义一个类,实现 Servlet接口,并重写接口中所有方法,并在 service方法中输入一句话
public class ServletDemo1 implements Servlet {
public void service(){}
}
3. 配置:在类上使用@WebServlet 注解,配置该 Servlet的访问路径
@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {
}
4. 访问:启动 Tomcat,浏览器输入URL 访问该Servlet
http://localhost:8080/web-demo/demo1
2.执行流程
3.生命周期
对象的生命周期指一个对象从被创建到被销毁的整个过程
Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次
请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。
服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
@WebServlet(urlPatterns = "/demo",loadOnStartup = 1)
负整数:第一次被访问时创建Servlet对象
0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
方法介绍
4.体系结构
5.为什么要使用Servlet? ---处理动态资源的能力
因为web服务器(tomcat、Weblogic、iis、apache)没有处理动态资源请求的能力(即该请求需要计算),只能处理静态资源的请求(如果浏览器请求某个html页面,web服务器查看请求的html页面是否存在,存在则返回。)如果要让web服务器处理动态资源的请求,则需要使用CGI1程序、组件加容器的方式。
HttpServlet 原理
urlPattern配置
Servlet 要想被访问,必须配置其访问路径(urlPattern)
五、respon request
今日目标
掌握Request对象的概念与使用
掌握Response对象的概念与使用
能够完成用户登录注册案例的实现
能够完成SqlSessionFactory工具类的抽取
1、Request和Response的概述
==Request是请求对象,Response是响应对象。==这两个对象在我们使用Servlet的时候有看到:
此时,我们就需要思考一个问题request和response这两个参数的作用是什么?
-
request:==获取==请求数据
-
浏览器会发送HTTP请求到后台服务器[Tomcat]
-
HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
-
后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中
-
所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
-
获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
-
-
response:==设置==响应数据
-
业务处理完后,后台就需要给前端返回业务处理的结果即响应数据
-
把响应数据封装到response对象中
-
后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果
-
浏览器最终解析结果,把内容展示在浏览器给用户浏览
-
对于上述所讲的内容,我们通过一个案例来初步体验下request和response对象的使用。
@WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //使用request对象 获取请求数据 String name = request.getParameter("name");//url?name=zhangsan //使用response对象 设置响应数据 response.setHeader("content-type","text/html;charset=utf-8"); response.getWriter().write("<h1>"+name+",欢迎您!</h1>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Post..."); } }
启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容:
小结
在这节中,我们主要认识了下request对象和reponse对象:
-
request对象是用来封装请求数据的对象
-
response对象是用来封装响应数据的对象
目前我们只知道这两个对象是用来干什么的,那么它们具体是如何实现的,就需要我们继续深入的学习。接下来,就先从Request对象来学习,主要学习下面这些内容:
-
request继承体系
-
request获取请求参数
-
request请求转发
2、Request对象
2.1 Request继承体系
在学习这节内容之前,我们先思考一个问题,前面在介绍Request和Reponse对象的时候,比较细心的同学可能已经发现:
-
当我们的Servlet类实现的是Servlet接口的时候,service方法中的参数是ServletRequest和ServletResponse
-
当我们的Servlet类继承的是HttpServlet类的时候,doGet和doPost方法中的参数就变成HttpServletRequest和HttpServletReponse
那么,
-
ServletRequest和HttpServletRequest的关系是什么?
-
request对象是有谁来创建的?
-
request提供了哪些API,这些API从哪里查?
首先,我们先来看下Request的继承体系:
从上图中可以看出,ServletRequest和HttpServletRequest都是Java提供的,所以我们可以打开JavaEE提供的API文档[参考: 资料/JavaEE7-api.chm],打开后可以看到:
所以ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,这个时候就引发了下面这个问题:
-
该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
-
Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
-
要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法
对于上述结论,要想验证,可以编写一个Servlet,在方法中把request对象打印下,就能看到最终的对象是不是RequestFacade,代码如下:
@WebServlet("/demo2") public class ServletDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(request); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
启动服务器,运行访问http://localhost:8080/request-demo/demo2
,得到运行结果:
小结
-
Request的继承体系为ServletRequest-->HttpServletRequest-->RequestFacade
-
Tomcat需要解析请求数据,封装为request对象,并且创建request对象传递到service方法
-
使用request对象,可以查阅JavaEE API文档的HttpServletRequest接口中方法说明
2.2 Request获取请求数据
HTTP请求数据总共分为三部分内容,分别是==请求行、请求头、请求体==,对于这三部分内容的数据,分别该如何获取,首先我们先来学习请求行数据如何获取?
2.2.1 获取请求行数据
请求行包含三块内容,分别是请求方式
、请求资源路径
、HTTP协议及版本
-
获取请求方式:
GET
String getMethod()
-
获取虚拟目录(项目访问路径):
/request-demo
String getContextPath()
-
获取URL(统一资源定位符):
http://localhost:8080/request-demo/req1
StringBuffer getRequestURL()
-
获取URI(统一资源标识符):
/request-demo/req1
String getRequestURI()
-
获取请求参数(GET方式):
username=zhangsan&password=123
String getQueryString()
介绍完上述方法后,咱们通过代码把上述方法都使用下:
/** * request 获取请求数据 */ @WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // String getMethod():获取请求方式: GET String method = req.getMethod(); System.out.println(method);//GET // String getContextPath():获取虚拟目录(项目访问路径):/request-demo String contextPath = req.getContextPath(); System.out.println(contextPath); // StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1 StringBuffer url = req.getRequestURL(); System.out.println(url.toString()); // String getRequestURI():获取URI(统一资源标识符): /request-demo/req1 String uri = req.getRequestURI(); System.out.println(uri); // String getQueryString():获取请求参数(GET方式): username=zhangsan String queryString = req.getQueryString(); System.out.println(queryString); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
启动服务器,访问http://localhost:8080/request-demo/req1?username=zhangsan&passwrod=123
,获取的结果如下:
对于请求头的数据,格式为key: value
如下:
所以根据请求头名称获取对应值的方法为:
String getHeader(String name)
接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用
/** * request 获取请求数据 */ @WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求头: user-agent: 浏览器的版本信息 String agent = req.getHeader("user-agent"); System.out.println(agent); //遍历所有的请求头信息 Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { //注意:这里每次回拿到下一个节点的key,每一次循环只允许调用一次 String key = headerNames.nextElement(); System.out.println(key + ":" +request.getHeader(key)); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
重新启动服务器后,http://localhost:8080/request-demo/req1?username=zhangsan&passwrod=123
,获取的结果如下:
对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:
-
获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
ServletInputStream getInputStream() 该方法可以获取字节
-
获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
BufferedReader getReader()
接下来,大家需要思考,要想获取到请求体的内容该如何实现?
具体实现的步骤如下:
1.准备一个页面,在页面中添加form表单,用来发送post请求
2.在Servlet的doPost方法中获取请求体数据
3.在doPost方法中使用request的getReader()或者getInputStream()来获取
4.访问测试
-
在项目的webapp目录下添加一个html页面,名称为:
req.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- action:form表单提交的请求地址 method:请求方式,指定为post --> <form action="/request-demo/req1" method="post"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit"> </form> </body> </html>
2.在Servlet的doPost方法中获取数据
/** * request 获取请求数据 */ @WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //在此处获取请求体中的数据 } }
3.调用getReader()或者getInputStream()方法,因为目前前端传递的是纯文本数据,所以我们采用getReader()方法来获取
/** * request 获取请求数据 */ @WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取post 请求体:请求参数 //1. 获取字符输入流 BufferedReader br = req.getReader(); //2. 读取数据 String line = br.readLine(); System.out.println(line); } }
==注意==
BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。
4.启动服务器,通过浏览器访问http://localhost:8080/request-demo/req.html
点击提交
按钮后,就可以在控制台看到前端所发送的请求数据
小结
HTTP请求数据中包含了请求行
、请求头
和请求体
,针对这三部分内容,Request对象都提供了对应的API方法来获取对应的值:
-
请求行
-
getMethod()获取请求方式
-
getContextPath()获取项目访问路径
-
getRequestURL()获取请求URL
-
getRequestURI()获取请求URI
-
getQueryString()获取GET请求方式的请求参数
-
-
请求头
-
getHeader(String name)根据请求头名称获取其对应的值
-
request.getHeaderNames() 获取所有请求头的key
-
-
请求体
-
注意: ==浏览器发送的POST请求才有请求体==
-
如果是纯文本数据:getReader()
-
如果是字节数据如文件数据:getInputStream()
-
2.2.4 获取请求参数的通用方式
在学习下面内容之前,我们先提出两个问题:
-
什么是请求参数?
-
请求参数和请求数据的关系是什么?
1.什么是请求参数?
为了能更好的回答上述两个问题,我们拿用户登录的例子来说明
1.1 想要登录网址,需要进入登录页面
1.2 在登录页面输入用户名和密码
1.3 将用户名和密码提交到后台
1.4 后台校验用户名和密码是否正确
1.5 如果正确,则正常登录,如果不正确,则提示用户名或密码错误
上述例子中,用户名和密码其实就是我们所说的请求参数。
2.什么是请求数据?
请求数据则是包含请求行、请求头和请求体的所有数据
3.请求参数和请求数据的关系是什么?
3.1 请求参数是请求数据中的部分内容
3.2 如果是GET请求,请求参数在请求行中
3.3 如果是POST请求,请求参数一般在请求体中
对于请求参数的获取,常用的有以下两种:
-
GET方式:
String getQueryString()
-
POST方式:
BufferedReader getReader();
有了上述的知识储备,我们来实现一个案例需求:
(1)发送一个GET请求并携带用户名,后台接收后打印到控制台
(2)发送一个POST请求并携带用户名,后台接收后打印到控制台
此处大家需要注意的是GET请求和POST请求接收参数的方式不一样,具体实现的代码如下:
@WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String result = req.getQueryString(); System.out.println(result); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { BufferedReader br = req.getReader(); String result = br.readLine(); System.out.println(result); } }
-
对于上述的代码,会存在什么问题呢?
-
-
如何解决上述重复代码的问题呢?
-
当然,也可以在doGet中调用doPost,在doPost中完成参数的获取和打印,另外需要注意的是,doGet和doPost方法都必须存在,不能删除任意一个。
==GET请求和POST请求获取请求参数的方式不一样,在获取请求参数这块该如何实现呢?==
要想实现,我们就需要==思考==:
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种==统一==获取请求参数的方式,从而==统一==doGet和doPost方法内的代码?
解决方案一:
@WebServlet("/req1") public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求方式 String method = req.getMethod(); //获取请求参数 String params = ""; if("GET".equals(method)){ params = req.getQueryString(); }else if("POST".equals(method)){ BufferedReader reader = req.getReader(); params = reader.readLine(); } //将请求参数进行打印控制台 System.out.println(params); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req,resp); } }
使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用
解决方案二:
request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作?
(1)根据不同的请求方式获取请求参数,获取的内容如下:
(2)把获取到的内容进行分割,内容如下:
(3)把分割后端数据,存入到一个Map集合中:
注意:因为参数的值可能是一个,也可能有多个,所以Map的值的类型为String数组。
基于上述理论,request对象为我们提供了如下方法:
-
获取所有参数Map集合
Map<String,String[]> getParameterMap()
-
根据名称获取参数值(数组)
String[] getParameterValues(String name)
-
根据名称获取参数值(单个值)
String getParameter(String name)
接下来,我们通过案例来把上述的三个方法进行实例演示:
1.修改req.html页面,添加爱好选项,爱好可以同时选多个
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/request-demo/req2" method="get"> <input type="text" name="username"><br> <input type="password" name="password"><br> <input type="checkbox" name="hobby" value="1"> 游泳 <input type="checkbox" name="hobby" value="2"> 爬山 <br> <input type="submit"> </form> </body> </html>
2.在Servlet代码中获取页面传递GET请求的参数值
2.1获取GET方式的所有请求参数
/** * request 通用方式获取请求参数 */ @WebServlet("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //GET请求逻辑 System.out.println("get...."); //1. 获取所有参数的Map集合 Map<String, String[]> map = req.getParameterMap(); for (String key : map.keySet()) { // username:zhangsan lisi System.out.print(key+":"); //获取值 String[] values = map.get(key); for (String value : values) { System.out.print(value + " "); } System.out.println(); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
获取的结果为:
2.2获取GET请求参数中的爱好,结果是数组值
/** * request 通用方式获取请求参数 */ @WebServlet("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //GET请求逻辑 //... System.out.println("------------"); String[] hobbies = req.getParameterValues("hobby"); for (String hobby : hobbies) { System.out.println(hobby); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
获取的结果为:
2.3获取GET请求参数中的用户名和密码,结果是单个值
/** * request 通用方式获取请求参数 */ @WebServlet("/req2") public class RequestDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //GET请求逻辑 //... String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println(username); System.out.println(password); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
获取的结果为:
3.在Servlet代码中获取页面传递POST请求的参数值
3.1将req.html页面form表单的提交方式改成post
3.2将doGet方法中的内容复制到doPost方法中即可
小结
-
req.getParameter()方法使用的频率会比较高
-
以后我们再写代码的时候,就只需要按照如下格式来编写:
public class RequestDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //采用request提供的获取请求参数的通用方式来获取请求参数 //编写其他的业务代码... } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req,resp); } }
2.3 IDEA快速创建Servlet
使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义如下格式:
由于格式固定,所以我们可以使用IDEA提供的模板来制作一个Servlet的模板,这样我们后期在创建Servlet的时候就会更高效,具体如何实现:
(1)按照自己的需求,修改Servlet创建的模板内容
(2)使用servlet模板创建Servlet类
2.4 请求参数中文乱码问题
问题展示:
(1)将req.html页面的请求方式修改为get
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/request-demo/req2" method="get"> <input type="text" name="username"><br> <input type="password" name="password"><br> <input type="checkbox" name="hobby" value="1"> 游泳 <input type="checkbox" name="hobby" value="2"> 爬山 <br> <input type="submit"> </form> </body> </html>
(2)在Servlet方法中获取参数,并打印
/** * 中文乱码问题解决方案 */ @WebServlet("/req4") public class RequestDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取username String username = request.getParameter("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)启动服务器,页面上输入中文参数
(4)查看控制台打印内容
(5)把req.html页面的请求方式改成post,再次发送请求和中文参数
(6)查看控制台打印内容,依然为乱码
通过上面的案例,会发现,不管是GET还是POST请求,在发送的请求参数中如果有中文,在后台接收的时候,都会出现中文乱码的问题。具体该如何解决呢?
2.4.1 POST请求解决方案
-
分析出现中文乱码的原因:
-
POST的请求参数是通过request的getReader()来获取流中的数据
-
TOMCAT在获取流的时候采用的编码是ISO-8859-1
-
ISO-8859-1编码是不支持中文的,所以会出现乱码
-
-
解决方案:
-
页面设置的编码格式为UTF-8
-
把TOMCAT在获取流数据之前的编码设置为UTF-8
-
通过request.setCharacterEncoding("UTF-8")设置编码,UTF-8也可以写成小写
-
修改后的代码为:
/** * 中文乱码问题解决方案 */ @WebServlet("/req4") public class RequestDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 解决乱码: POST getReader() //设置字符输入流的编码,设置的字符集要和页面保持一致 request.setCharacterEncoding("UTF-8"); //2. 获取username String username = request.getParameter("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
重新发送POST请求,就会在控制台看到正常展示的中文结果。
至此POST请求中文乱码的问题就已经解决,但是这种方案不适用于GET请求,这个原因是什么呢,咱们下面再分析。
2.4.2 GET请求解决方案
刚才提到一个问题是POST请求的中文乱码解决方案为什么不适用GET请求?
-
GET请求获取请求参数的方式是
request.getQueryString()
-
POST请求获取请求参数的方式是
request.getReader()
-
request.setCharacterEncoding("utf-8")是设置request处理流的编码
-
getQueryString方法并没有通过流的方式获取数据
所以GET请求不能用设置编码的方式来解决中文乱码问题,那问题又来了,如何解决GET请求的中文乱码呢?
-
首先我们需要先分析下GET请求出现乱码的原因:
(1)浏览器通过HTTP协议发送请求和数据给后台服务器(Tomcat)
(2)浏览器在发送HTTP的过程中会对中文数据进行URL==编码==
(3)在进行URL编码的时候会采用页面<meta>
标签指定的UTF-8的方式进行编码,张三
编码后的结果为%E5%BC%A0%E4%B8%89
(4)后台服务器(Tomcat)接收到%E5%BC%A0%E4%B8%89
后会默认按照ISO-8859-1
进行URL==解码==
(5)由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。
思考: 如果把req.html
页面的<meta>
标签的charset属性改成ISO-8859-1
,后台不做操作,能解决中文乱码问题么?
答案是否定的,因为ISO-8859-1
本身是不支持中文展示的,所以改了<meta>标签的charset属性后,会导致页面上的中文内容都无法正常展示。
分析完上面的问题后,我们会发现,其中有两个我们不熟悉的内容就是==URL编码==和==URL解码==,什么是URL编码,什么又是URL解码呢?
URL编码
这块知识我们只需要了解下即可,具体编码过程分两步,分别是:
(1)将字符串按照编码方式转为二进制(张三)(3个字节 = 24)
(2)每个字节转为2个16进制数并在前边加上%
张三
按照UTF-8的方式转换成二进制的结果为:
1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
这个结果是如何计算的?
使用http://www.mytju.com/classcode/tools/encode_utf8.asp
,输入张三
就可以获取张和三分别对应的10进制,然后在使用计算器,选择程序员模式,计算出对应的二进制数据结果:
在计算的十六进制结果中,每两位前面加一个%,就可以获取到%E5%BC%A0%E4%B8%89
。
当然你从上面所提供的网站中就已经能看到编码16进制的结果了:
但是对于上面的计算过程,如果没有工具,纯手工计算的话,相对来说还是比较复杂的,我们也不需要进行手动计算,在Java中已经为我们提供了编码和解码的API工具类可以让我们更快速的进行编码和解码:
编码:
java.net.URLEncoder.encode("需要被编码的内容","字符集(UTF-8)")
解码:
java.net.URLDecoder.decode("需要被解码的内容","字符集(UTF-8)")
接下来咱们对张三
来进行编码和解码
public class URLDemo { public static void main(String[] args) throws UnsupportedEncodingException { String username = "张三"; //1. URL编码 String encode = URLEncoder.encode(username, "utf-8"); System.out.println(encode); //打印:%E5%BC%A0%E4%B8%89 //2. URL解码 //String decode = URLDecoder.decode(encode, "utf-8");//打印:张三 String decode = URLDecoder.decode(encode, "ISO-8859-1");//打印:`å¼ ä¸ ` System.out.println(decode); } }
到这,我们就可以分析出GET请求中文参数出现乱码的原因了,
-
浏览器把中文参数按照
UTF-8
进行URL编码 -
Tomcat对获取到的内容进行了
ISO-8859-1
的URL解码 -
在控制台就会出现类上
å¼ ä¸
的乱码,最后一位是个空格
-
清楚了出现乱码的原因,接下来我们就需要想办法进行解决
从上图可以看住,
-
在进行编码和解码的时候,不管使用的是哪个字符集,他们对应的
%E5%BC%A0%E4%B8%89
是一致的 -
那他们对应的二进制值也是一样的,为:
-
1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
-
-
为所以我们可以考虑把
å¼ ä¸
转换成字节,在把字节转换成张三
,在转换的过程中是它们的编码一致,就可以解决中文乱码问题。
具体的实现步骤为:
1.按照ISO-8859-1编码获取乱码
å¼ ä¸
对应的字节数组2.按照UTF-8编码获取字节数组对应的字符串
实现代码如下:
public class URLDemo { public static void main(String[] args) throws UnsupportedEncodingException { String username = "张三"; //1. URL编码 String encode = URLEncoder.encode(username, "utf-8"); System.out.println(encode); //2. URL解码 String decode = URLDecoder.decode(encode, "ISO-8859-1"); System.out.println(decode); //此处打印的是对应的乱码数据 //3. 转换为字节数据,编码 byte[] bytes = decode.getBytes("ISO-8859-1"); for (byte b : bytes) { System.out.print(b + " "); } //此处打印的是:-27 -68 -96 -28 -72 -119 //4. 将字节数组转为字符串,解码 String s = new String(bytes, "utf-8"); System.out.println(s); //此处打印的是张三 } }
说明:在第18行中打印的数据是-27 -68 -96 -28 -72 -119
和张三
转换成的二进制数据1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
为什么不一样呢?
其实打印出来的是十进制数据,我们只需要使用计算机换算下就能得到他们的对应关系,如下图:
至此对于GET请求中文乱码的解决方案,我们就已经分析完了,最后在代码中去实现下:
/** * 中文乱码问题解决方案 */ @WebServlet("/req4") public class RequestDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 解决乱码:POST,getReader() //request.setCharacterEncoding("UTF-8");//设置字符输入流的编码 //2. 获取username String username = request.getParameter("username"); System.out.println("解决乱码前:"+username); //3. GET,获取参数的方式:getQueryString // 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1 /* //3.1 先对乱码数据进行编码:转为字节数组 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); //3.2 字节数组解码 username = new String(bytes, StandardCharsets.UTF_8);*/ username = new String(username.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8); System.out.println("解决乱码后:"+username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
注意
-
把
request.setCharacterEncoding("UTF-8")
代码注释掉后,会发现GET请求参数乱码解决方案同时也可也把POST请求参数乱码的问题也解决了 -
只不过对于POST请求参数一般都会比较多,采用这种方式解决乱码起来比较麻烦,所以对于POST请求还是建议使用设置编码的方式进行。
另外需要说明一点的是==Tomcat8.0之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8==
小结
-
中文乱码解决方案
-
POST请求和GET请求的参数中如果有中文,后台接收数据就会出现中文乱码问题
GET请求在Tomcat8.0以后的版本就不会出现了
-
POST请求解决方案是:设置输入流的编码
request.setCharacterEncoding("UTF-8"); 注意:设置的字符集要和页面保持一致
-
通用方式(GET/POST):需要先解码,再编码
new String(username.getBytes("ISO-8859-1"),"UTF-8");
-
URL编码实现方式:
-
编码:
URLEncoder.encode(str,"UTF-8");
-
解码:
URLDecoder.decode(s,"ISO-8859-1");
2.5 Request请求转发
-
==请求转发(forward):一种在服务器内部的资源跳转方式。==
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A处理完请求后将请求发给资源B
(3)资源B处理完后将结果响应给浏览器
(4)请求从资源A到资源B的过程就叫==请求转发==
2.请求转发的实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp);
具体如何来使用,我们先来看下需求:
针对上述需求,具体的实现步骤为:
1.创建一个RequestDemo5类,接收/req5的请求,在doGet方法中打印
demo5
2.创建一个RequestDemo6类,接收/req6的请求,在doGet方法中打印
demo6
3.在RequestDemo5的方法中使用
req.getRequestDispatcher("/req6").forward(req,resp)进行请求转发
4.启动测试
(1)创建RequestDemo5类
/** * 请求转发 */ @WebServlet("/req5") public class RequestDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo5..."); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)创建RequestDemo6类
/** * 请求转发 */ @WebServlet("/req6") public class RequestDemo6 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo6..."); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)在RequestDemo5的doGet方法中进行请求转发
/** * 请求转发 */ @WebServlet("/req5") public class RequestDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo5..."); //请求转发 request.getRequestDispatcher("/req6").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(4)启动测试
访问http://localhost:8080/request-demo/req5
,就可以在控制台看到如下内容:
说明请求已经转发到了/req6
-
请求转发资源间共享数据:使用Request对象
此处主要解决的问题是把请求从/req5
转发到/req6
的时候,如何传递数据给/req6
。
需要使用request对象提供的三个方法:
-
存储数据到request域[范围,数据是存储在request对象]中
void setAttribute(String name,Object o);
-
根据key获取值
Object getAttribute(String name);
-
根据key删除该键值对
void removeAttribute(String name);
接着上个需求来:
1.在RequestDemo5的doGet方法中转发请求之前,将数据存入request域对象中
2.在RequestDemo6的doGet方法从request域对象中获取数据,并将数据打印到控制台
3.启动访问测试
(1)修改RequestDemo5中的方法
@WebServlet("/req5") public class RequestDemo5 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo5..."); //存储数据 request.setAttribute("msg","hello"); //请求转发 request.getRequestDispatcher("/req6").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)修改RequestDemo6中的方法
/** * 请求转发 */ @WebServlet("/req6") public class RequestDemo6 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo6..."); //获取数据 Object msg = request.getAttribute("msg"); System.out.println(msg); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)启动测试
访问http://localhost:8080/request-demo/req5
,就可以在控制台看到如下内容:
此时就可以实现在转发多个资源之间共享数据。
4.请求转发的特点
-
浏览器地址栏路径不发生变化
虽然后台从
/req5
转发到/req6
,但是浏览器的地址一直是/req5
,未发生变化 -
-
只能转发到当前服务器的内部资源
不能从一个服务器通过转发访问另一台服务器
-
一次请求,可以在转发资源间使用request共享数据
虽然后台从
/req5
转发到/req6
,但是这个==只有一次请求==
3、Response对象
前面讲解完Request对象,接下来我们回到刚开始的那张图:
-
Request:使用request对象来==获取==请求数据
-
Response:使用response对象来==设置==响应数据
Reponse的继承体系和Request的继承体系也非常相似:
介绍完Response的相关体系结构后,接下来对于Response我们需要学习如下内容:
-
Response设置响应数据的功能介绍
-
Response完成重定向
-
Response响应字符数据
-
Response响应字节数据
3.1 Response设置响应数据功能介绍
HTTP响应数据总共分为三部分内容,分别是==响应行、响应头、响应体==,对于这三部分内容的数据,respone对象都提供了哪些方法来进行设置?
-
响应行
对于响应头,比较常用的就是设置响应状态码:
void setStatus(int sc);
2.响应头
设置响应头键值对:
void setHeader(String name,String value);
3.响应体
对于响应体,是通过字符、字节输出流的方式往浏览器写,
获取字符输出流:
PrintWriter getWriter();
获取字节输出流
ServletOutputStream getOutputStream();
介绍完这些方法后,后面我们会通过案例把这些方法都用一用,首先先来完成下重定向的功能开发。
3.2 Respones请求重定向
-
==Response重定向(redirect):一种资源跳转方式。==
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径
(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B
(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫==重定向==
-
重定向的实现方式:
resp.setStatus(302); resp.setHeader("location","资源B的访问路径");
具体如何来使用,我们先来看下需求:
针对上述需求,具体的实现步骤为:
1.创建一个ResponseDemo1类,接收/resp1的请求,在doGet方法中打印
resp1....
2.创建一个ResponseDemo2类,接收/resp2的请求,在doGet方法中打印
resp2....
3.在ResponseDemo1的方法中使用
response.setStatus(302);
response.setHeader("Location","/request-demo/resp2") 来给前端响应结果数据
4.启动测试
(1)创建ResponseDemo1类
@WebServlet("/resp1") public class ResponseDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp1...."); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)创建ResponseDemo2类
@WebServlet("/resp2") public class ResponseDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp2...."); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)在ResponseDemo1的doGet方法中给前端响应数据
@WebServlet("/resp1") public class ResponseDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp1...."); //重定向 //1.设置响应状态码 302 response.setStatus(302); //2. 设置响应头 Location response.setHeader("Location","/request-demo/resp2"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(4)启动测试
访问http://localhost:8080/request-demo/resp1
,就可以在控制台看到如下内容:
虽然功能已经实现,但是从设置重定向的两行代码来看,会发现除了重定向的地址不一样,其他的内容都是一模一样,所以request对象给我们提供了简化的编写方式为:
resposne.sendRedirect("/request-demo/resp2")
所以第3步中的代码就可以简化为:
@WebServlet("/resp1") public class ResponseDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp1...."); //重定向 resposne.sendRedirect("/request-demo/resp2"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
重定向的特点
-
浏览器地址栏路径发送变化
当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化
-
-
可以重定向到任何位置的资源(服务内容、外部均可)
因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。
-
两次请求,不能在多个资源使用request共享数据
因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据
介绍完==请求重定向==和==请求转发==以后,接下来需要把这两个放在一块对比下:
以后到底用哪个,还是需要根据具体的业务来决定。
3.3 路径问题
-
问题1:转发的时候路径上没有加
/request-demo
而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?
其实判断的依据很简单,只需要记住下面的规则即可:
-
浏览器使用:需要加虚拟目录(项目访问路径)
-
服务端使用:不需要加虚拟目录
对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录
对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。
掌握了这个规则,接下来就通过一些练习来强化下知识的学习:
-
<a href='路劲'>
-
<form action='路径'>
-
req.getRequestDispatcher("路径")
-
resp.sendRedirect("路径")
答案:
1.超链接,从浏览器发送,需要加 2.表单,从浏览器发送,需要加 3.转发,是从服务器内部跳转,不需要加 4.重定向,是由浏览器进行跳转,需要加。
-
问题2:在重定向的代码中,
/request-demo
是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?
答案也比较简单,我们可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的request对象中的getContextPath()方法,修改后的代码如下:
@WebServlet("/resp1") public class ResponseDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("resp1...."); //简化方式完成重定向 //动态获取虚拟目录 String contextPath = request.getContextPath(); response.sendRedirect(contextPath+"/resp2"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
重新启动访问测试,功能依然能够实现,此时就可以动态获取项目访问的虚拟路径,从而降低代码的耦合度。
3.4 Response响应字符数据
要想将字符数据写回到浏览器,我们需要两个步骤:
-
通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
-
通过字符输出流写数据: writer.write("aaa");
接下来,我们实现通过些案例把响应字符数据给实际应用下:
-
返回一个简单的字符串
aaa
/** * 响应字符数据:设置字符数据的响应体 */ @WebServlet("/resp3") public class ResponseDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); //1. 获取字符输出流 PrintWriter writer = response.getWriter(); writer.write("aaa"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
PrintWriter writer = response.getWriter(); //content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签 response.setHeader("content-type","text/html"); writer.write("<h1>aaa</h1>");
==注意:==一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。
-
返回一个中文的字符串
你好
,需要注意设置响应数据的编码为utf-8
//设置响应的数据格式及数据的编码 response.setContentType("text/html;charset=utf-8"); writer.write("你好");
3.3 Response响应字节数据
要想将字节数据写回到浏览器,我们需要两个步骤:
-
通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();
-
通过字节输出流写数据: outputStream.write(字节数据);、
补充知识:获取webapp的路径的方法为:
request.getServletContext().getRealPath("/a.jpg")
注意:粘贴文件记得打开文件路径去粘贴
接下来,我们实现通过些案例把响应字符数据给实际应用下:
-
返回一个图片文件到浏览器
/** * 响应字节数据:设置字节数据的响应体 */ @WebServlet("/resp4") public class ResponseDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 读取文件 FileInputStream fis = new FileInputStream("d://a.jpg"); //2. 获取response字节输出流 ServletOutputStream os = response.getOutputStream(); //3. 完成流的copy byte[] buff = new byte[1024]; int len = 0; while ((len = fis.read(buff))!= -1){ os.write(buff,0,len); } fis.close(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
上述代码中,对于流的copy的代码还是比较复杂的,所以我们可以使用别人提供好的方法来简化代码的开发,具体的步骤是:
(1)pom.xml添加依赖
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
(2)调用工具类方法
//fis:输入流 //os:输出流 IOUtils.copy(fis,os);
优化后的代码:
/** * 响应字节数据:设置字节数据的响应体 */ @WebServlet("/resp4") public class ResponseDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 读取文件 FileInputStream fis = new FileInputStream("d://a.jpg"); //2. 获取response字节输出流 ServletOutputStream os = response.getOutputStream(); //3. 完成流的copy IOUtils.copy(fis,os); fis.close(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
4、用户注册登录案例
接下来我们通过两个比较常见的案例,一个是==注册==,一个是==登录==来对今天学习的内容进行一个实战演练,首先来实现用户登录。
4.1 用户登录
4.1.1 需求分析
-
用户在登录页面输入用户名和密码,提交请求给LoginServlet
-
在LoginServlet中接收请求和数据[用户名和密码]
-
在LoginServlt中通过Mybatis实现调用UserMapper来根据用户名和密码查询数据库表
-
将查询的结果封装到User对象中进行返回
-
在LoginServlet中判断返回的User对象是否为null
-
如果为nul,说明根据用户名和密码没有查询到用户,则登录失败,返回"登录失败"数据给前端
-
如果不为null,则说明用户存在并且密码正确,则登录成功,返回"登录成功"数据给前端
4.1.2 环境准备
-
复制资料中的静态页面到项目的webapp目录下
参考资料\1. 登陆注册案例\1. 静态页面
,拷贝完效果如下:
-
创建db1数据库,创建tb_user表,创建User实体类
2.1 将资料\1. 登陆注册案例\2. MyBatis环境\tb_user.sql
中的sql语句执行下:
2.2 将资料\1. 登陆注册案例\2. MyBatis环境\User.java
拷贝到com.hxzy.pojo
-
在项目的pom.xml导入Mybatis和Mysql驱动坐标
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency>
-
创建mybatis-config.xml核心配置文件,UserMapper.xml映射文件,UserMapper接口
4.1 将资料\1. 登陆注册案例\2. MyBatis环境\mybatis-config.xml
拷贝到resources目录下
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--起别名--> <typeAliases> <package name="com.hxzy.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- useSSL:关闭SSL安全连接 性能更高 useServerPrepStmts:开启预编译功能 & 等同于 & ,xml配置文件中不能直接写 &符号 --> <property name="url" value="jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!--扫描mapper--> <package name="com.hxzy.mapper"/> </mappers> </configuration>
4.2 在com.hxzy.mapper包下创建UserMapper接口
public interface UserMapper { }
4.3 将资料\1. 登陆注册案例\2. MyBatis环境\UserMapper.xml
拷贝到resources目录下
==注意:在resources下创建UserMapper.xml的目录时,要使用/分割==
至此我们所需要的环境就都已经准备好了,具体该如何实现?
4.1.3 代码实现
-
在UserMapper接口中提供一个根据用户名和密码查询用户对象的方法
/** * 根据用户名和密码查询用户对象 * @param username * @param password * @return */ @Select("select * from tb_user where username = #{username} and password = #{password}") User select(@Param("username") String username,@Param("password") String password);
说明
@Param注解的作用:用于传递参数,是方法的参数可以与SQL中的字段名相对应。
-
修改loign.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv"> <form action="/request-demo/loginServlet" method="post" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?点击注册</a> </div> </form> </div> </body> </html>
-
编写LoginServlet
@WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //2. 调用MyBatis完成查询 //2.1 获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2.2 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //2.3 获取Mapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //2.4 调用方法 User user = userMapper.select(username, password); //2.5 释放资源 sqlSession.close(); //获取字符输出流,并设置content type response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); //3. 判断user释放为null if(user != null){ // 登陆成功 writer.write("登陆成功"); }else { // 登陆失败 writer.write("登陆失败"); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
启动服务器测试
4.1 如果用户名和密码输入错误,则
4.2 如果用户名和密码输入正确,则
至此用户的登录功能就已经完成了~
4.2 用户注册
4.2.1 需求分析
4.2.2 代码编写
-
编写UserMapper提供根据用户名查询用户数据方法和添加用户方法
/** * 根据用户名查询用户对象 * @param username * @return */ @Select("select * from tb_user where username = #{username}") User selectByUsername(String username); /** * 添加用户 * @param user */ @Insert("insert into tb_user values(null,#{username},#{password})") void add(User user);
-
修改register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="css/register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="login.html">登录</a> </div> <form id="reg-form" action="/request-demo/registerServlet" method="post"> <table> <tr> <td>用户名</td> <td class="inputs"> <input name="username" type="text" id="username"> <br> <span id="username_err" class="err_msg" style="display: none">用户名不太受欢迎</span> </td> </tr> <tr> <td>密码</td> <td class="inputs"> <input name="password" type="password" id="password"> <br> <span id="password_err" class="err_msg" style="display: none">密码格式有误</span> </td> </tr> </table> <div class="buttons"> <input value="注 册" type="submit" id="reg_btn"> </div> <br class="clear"> </form> </div> </body> </html>
-
创建RegisterServlet类
@WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收用户数据 String username = request.getParameter("username"); String password = request.getParameter("password"); //封装用户对象 User user = new User(); user.setUsername(username); user.setPassword(password); //2. 调用mapper 根据用户名查询用户对象 //2.1 获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2.2 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //2.3 获取Mapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //2.4 调用方法 User u = userMapper.selectByUsername(username); //3. 判断用户对象释放为null if( u == null){ // 用户名不存在,添加用户 userMapper.add(user); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); }else { // 用户名存在,给出提示信息 response.setContentType("text/html;charset=utf-8"); response.getWriter().write("用户名已存在"); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
启动服务器进行测试
4.1 如果测试成功,则在数据库中就能查看到新注册的数据
4.2 如果用户已经存在,则在页面上展示 用户名已存在
的提示信息
4.3 SqlSessionFactory工具类抽取
上面两个功能已经实现,但是在写Servlet的时候,因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
有了这些重复代码就会造成一些问题:
-
重复代码不利于后期的维护
-
SqlSessionFactory工厂类进行重复创建
-
就相当于每次买手机都需要重新创建一个手机生产工厂来给你制造一个手机一样,资源消耗非常大但性能却非常低。所以这么做是不允许的。
-
那如何来优化呢?
-
代码重复可以抽取工具类
-
对指定代码只需要执行一次可以使用静态代码块
有了这两个方向后,代码具体该如何编写?
public class SqlSessionFactoryUtils { private static SqlSessionFactory sqlSessionFactory; static { //静态代码块会随着类的加载而自动执行,且只执行一次 try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; } }
工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用
SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();
这样就可以很好的解决上面所说的代码重复和重复创建工厂导致性能低的问题了。
六、会话技术
今日目标
理解什么是会话跟踪技术
掌握Cookie的使用
掌握Session的使用
完善用户登录注册案例的功能
1,会话跟踪技术的概述
对于会话跟踪
这四个词,我们需要拆开来进行解释,首先要理解什么是会话
,然后再去理解什么是会话跟踪
:
-
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含==多次==请求和响应。
-
从浏览器发出请求到服务端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了
-
会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
-
浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为==会话==。
用实际场景来理解下会话,比如在我们访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品的详情,加入购物车等都是在这一次会话中完成。
思考:下图中总共建立了几个会话?
-
-
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间==共享数据==。
-
服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器
-
服务器需要用来识别请求是否来自同一个浏览器
-
服务器用来识别浏览器的过程,这个过程就是==会话跟踪==
-
服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据
那么我们又有一个问题需要思考,一个会话中的多次请求为什么要共享数据呢?有了这个数据共享功能后能实现哪些功能呢?
-
购物车:
加入购物车
和去购物车结算
是两次请求,但是后面这次请求要想展示前一次请求所添加的商品,就需要用到数据共享。 -
页面展示用户登录信息:很多网站,登录后访问多个功能发送多次请求后,浏览器上都会有当前登录用户的信息[用户名],比如百度、京东、码云等。
-
-
网站登录页面的
记住我
功能:当用户登录成功后,勾选记住我
按钮后下次再登录的时候,网站就会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次请求,他们之间也涉及到共享数据 -
-
登录页面的验证码功能:生成验证码和输入验证码点击注册这也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据。
-
-
通过这几个例子的讲解,相信大家对会话追踪
技术已经有了一定的理解,该技术在实际开发中也非常重要。那么接下来我们就需要去学习下会话跟踪
技术,在学习这些技术之前,我们需要思考:为什么现在浏览器和服务器不支持数据共享呢?
-
浏览器和服务器之间使用的是HTTP请求来进行数据传输
-
HTTP协议是==无状态==的,每次浏览器向服务器请求时,服务器都会将该请求视为==新的==请求
-
HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
-
请求与请求之间独立后,就无法实现多次请求之间的数据共享
分析完具体的原因后,那么该如何实现会话跟踪技术呢? 具体的实现方式有:
(1)客户端会话跟踪技术:==Cookie==
(2)服务端会话跟踪技术:==Session==
这两个技术都可以实现会话跟踪,它们之间最大的区别:==Cookie是存储在浏览器端而Session是存储在服务器端==
具体的学习思路为:
-
CooKie的基本使用、原理、使用细节
-
Session的基本使用、原理、使用细节
-
Cookie和Session的综合案例
小结
在这节中,我们主要介绍了下什么是会话和会话跟踪技术,需要注意的是:
-
HTTP协议是无状态的,靠HTTP协议是无法实现会话跟踪
-
想要实现会话跟踪,就需要用到Cookie和Session
这个Cookie和Session具体该如何使用,接下来就先从Cookie来学起。
2,Cookie
学习Cookie,我们主要解决下面几个问题:
-
什么是Cookie?
-
Cookie如何来使用?
-
Cookie是如何实现的?
-
Cookie的使用注意事项有哪些?
2.1 Cookie的基本使用
1.概念
==Cookie==:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。
2.Cookie的工作流程
-
服务端提供了两个Servlet,分别是ServletA和ServletB
-
浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
-
服务端ServletA在处理的过程中可以创建一个Cookie对象并将
name=zs
的数据存入Cookie -
服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
-
浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就==建立了一次会话==
-
==在同一次会话==中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
-
ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享
3.Cookie的基本使用
对于Cookie的使用,我们更关注的应该是后台代码如何操作Cookie,对于Cookie的操作主要分两大类,本别是==发送Cookie==和==获取Cookie==,对于上面这两块内容,分别该如何实现呢?
3.1 发送Cookie
-
创建Cookie对象,并设置数据
Cookie cookie = new Cookie("key","value");
-
发送Cookie到客户端:使用==response==对象
response.addCookie(cookie);
介绍完发送Cookie对应的步骤后,接下面通过一个案例来完成Cookie的发送,具体实现步骤为:
需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器
1.创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖
2.编写Servlet类,名称为AServlet
3.在AServlet中创建Cookie对象,存入数据,发送给前端
4.启动测试,在浏览器查看Cookie对象中的值
(1)创建Maven项目cookie-demo,并在pom.xml添加依赖
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--jsp--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <!--jstl--> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> </plugin> </plugins> </build>
(2)编写Servlet类,名称为AServlet
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)在Servlet中创建Cookie对象,存入数据,发送给前端
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie //1. 创建Cookie对象 Cookie cookie = new Cookie("username","zs"); //2. 发送Cookie,response response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(4)启动测试,在浏览器查看Cookie对象中的值
访问http://localhost:8080/cookie-demo/aServlet
chrome浏览器查看Cookie的值(这里通过Edge浏览器方便查看一点),有两种方式,分布式:
方式一:
方式二:选中打开开发者工具或者 使用快捷键F12 或者 Ctrl+Shift+I
3.2 获取Cookie
-
获取客户端携带的所有Cookie,使用==request==对象
Cookie[] cookies = request.getCookies();
-
遍历数组,获取每一个Cookie对象:for
-
使用Cookie对象方法获取数据
cookie.getName(); cookie.getValue();
介绍完获取Cookie对应的步骤后,接下面再通过一个案例来完成Cookie的获取,具体实现步骤为:
需求:在Servlet中获取前一个案例存入在Cookie对象中的数据
1.编写一个新Servlet类,名称为BServlet
2.在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
3.启动测试,在控制台打印出获取的值
(1)编写一个新Servlet类,名称为BServlet
@WebServlet("/bServlet") public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
@WebServlet("/bServlet") public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取Cookie //1. 获取Cookie数组 Cookie[] cookies = request.getCookies(); //2. 遍历数组 for (Cookie cookie : cookies) { //3. 获取数据 String name = cookie.getName(); if("username".equals(name)){ String value = cookie.getValue(); System.out.println(name+":"+value); break; } } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)启动测试,在控制台打印出获取的值
访问http://localhost:8080/cookie-demo/bServlet
在IDEA控制台就能看到输出的结果:
==思考:==测试的时候
-
在访问AServlet和BServlet的中间把关闭浏览器,重启浏览器后访问BServlet能否获取到Cookie中的数据?
这个问题,我们会在Cookie的使用细节中讲,大家可以动手先试下。
小结
在这节中,我们主要讲解了Cookie的基本使用,包含两部分内容
-
发送Cookie:
-
创建Cookie对象,并设置值:Cookie cookie = new Cookie("key","value");
-
发送Cookie到客户端使用的是Reponse对象:response.addCookie(cookie);
-
-
获取Cookie:
-
使用Request对象获取Cookie数组:Cookie[] cookies = request.getCookies();
-
遍历数组
-
获取数组中每个Cookie对象的值:cookie.getName()和cookie.getValue()
-
介绍完Cookie的基本使用之后,那么Cookie的底层到底是如何实现一次会话两次请求之间的数据共享呢?
2.2 Cookie的原理分析
对于Cookie的实现原理是基于HTTP协议的,其中设计到HTTP协议中的两个请求头信息:
-
响应头:set-cookie
-
请求头: cookie
-
-
前面的案例中已经能够实现,AServlet给前端发送Cookie,BServlet从request中获取Cookie的功能
-
对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据
-
当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据==
Set-Cookie:username=zs
== -
浏览器获取到响应结果后,从响应头中就可以获取到
Set-Cookie
对应值username=zs
,并将数据存储在浏览器的内存中 -
浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加==
Cookie: username=zs
==发送给服务端BServlet -
Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组
-
BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据
接下来,使用刚才的案例,把上述结论验证下:
(1)访问AServlet对应的地址http://localhost:8080/cookie-demo/aServlet
使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看==响应头==中的数据
(2)访问BServlet对应的地址`http://localhost:8080/cookie-demo/bServlet
使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看==请求头==中的数据
2.3 Cookie的使用细节
在这节我们主要讲解两个知识,第一个是Cookie的存活时间,第二个是Cookie如何存储中文,首先来学习下Cookie的存活时间。
2.3.1 Cookie的存活时间
前面让大家思考过一个问题:
(1)浏览器发送请求给AServlet,AServlet会响应一个存有usernanme=zs
的Cookie对象给浏览器
(2)浏览器接收到响应数据将cookie存入到浏览器内存中
(3)当浏览器再次发送请求给BServlet,BServlet就可以使用Request对象获取到Cookie数据
(4)在发送请求到BServlet之前,如果把浏览器关闭再打开进行访问,BServlet能否获取到Cookie数据?
==注意:浏览器关闭再打开不是指打开一个新的选显卡,而且必须是先关闭再打开,顺序不能变。==
针对上面这个问题,通过演示,会发现,BServlet中无法再获取到Cookie数据,这是为什么呢?
-
默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁
这个结论就印证了上面的演示效果,但是如果使用这种默认情况下的Cookie,有些需求就无法实现,比如:
上面这个网站的登录页面上有一个记住我
的功能,这个功能大家都比较熟悉
-
第一次输入用户名和密码并勾选
记住我
然后进行登录 -
下次再登陆的时候,用户名和密码就会被自动填充,不需要再重新输入登录
-
比如
记住我
这个功能需要记住用户名和密码一个星期,那么使用默认情况下的Cookie就会出现问题 -
因为默认情况,浏览器一关,Cookie就会从浏览器内存中删除,对于
记住我
功能就无法实现
所以我们现在就遇到一个难题是如何将Cookie持久化存储?
Cookie其实已经为我们提供好了对应的API来完成这件事,这个API就是==setMaxAge==,
-
设置Cookie存活时间
setMaxAge(int seconds)
参数值为:
1.正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
2.负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁
3.零:删除对应Cookie
4.谷歌浏览器会默认打开 “关闭所有窗口时清除 Cookie 及网站数据”菜单,如果需要关闭浏览器不被清理掉,需要关闭菜单
接下来,咱们就在AServlet中去设置Cookie的存活时间。
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie //1. 创建Cookie对象 Cookie cookie = new Cookie("username","zs"); //设置存活时间 ,1周 7天 cookie.setMaxAge(60*60*24*7); //易阅读,需程序计算 //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算 //2. 发送Cookie,response response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
修改完代码后,启动测试,访问http://localhost:8080/cookie-demo/aServlet
-
访问一个AServlet后,把浏览器关闭重启后,再去访问
http://localhost:8080/cookie-demo/bServet
,能在控制台打印出username:zs
,说明Cookie没有随着浏览器关闭而被销毁 -
通过浏览器查看Cookie的内容,会发现Cookie的相关信息
-
2.3.2 Cookie存储中文
首先,先来演示一个效果,将之前username=zs
的值改成username=张三
,把汉字张三
存入到Cookie中,看是什么效果:
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie String value = "张三"; Cookie cookie = new Cookie("username",value); //设置存活时间 ,1周 7天 cookie.setMaxAge(60*60*24*7); //2. 发送Cookie,response response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动访问测试,访问http://localhost:8080/cookie-demo/aServlet
会发现浏览器会提示错误信息
通过上面的案例演示,我们得到一个结论:
-
Cookie不能直接存储中文
Cookie不能存储中文,但是如果有这方面的需求,这个时候该如何解决呢?
这个时候,我们可以使用之前学过的一个知识点叫URL编码
,所以如果需要存储中文,就需要进行转码,具体的实现思路为:
1.在AServlet中对中文进行URL编码,采用URLEncoder.encode(),将编码后的值存入Cookie中
2.在BServlet中获取Cookie中的值,获取的值为URL编码后的值
3.将获取的值在进行URL解码,采用URLDecoder.decode(),就可以获取到对应的中文值
(1)在AServlet中对中文进行URL编码
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie String value = "张三"; //对中文进行URL编码 value = URLEncoder.encode(value, "UTF-8"); System.out.println("存储数据:"+value); //将编码后的值存入Cookie中 Cookie cookie = new Cookie("username",value); //设置存活时间 ,1周 7天 cookie.setMaxAge(60*60*24*7); //2. 发送Cookie,response response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)在BServlet中获取值,并对值进行解码
@WebServlet("/bServlet") public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取Cookie //1. 获取Cookie数组 Cookie[] cookies = request.getCookies(); //2. 遍历数组 for (Cookie cookie : cookies) { //3. 获取数据 String name = cookie.getName(); if("username".equals(name)){ String value = cookie.getValue();//获取的是URL编码后的值 %E5%BC%A0%E4%B8%89 //URL解码 value = URLDecoder.decode(value,"UTF-8"); System.out.println(name+":"+value);//value解码后为 张三 break; } } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
至此,我们就可以将中文存入Cookie中进行使用。
小结
Cookie的使用细节中,我们讲了Cookie的存活时间
和存储中文
:
-
存活时间,需要掌握setMaxAge()API的使用
-
存储中文,需要掌握URL编码和解码的使用
3,Session
Cookie已经能完成一次会话多次请求之间的数据共享,之前我们还提到过Session也可以实现,那么:
-
什么是Session?
-
Session如何来使用?
-
Session是如何实现的?
-
Session的使用注意事项有哪些?
3.1 Session的基本使用
1.概念
==Session==:服务端会话跟踪技术:将数据保存到服务端。
-
Session是存储在服务端而Cookie是存储在客户端
-
存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
-
存储在服务端的数据相比于客户端来说就更安全
2.Session的工作流程
-
在服务端的AServlet获取一个Session对象,把数据存入其中
-
在服务端的BServlet获取到相同的Session对象,从中取出数据
-
就可以实现一次会话中多次请求之间的数据共享了
-
现在最大的问题是如何保证AServlet和BServlet使用的是同一个Session对象(在原理分析会讲解)?
3.Session的基本使用
在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能。
具体的使用步骤为:
-
获取Session对象,使用的是request对象
HttpSession session = request.getSession();
-
Session对象提供的功能:
-
存储数据到 session 域中
void setAttribute(String name, Object o)
-
根据 key,获取值
Object getAttribute(String name)
-
根据 key,删除该键值对
void removeAttribute(String name)
-
介绍完Session相关的API后,接下来通过一个案例来完成对Session的使用,具体实现步骤为:
需求:在一个Servlet中往Session中存入数据,在另一个Servlet中获取Session中存入的数据
1.创建名为SessionDemo1的Servlet类
2.创建名为SessionDemo2的Servlet类
3.在SessionDemo1的方法中:获取Session对象、存储数据
4.在SessionDemo2的方法中:获取Session对象、获取数据
5.启动测试
(1)创建名为SessionDemo1的Servlet类
@WebServlet("/demo1") public class SessionDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)创建名为SessionDemo2的Servlet类
@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)SessionDemo1:获取Session对象、存储数据
@WebServlet("/demo1") public class SessionDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //存储到Session中 //1. 获取Session对象 HttpSession session = request.getSession(); //2. 存储数据 session.setAttribute("username","zs"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(4)SessionDemo2:获取Session对象、获取数据
@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取数据,从session中 //1. 获取Session对象 HttpSession session = request.getSession(); //2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(5)启动测试,
-
先访问
http://localhost:8080/cookie-demo/demo1
,将数据存入Session -
在访问
http://localhost:8080/cookie-demo/demo2
,从Session中获取数据 -
查看控制台
-
通过案例的效果,能看到Session是能够在一次会话中两次请求之间共享数据。
小结
至此Session的基本使用就已经完成了,重点要掌握的是:
-
Session的获取
HttpSession session = request.getSession();
-
Session常用方法的使用
void setAttribute(String name, Object o) Object getAttribute(String name)
注意:Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。
介绍完Session的基本使用之后,那么Session的底层到底是如何实现一次会话两次请求之间的数据共享呢?
3.2 Session的原理分析
-
Session是基于Cookie实现的
这句话其实不太能详细的说明Session的底层实现,接下来,咱们一步步来分析下Session的具体实现原理:
(1)前提条件
Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个。
那么它们是一个对象么?要验证这个结论也很简单,只需要在上面案例中的两个Servlet中分别打印下Session对象
SessionDemo1
@WebServlet("/demo1") public class SessionDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //存储到Session中 //1. 获取Session对象 HttpSession session = request.getSession(); System.out.println(session); //2. 存储数据 session.setAttribute("username","zs"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
SessionDemo2
@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取数据,从session中 //1. 获取Session对象 HttpSession session = request.getSession(); System.out.println(session); //2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
启动测试,分别访问
http://localhost:8080/cookie-demo/demo1
http://localhost:8080/cookie-demo/demo2
通过打印可以得到如下结论:
-
两个Servlet类中获取的Session对象是同一个
-
把demo1和demo2请求刷新多次,控制台最终打印的结果都是同一个
那么问题又来了,如果新开一个浏览器,访问demo1或者demo2,打印在控制台的Session还是同一个对象么?
==注意:在一台电脑上演示的时候,如果是相同的浏览器必须要把浏览器全部关掉重新打开,才算新开的一个浏览器。==
当然也可以使用不同的浏览器进行测试,就不需要把之前的浏览器全部关闭。
测试的结果:如果是不同浏览器或者重新打开浏览器后,打印的Session就不一样了。
所以Session实现的也是一次会话中的多次请求之间的数据共享。
那么最主要的问题就来了,Session是如何保证在一次会话中获取的Session对象是同一个呢?
(1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
(2)demo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器
(3)Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10
当做一个cookie,添加Set-Cookie:JESSIONID=10
到响应头中,并响应给浏览器
(4)浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中
(5)浏览器在同一会话中访问demo2的时候,会把cookie中的数据按照cookie: JESSIONID=10
的格式添加到请求头中并发送给服务器Tomcat
(6)demo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10
的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象
(7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象
至此,Session是基于Cookie来实现的
这就话,我们就解释完了,接下来通过实例来演示下:
(1)使用chrome浏览器访问http://localhost:8080/cookie-demo/demo1
,打开开发者模式(F12或Ctrl+Shift+I),查看==响应头(Response Headers)==数据:
(2)使用chrome浏览器再次访问http://localhost:8080/cookie-demo/demo2
,查看==请求头(Request Headers)==数据:
小结
介绍完Session的原理,我们只需要记住
-
Session是基于Cookie来实现的
3.3 Session的使用细节
这节我们会主要讲解两个知识,第一个是Session的钝化和活化,第二个是Session的销毁,首先来学习什么是Session的钝化和活化?
3.3.1 Session钝化与活化
首先需要大家思考的问题是:
-
服务器重启后,Session中的数据是否还在?
要想回答这个问题,我们可以先看下下面这幅图,
(1)服务器端AServlet和BServlet共用的session对象应该是存储在服务器的内存中
(2)服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了
所以session数据应该也已经不存在了。但是如果session不存在会引发什么问题呢?
举个例子说明下,
(1)用户把需要购买的商品添加到购物车,因为要实现同一个会话多次请求数据共享,所以假设把数据存入Session对象中
(2)用户正要付钱的时候接到一个电话,付钱的动作就搁浅了
(3)正在用户打电话的时候,购物网站因为某些原因需要重启
(4)重启后session数据被销毁,购物车中的商品信息也就会随之而消失
(5)用户想再次发起支付,就会出为问题
所以说对于session的数据,我们应该做到就算服务器重启了,也应该能把数据保存下来才对。
分析了这么多,那么Tomcat服务器在重启的时候,session数据到底会不会保存以及是如何保存的,我们可以通过实际案例来演示下:
==注意:这里所说的关闭和启动应该要确保是正常的关闭和启动。==
那如何才是正常关闭Tomcat服务器呢?
需要使用命令行的方式来启动和停止Tomcat服务器:
==启动==:进入到项目pom.xml所在目录,执行tomcat7:run
==停止==:在启动的命令行界面,输入ctrl+c
有了上述两个正常启动和关闭的方式后,接下来的测试流程是:
(1)先启动Tomcat服务器
(2)访问http://localhost:8080/cookie-demo/demo1
将数据存入session中
(3)正确停止Tomcat服务器
(4)再次重新启动Tomcat服务器
(5)访问http://localhost:8080/cookie-demo/demo2
查看是否能获取到session中的数据
经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。
那么Tomcat服务器到底是如何做到的呢?
具体的原因就是:Session的钝化和活化:
-
钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中
-
钝化的数据路径为:
项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser
-
-
-
活化:再次启动服务器后,从文件中加载数据到Session中
-
数据加载到Session中后,路径中的
SESSIONS.ser
文件会被删除掉
-
对于上述的整个过程,大家只需要了解下即可。因为所有的过程都是Tomcat自己完成的,不需要我们参与。
小结
Session的钝化和活化介绍完后,需要我们注意的是:
-
session数据存储在服务端,服务器重启后,session数据会被保存
-
浏览器被关闭启动后,重新建立的连接就已经是一个全新的会话,获取的session数据也是一个新的对象
-
session的数据要想共享,浏览器不能关闭,所以session数据不能长期保存数据
-
cookie是存储在客户端,是可以长期保存
3.3.2 Session销毁
session的销毁会有两种方式:
-
默认情况下,无操作,30分钟自动销毁
-
对于这个失效时间,是可以通过配置进行修改的
-
在项目的web.xml中配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <session-config> <session-timeout>100</session-timeout> </session-config> </web-app>
-
如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的
-
-
-
-
调用Session对象的invalidate()进行销毁
-
在SessionDemo2类中添加session销毁的方法
@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取数据,从session中 //1. 获取Session对象 HttpSession session = request.getSession(); System.out.println(session); // 销毁 session.invalidate(); //2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
启动访问测试,先访问demo1将数据存入到session,再次访问demo2从session中获取数据
-
-
该销毁方法一般会在用户退出的时候,需要将session销毁掉。
-
Cookie和Session小结
-
Cookie 和 Session 都是来完成一次会话内多次请求间==数据共享==的。
所需两个对象放在一块,就需要思考:
Cookie和Session的区别是什么?
Cookie和Session的应用场景分别是什么?
-
区别:
-
存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
-
安全性:Cookie不安全,Session安全
-
数据大小:Cookie最大3KB,Session无大小限制
-
存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
-
服务器性能:Cookie不占服务器资源,Session占用服务器资源
-
-
应用场景:
-
购物车:使用Cookie来存储
-
以登录用户的名称展示:使用Session来存储
-
记住我功能:使用Cookie来存储
-
验证码:使用session来存储
-
-
结论
-
Cookie是用来保证用户在未登录情况下的身份识别
-
Session是用来保存用户登录后的数据
-
介绍完Cookie和Session以后,具体用哪个还是需要根据具体的业务进行具体分析。
4,用户登录注册案例
4.1 需求分析
需求说明:
-
完成用户登录功能,如果用户勾选“记住用户” ,则下次访问登录页面==自动==填充用户名密码
-
完成注册功能,并实现==验证码==功能
4.2 用户登录功能
-
需求:
-
用户登录成功后,跳转到列表页面,并在页面上展示当前登录的用户名称
-
用户登录失败后,跳转回登录页面,并在页面上展示对应的错误信息
-
实现流程分析
(1)前端通过表单发送请求和数据给Web层的LoginServlet
(2)在LoginServlet中接收请求和数据[用户名和密码]
(3)LoginServlet接收到请求和数据后,调用Service层完成根据用户名和密码查询用户对象
(4)在Service层需要编写UserService类,在类中实现login方法,方法中调用Dao层的UserMapper
(5)在UserMapper接口中,声明一个根据用户名和密码查询用户信息的方法
(6)Dao层把数据查询出来以后,将返回数据封装到User对象,将对象交给Service层
(7)Service层将数据返回给Web层
(8)Web层获取到User对象后,判断User对象,如果为Null,则将错误信息响应给登录页面,如果不为Null,则跳转到列表页面,并把当前登录用户的信息存入Session携带到列表页面。
-
具体实现
(1)完成Dao层的代码编写
(1.1)将04-资料\1. 登录注册案例\2. MyBatis环境\UserMapper.java
放到com.itheima.mapper`包下:
public interface UserMapper { /** * 根据用户名和密码查询用户对象 * @param username * @param password * @return */ @Select("select * from tb_user where username = #{username} and password = #{password}") User select(@Param("username") String username,@Param("password") String password); /** * 根据用户名查询用户对象 * @param username * @return */ @Select("select * from tb_user where username = #{username}") User selectByUsername(String username); /** * 添加用户 * @param user */ @Insert("insert into tb_user values(null,#{username},#{password})") void add(User user); }
(1.2)将04-资料\1. 登录注册案例\2. MyBatis环境\User.java
放到com.itheima.pojo
包下:
public class User { private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
(1.3)将04-资料\1. 登录注册案例\2. MyBatis环境\UserMapper.xml
放入到resources/com/itheima/mapper`目录下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.UserMapper"> </mapper>
(2)完成Service层的代码编写
(2.1)在com.itheima.service
包下,创建UserService类
public class UserService { //1.使用工具类获取SqlSessionFactory SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory(); /** * 登录方法 * @param username * @param password * @return */ public User login(String username,String password){ //2. 获取SqlSession SqlSession sqlSession = factory.openSession(); //3. 获取UserMapper UserMapper mapper = sqlSession.getMapper(UserMapper.class); //4. 调用方法 User user = mapper.select(username, password); //释放资源 sqlSession.close(); return user; } }
(3)完成页面和Web层的代码编写
(3.1)将04-资料\1. 登录注册案例\1. 静态页面
拷贝到项目的webapp
目录下:
(3.2)将login.html内容修改成login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv" style="height: 350px"> <form action="/brand-demo/loginServlet" method="post" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <div id="errorMsg">用户名或密码不正确</div> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <p>Remember:<input id="remember" name="remember" type="checkbox"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?</a> </div> </form> </div> </body> </html>
(3.3)创建LoginServlet类
@WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //2. 调用service查询 User user = service.login(username, password); //3. 判断 if(user != null){ //登录成功,跳转到查询所有的BrandServlet //将登陆成功后的user对象,存储到session HttpSession session = request.getSession(); session.setAttribute("user",user); String contextPath = request.getContextPath(); response.sendRedirect(contextPath+"/selectAllServlet"); }else { // 登录失败, // 存储错误信息到request request.setAttribute("login_msg","用户名或密码错误"); // 跳转到login.jsp request.getRequestDispatcher("/login.jsp").forward(request,response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3.4)在brand.jsp中<body>标签下添加欢迎当前用户的提示信息:
<h1>${user.username},欢迎您</h1>
(3.5) 修改login.jsp,将错误信息使用EL表达式来获取
修改前内容:<div id="errorMsg">用户名或密码不正确</div> 修改后内容: <div id="errorMsg">${login_msg}</div>
(4)启动,访问测试
(4.1) 进入登录页面,输入错误的用户名或密码
(4.2)输入正确的用户和密码信息
小结
-
在LoginServlet中,将登录成功的用户数据存入session中,方法在列表页面中获取当前登录用户信息进行展示
-
在LoginServlet中,将登录失败的错误信息存入到request中,如果存入到session中就会出现这次会话的所有请求都有登录失败的错误信息,这个是不需要的,所以不用存入到session中
4.3 记住我-设置Cookie
-
需求:
如果用户勾选“记住用户” ,则下次访问登陆页面自动填充用户名密码。这样可以提升用户的体验。
对应上面这个需求,最大的问题就是: 如何自动填充用户名和密码?
-
实现流程分析
因为记住我
功能要实现的效果是,就算用户把浏览器关闭过几天再来访问也能自动填充,所以需要将登陆信息存入一个可以长久保存,并且能够在浏览器关闭重新启动后依然有效的地方,就是我们前面讲的==Cookie==,所以:
-
将用户名和密码写入==Cookie==中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
-
在页面获取Cookie数据后,设置到用户名和密码框中
-
何时写入Cookie?
-
用户必须登陆成功后才需要写
-
用户必须在登录页面勾选了
记住我
的复选框 -
-
(1)前端需要在发送请求和数据的时候,多携带一个用户是否勾选Remember
的数据
(2)LoginServlet获取到数据后,调用Service完成用户名和密码的判定
(3)登录成功,并且用户在前端勾选了记住我
,需要往Cookie中写入用户名和密码的数据,并设置Cookie存活时间
(4)设置成功后,将数据响应给前端
-
具体实现
(1)在login.jsp为复选框设置值
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv" style="height: 350px"> <form action="/brand-demo/loginServlet" method="post" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <div id="errorMsg">${login_msg}</div> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?</a> </div> </form> </div> </body> </html>
(2)在LoginServlet获取复选框的值并在登录成功后进行设置Cookie
@WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //获取复选框数据 String remember = request.getParameter("remember"); //2. 调用service查询 User user = service.login(username, password); //3. 判断 if(user != null){ //登录成功,跳转到查询所有的BrandServlet //判断用户是否勾选记住我,字符串写前面是为了避免出现空指针异常 if("1".equals(remember)){ //勾选了,发送Cookie //1. 创建Cookie对象 Cookie c_username = new Cookie("username",username); Cookie c_password = new Cookie("password",password); // 设置Cookie的存活时间 c_username.setMaxAge( 60 * 60 * 24 * 7); c_password.setMaxAge( 60 * 60 * 24 * 7); //2. 发送 response.addCookie(c_username); response.addCookie(c_password); } //将登陆成功后的user对象,存储到session HttpSession session = request.getSession(); session.setAttribute("user",user); String contextPath = request.getContextPath(); response.sendRedirect(contextPath+"/selectAllServlet"); }else { // 登录失败, // 存储错误信息到request request.setAttribute("login_msg","用户名或密码错误"); // 跳转到login.jsp request.getRequestDispatcher("/login.jsp").forward(request,response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3)启动访问测试,
只有当前用户名和密码输入正确,并且勾选了Remeber的复选框,在响应头中才可以看得cookie的相关数据
4.4 记住我-获取Cookie
-
需求
登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就已经存储好了,接下来就需要在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。
如何在页面直接获取Cookie中的值呢?
2.实现流程分析
在页面可以使用EL表达式,${cookie.==key==.value}
key:指的是存储在cookie中的键名称
(1)在login.jsp用户名的表单输入框使用value值给表单元素添加默认值,value可以使用${cookie.username.value}
(2)在login.jsp密码的表单输入框使用value值给表单元素添加默认值,value可以使用${cookie.password.value}
3.具体实现
(1)修改login.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv" style="height: 350px"> <form action="/brand-demo/loginServlet" method="post" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <div id="errorMsg">${login_msg}</div> <p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p> <p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p> <p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset"> <a href="register.html">没有账号?</a> </div> </form> </div> </body> </html>
4.访问测试,重新访问登录页面,就可以看得用户和密码已经被填充。
4.5 用户注册功能
-
需求
-
注册功能:保存用户信息到数据库
-
验证码功能
-
展示验证码:展示验证码图片,并可以点击切换
-
校验验证码:验证码填写不正确,则注册失败
-
-
2.实现流程分析
(1)前端通过表单发送请求和数据给Web层的RegisterServlet
(2)在RegisterServlet中接收请求和数据[用户名和密码]
(3)RegisterServlet接收到请求和数据后,调用Service层完成用户信息的保存
(4)在Service层需要编写UserService类,在类中实现register方法,需要判断用户是否已经存在,如果不存在,则完成用户数据的保存
(5)在UserMapper接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法
(6)在UserService类中保存成功则返回true,失败则返回false,将数据返回给Web层
(7)Web层获取到结果后,如果返回的是true,则提示注册成功
,并转发到登录页面,如果返回false则提示用户名已存在
并转发到注册页面
-
具体实现
(1)Dao层代码参考资料中的内容完成
(2)编写Service层代码
public class UserService { //1.使用工具类获取SqlSessionFactory SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory(); /** * 注册方法 * @return */ public boolean register(User user){ //2. 获取SqlSession SqlSession sqlSession = factory.openSession(); //3. 获取UserMapper UserMapper mapper = sqlSession.getMapper(UserMapper.class); //4. 判断用户名是否存在 User u = mapper.selectByUsername(user.getUsername()); if(u == null){ // 用户名不存在,注册 mapper.add(user); sqlSession.commit(); } sqlSession.close(); return u == null; } }
(3)完成页面和Web层的代码编写
(3.1)将register.html内容修改成register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="css/register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="login.html">登录</a> </div> <form id="reg-form" action="/brand-demo/registerServlet" method="post"> <table> <tr> <td>用户名</td> <td class="inputs"> <input name="username" type="text" id="username"> <br> <span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span> </td> </tr> <tr> <td>密码</td> <td class="inputs"> <input name="password" type="password" id="password"> <br> <span id="password_err" class="err_msg" style="display: none">密码格式有误</span> </td> </tr> <tr> <td>验证码</td> <td class="inputs"> <input name="checkCode" type="text" id="checkCode"> <img src="imgs/a.jpg"> <a href="#" id="changeImg" >看不清?</a> </td> </tr> </table> <div class="buttons"> <input value="注 册" type="submit" id="reg_btn"> </div> <br class="clear"> </form> </div> </body> </html>
(3.2)编写RegisterServlet
@WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取用户名和密码数据 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); //2. 调用service 注册 boolean flag = service.register(user); //3. 判断注册成功与否 if(flag){ //注册功能,跳转登陆页面 request.setAttribute("register_msg","注册成功,请登录"); request.getRequestDispatcher("/login.jsp").forward(request,response); }else { //注册失败,跳转到注册页面 request.setAttribute("register_msg","用户名已存在"); request.getRequestDispatcher("/register.jsp").forward(request,response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(3.3)需要在页面上展示后台返回的错误信息,需要修改register.jsp
修改前:<span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span> 修改后:<span id="username_err" class="err_msg">${register_msg}</span>
(3.4)如果注册成功,需要把成功信息展示在登录页面,所以也需要修改login.jsp
修改前:<div id="errorMsg">${login_msg}</div> 修改后:<div id="errorMsg">${login_msg} ${register_msg}</div>
(3.5)修改login.jsp,将注册跳转地址修改为register.jsp
修改前:<a href="register.html">没有账号?</a> 修改后: <a href="register.jsp">没有账号?</a>
(3.6)启动测试,
如果是注册的用户信息已经存在:
如果注册的用户信息不存在,注册成功:
4.6 验证码-展示
-
需求分析
展示验证码:展示验证码图片,并可以点击切换
验证码的生成是通过工具类来实现的,具体的工具类参考
04-资料\1. 登录注册案例\CheckCodeUtil.java
在该工具类中编写main方法进行测试:
public static void main(String[] args) throws IOException { //生成验证码的图片位置 OutputStream fos = new FileOutputStream("d://a.jpg"); //checkCode为最终验证码的数据 String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4); System.out.println(checkCode); }
生成完验证码以后,我们就可以知晓:
-
验证码就是使用Java代码生成的一张图片
-
验证码的作用:防止机器自动注册,攻击服务器
-
实现流程分析
(1)前端发送请求给CheckCodeServlet
(2)CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端
思考:如何将图片写回到前端浏览器呢?
(1)Java中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上 (2)生成磁盘的过程中使用的是OutputStream流,如何把这个图片生成在页面呢? (3)前面在将Reponse对象的时候,它有一个方法可以获取其字节输出流,getOutputStream() (4)综上所述,我们可以把写往磁盘的流对象更好成Response的字节流,即可完成图片响应给前端
-
具体实现
(1)修改Register.jsp页面,将验证码的图片从后台获取
<tr> <td>验证码</td> <td class="inputs"> <input name="checkCode" type="text" id="checkCode"> <img id="checkCodeImg" src="/brand-demo/checkCodeServlet"> <a href="#" id="changeImg" >看不清?</a> </td> </tr> <script> document.getElementById("changeImg").onclick = function () { //路径后面添加时间戳的目的是避免浏览器进行缓存静态资源 document.getElementById("checkCodeImg").src = "/brand-demo/checkCodeServlet?"+new Date().getMilliseconds(); } </script>
(2)编写CheckCodeServlet类,用来接收请求生成验证码
@WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 生成验证码 ServletOutputStream os = response.getOutputStream(); String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
4.7验证码-校验
-
需求
-
判断程序生成的验证码 和 用户输入的验证码 是否一样,如果不一样,则阻止注册
-
验证码图片访问和提交注册表单是==两次==请求,所以要将程序生成的验证码存入Session中
-
思考:为什么要把验证码数据存入到Session中呢?
-
生成验证码和校验验证码是两次请求,此处就需要在一个会话的两次请求之间共享数据
-
验证码属于安全数据类的,所以我们选中Session来存储验证码数据。
-
实现流程分析
(1)在CheckCodeServlet中生成验证码的时候,将验证码数据存入Session对象
(2)前端将验证码和注册数据提交到后台,交给RegisterServlet类
(3)RegisterServlet类接收到请求和数据后,其中就有验证码,和Session中的验证码进行对比
(4)如果一致,则完成注册,如果不一致,则提示错误信息
-
具体实现
(1)修改CheckCodeServlet类,将验证码存入Session对象
@WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 生成验证码 ServletOutputStream os = response.getOutputStream(); String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4); // 存入Session HttpSession session = request.getSession(); session.setAttribute("checkCodeGen",checkCode); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
(2)在RegisterServlet中,获取页面的和session对象中的验证码,进行对比
package com.itheima.web; import com.itheima.pojo.User; import com.itheima.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; @WebServlet("/registerServlet") public class RegisterServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取用户名和密码数据 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); // 获取用户输入的验证码 String checkCode = request.getParameter("checkCode"); // 程序生成的验证码,从Session获取 HttpSession session = request.getSession(); String checkCodeGen = (String) session.getAttribute("checkCodeGen"); // 比对 if(!checkCodeGen.equalsIgnoreCase(checkCode)){ request.setAttribute("register_msg","验证码错误"); request.getRequestDispatcher("/register.jsp").forward(request,response); // 不允许注册 return; } //2. 调用service 注册 boolean flag = service.register(user); //3. 判断注册成功与否 if(flag){ //注册功能,跳转登陆页面 request.setAttribute("register_msg","注册成功,请登录"); request.getRequestDispatcher("/login.jsp").forward(request,response); }else { //注册失败,跳转到注册页面 request.setAttribute("register_msg","用户名已存在"); request.getRequestDispatcher("/register.jsp").forward(request,response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
至此,用户的注册登录功能就已经完成了。
七、Filter&Listener&Ajax
今日目标:
能够使用 Filter 完成登陆状态校验功能
能够使用 axios 发送 ajax 请求
熟悉 json 格式,并能使用 Fastjson 完成 java 对象和 json 串的相互转换
使用 axios + json 完成综合案例
1,Filter
1.1 Filter概述
Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。Servlet 我们之前都已经学习过了,Filter和Listener 我们今天都会进行学习。
过滤器可以把对资源的请求==拦截==下来,从而实现一些特殊的功能。
如下图所示,浏览器可以访问服务器上的所有的资源(servlet、jsp、html等)
而在访问到这些资源之前可以使过滤器拦截来下,也就是说在访问资源之前会先经过 Filter,如下图
拦截器拦截到后可以做什么功能呢?
==过滤器一般完成一些通用的操作。==比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。
我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到 查询所有
的超链接
当我点击该按钮,居然可以看到品牌的数据
这显然和我们的要求不符。我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是==权限控制==,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理
、 敏感字符处理
等等…
1.2 Filter快速入门
1.2.1 开发步骤
进行 Filter
开发分成以下三步实现
-
定义类,实现 Filter接口,并重写其所有方法
-
配置Filter拦截资源的路径:在类上定义
@WebFilter
注解。而注解的value
属性值/*
表示拦截所有的资源 -
在doFilter方法中输出一句话,并放行
上述代码中的
chain.doFilter(request,response);
就是放行,也就是让其访问本该访问的资源。
1.2.2 代码演示
创建一个项目,项目下有一个 hello.jsp
页面,项目结构如下:
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>filter-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>80</port> </configuration> </plugin> </plugins> </build> </project>
hello.jsp
页面内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>hello JSP~</h1> </body> </html>
我们现在在浏览器输入 http://localhost/filter-demo/hello.jsp
访问 hello.jsp
页面,这里是可以访问到 hello.jsp
页面内容的。
接下来编写过滤器。过滤器是 Web 三大组件之一,所以我们将 filter
创建在 com.itheima.web.filter
包下,起名为 FilterDemo
@WebFilter("/*") public class FilterDemo implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("FilterDemo..."); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
重启启动服务器,再次重新访问 hello.jsp
页面,这次发现页面没有任何效果,但是在 idea
的控制台可以看到如下内容
上述效果说明 FilterDemo
这个过滤器的 doFilter()
方法执行了,但是为什么在浏览器上看不到 hello.jsp
页面的内容呢?这是因为在 doFilter()
方法中添加放行的方法才能访问到 hello.jsp
页面。那就在 doFilter()
方法中添加放行的代码
//放行 chain.doFilter(request,response);
再次重启服务器并访问 hello.jsp
页面,发现这次就可以在浏览器上看到页面效果。
FilterDemo
过滤器完整代码如下:
@WebFilter("/*") public class FilterDemo implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("1.FilterDemo..."); //放行 chain.doFilter(request,response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
1.3 Filter执行流程
如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:
-
放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
从上图就可以看出肯定 ==会== 回到Filter中
-
如果回到Filter中,是重头执行还是执行放行后的逻辑呢?
如果是重头执行的话,就意味着
放行前逻辑
会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到放行后逻辑
,执行该部分代码。
通过上述的说明,我们就可以总结Filter的执行流程如下:
接下来我们通过代码验证一下,在 doFilter()
方法前后都加上输出语句,如下
同时在 hello.jsp
页面加上输出语句,如下
执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp
页面,在控制台打印的内容如下:
以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。
1.4 Filter拦截路径配置
拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter
注解进行配置。如:@WebFilter("拦截路径")
拦截路径有如下四种配置方式:
-
拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
-
目录拦截:/user/*:访问/user下的所有资源,都会被拦截
-
后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
-
拦截所有:/*:访问所有资源,都会被拦截
通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet
的请求资源路径配置方式一样,但是表示的含义不同。
1.5 过滤器链
1.5.1 概述
过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程
上图中的过滤器链执行是按照以下流程执行:
-
执行
Filter1
的放行前逻辑代码 -
执行
Filter1
的放行代码 -
执行
Filter2
的放行前逻辑代码 -
执行
Filter2
的放行代码 -
访问到资源
-
执行
Filter2
的放行后逻辑代码 -
执行
Filter1
的放行后逻辑代码
以上流程串起来就像一条链子,故称之为过滤器链。
1.5.2 代码演示
-
编写第一个过滤器
FilterDemo
,配置成拦截所有资源@WebFilter("/*") public class FilterDemo implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //1. 放行前,对 request数据进行处理 System.out.println("1.FilterDemo..."); //放行 chain.doFilter(request,response); //2. 放行后,对Response 数据进行处理 System.out.println("3.FilterDemo..."); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
-
编写第二个过滤器
FilterDemo2
,配置炒年糕拦截所有资源@WebFilter("/*") public class FilterDemo2 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //1. 放行前,对 request数据进行处理 System.out.println("2.FilterDemo..."); //放行 chain.doFilter(request,response); //2. 放行后,对Response 数据进行处理 System.out.println("4.FilterDemo..."); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
-
修改
hello.jsp
页面中脚本的输出语句<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>hello JSP~</h1> <% System.out.println("3.hello jsp"); %> </body> </html>
-
启动服务器,在浏览器输入
http://localhost/filter-demo/hello.jsp
进行测试,在控制台打印内容如下从结果可以看到确实是按照我们之前说的执行流程进行执行的。
1.5.3 问题
上面代码中为什么是先执行 FilterDemo
,后执行 FilterDemo2
呢?
我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。
比如有如下两个名称的过滤器 : BFilterDemo
和 AFilterDemo
。那一定是 AFilterDemo
过滤器先执行。
1.6 案例
1.6.1 需求
访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面
1.6.2 分析
我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter
,在该过滤器中进行登陆状态校验即可。而在该 Filter
中逻辑如下:
1.6.3 代码实现
1.6.3.1 创建Filter
在 brand-demo
工程创建 com.itheima.web.filter
包,在该下创建名为 LoginFilter
的过滤器
@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { } public void init(FilterConfig config) throws ServletException { } public void destroy() { } }
1.6.3.2 编写逻辑代码
在 doFilter()
方法中编写登陆状态校验的逻辑代码。
我们首先需要从 session
对象中获取用户信息,但是 ServletRequest
类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest
对象。
HttpServletRequest req = (HttpServletRequest) request;
然后完成以下逻辑
-
获取Session对象
-
从Session对象中获取名为
user
的数据 -
判断获取到的数据是否是 null
-
如果不是,说明已经登陆,放行
-
如果是,说明尚未登陆,将提示信息存储到域对象中并跳转到登陆页面
-
代码如下:
@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; //1. 判断session中是否有user HttpSession session = req.getSession(); Object user = session.getAttribute("user"); //2. 判断user是否为null if(user != null){ // 登录过了 //放行 chain.doFilter(request, response); }else { // 没有登陆,存储提示信息,跳转到登录页面 req.setAttribute("login_msg","您尚未登陆!"); req.getRequestDispatcher("/login.jsp").forward(req,response); } } public void init(FilterConfig config) throws ServletException { } public void destroy() { } }
1.6.3.3 测试并抛出问题
在浏览器上输入 http://localhost:8080/brand-demo/
,可以看到如下页面效果
从上面效果可以看出没有登陆确实是跳转到登陆页面了,但是登陆页面为什么展示成这种效果了呢?
1.6.3.4 问题分析及解决
因为登陆页面需要 css/login.css
这个文件进行样式的渲染,下图是登陆页面引入的css文件图解
而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。
综上,我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理
//判断访问资源路径是否和登录注册相关 //1,在数组中存储登陆和注册相关的资源路径 String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"}; //2,获取当前访问的资源路径 String url = req.getRequestURL().toString(); //3,遍历数组,获取到每一个需要放行的资源路径 for (String u : urls) { //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串 /* 比如当前访问的资源路径是 /brand-demo/login.jsp 而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行 */ if(url.contains(u)){ //找到了,放行 chain.doFilter(request, response); //break; return; } }
1.6.3.5 过滤器完整代码
@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; //判断访问资源路径是否和登录注册相关 //1,在数组中存储登陆和注册相关的资源路径 String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"}; //2,获取当前访问的资源路径 String url = req.getRequestURL().toString(); //3,遍历数组,获取到每一个需要放行的资源路径 for (String u : urls) { //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串 /* 比如当前访问的资源路径是 /brand-demo/login.jsp 而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行 */ if(url.contains(u)){ //找到了,放行 chain.doFilter(request, response); //break; return; } } //1. 判断session中是否有user HttpSession session = req.getSession(); Object user = session.getAttribute("user"); //2. 判断user是否为null if(user != null){ // 登录过了 //放行 chain.doFilter(request, response); }else { // 没有登陆,存储提示信息,跳转到登录页面 req.setAttribute("login_msg","您尚未登陆!"); req.getRequestDispatcher("/login.jsp").forward(req,response); } } public void init(FilterConfig config) throws ServletException { } public void destroy() { } }
2,Listener
2.1 概述
-
Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
-
监听器可以监听就是在
application
,session
,request
三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。request 和 session 我们学习过。而
application
是ServletContext
类型的对象。ServletContext
代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。
2.2 分类
JavaWeb 提供了8个监听器:
这里面只有 ServletContextListener
这个监听器后期我们会接触到,ServletContextListener
是用来监听 ServletContext
对象的创建和销毁。
ServletContextListener
接口中有以下两个方法
-
void contextInitialized(ServletContextEvent sce)
:ServletContext
对象被创建了会自动执行的方法 -
void contextDestroyed(ServletContextEvent sce)
:ServletContext
对象被销毁时会自动执行的方法
2.3 代码演示
我们只演示一下 ServletContextListener
监听器
-
定义一个类,实现
ServletContextListener
接口 -
重写所有的抽象方法
-
使用
@WebListener
进行配置
代码如下:
@WebListener public class ContextLoaderListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { //加载资源 System.out.println("ContextLoaderListener..."); } @Override public void contextDestroyed(ServletContextEvent sce) { //释放资源 } }
启动服务器,就可以在启动的日志信息中看到 contextInitialized()
方法输出的内容,同时也说明了 ServletContext
对象在服务器启动的时候被创建了。
3,Ajax
3.1 概述
==AJAX
(Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。==
我们先来说概念中的 JavaScript
和 XML
,JavaScript
表明该技术和前端相关;XML
是指以此进行数据交换。而这两个我们之前都学习过。
3.1.1 作用
AJAX 作用有以下两方面:
-
与服务器进行数据交换:通过AJAX可以给服务器发送请求,服务器将数据直接响应回给浏览器。如下图
我们先来看之前做功能的流程,如下图:
如上图,Servlet
调用完业务逻辑层后将数据存储到域对象中,然后跳转到指定的 jsp
页面,在页面上使用 EL表达式
和 JSTL
标签库进行数据的展示。
而我们学习了AJAX 后,就可以==使用AJAX和服务器进行通信,以达到使用 HTML+AJAX来替换JSP页面==了。如下图,浏览器发送请求servlet,servlet 调用完业务逻辑层后将数据直接响应回给浏览器页面,页面使用 HTML 来进行数据展示。
-
异步交互:可以在==不重新加载整个页面==的情况下,与服务器交换数据并==更新部分网页==的技术,如:搜索联想、用户名是否可用校验,等等…
上图所示的效果我们经常见到,在我们输入一些关键字(例如 奥运
)后就会在下面联想出相关的内容,而联想出来的这部分数据肯定是存储在百度的服务器上,而我们并没有看出页面重新刷新,这就是 ==更新局部页面== 的效果。再如下图:
我们在用户名的输入框输入用户名,当输入框一失去焦点,如果用户名已经被占用就会在下方展示提示的信息;在这整个过程中也没有页面的刷新,只是在局部展示出了提示信息,这就是 ==更新局部页面== 的效果。
3.1.2 同步和异步
知道了局部刷新后,接下来我们再聊聊同步和异步:
-
同步发送请求过程如下
浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束后才能,浏览器页面才能继续做其他的操作。
-
异步发送请求过程如下
浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。
3.2 快速入门
3.2.1 服务端实现
在项目的创建 com.itheima.web.servlet
,并在该包下创建名为 AjaxServlet
的servlet
@WebServlet("/ajaxServlet") public class AjaxServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 响应数据 response.getWriter().write("hello ajax~"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
3.2.2 客户端实现
在 webapp
下创建名为 01-ajax-demo1.html
的页面,在该页面书写 ajax
代码
-
创建核心对象,不同的浏览器创建的对象是不同的
var xhttp; if (window.XMLHttpRequest) { xhttp = new XMLHttpRequest(); } else { // code for IE6, IE5 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
-
发送请求
//建立连接 xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet"); //发送请求 xhttp.send();
-
获取响应
xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { // 通过 this.responseText 可以获取到服务端响应的数据 alert(this.responseText); } };
完整代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> //1. 创建核心对象 var xhttp; if (window.XMLHttpRequest) { xhttp = new XMLHttpRequest(); } else { // code for IE6, IE5 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } //2. 发送请求 xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet"); xhttp.send(); //3. 获取响应 xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { alert(this.responseText); } }; </script> </body> </html>
3.2.3 测试
在浏览器地址栏输入 http://localhost:8080/ajax-demo/01-ajax-demo1.html
,在 01-ajax-demo1.html
加载的时候就会发送 ajax
请求,效果如下
我们可以通过 开发者模式
查看发送的 AJAX 请求。在浏览器上按 F12
快捷键
这个是查看所有的请求,如果我们只是想看 异步请求的话,点击上图中 All
旁边的 XHR
,会发现只展示 Type 是 xhr
的请求。如下图:
3.3 案例
需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在
3.3.1 分析
-
前端完成的逻辑
-
给用户名输入框绑定光标失去焦点事件
onblur
-
发送 ajax请求,携带username参数
-
处理响应:是否显示提示信息
-
-
后端完成的逻辑
-
接收用户名
-
调用service查询User。此案例是为了演示前后端异步交互,所以此处我们不做业务逻辑处理
-
返回标记
-
整体流程如下:
3.3.2 后端实现
在 com.ithiema.web.servlet
包中定义名为 SelectUserServlet
的servlet。代码如下:
@WebServlet("/selectUserServlet") public class SelectUserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收用户名 String username = request.getParameter("username"); //2. 调用service查询User对象,此处不进行业务逻辑处理,直接给 flag 赋值为 true,表明用户名占用 boolean flag = true; //3. 响应标记 response.getWriter().write("" + flag); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
3.3.3 前端实现
将 04-资料\1. 验证用户名案例\1. 静态页面
下的文件整体拷贝到项目下 webapp
下。并在 register.html
页面的 body
结束标签前编写 script
标签,在该标签中实现如下逻辑
第一步:给用户名输入框绑定光标失去焦点事件 onblur
//1. 给用户名输入框绑定 失去焦点事件 document.getElementById("username").onblur = function () { }
第二步:发送 ajax请求,携带username参数
在 第一步
绑定的匿名函数中书写发送 ajax 请求的代码
//2. 发送ajax请求 //2.1. 创建核心对象 var xhttp; if (window.XMLHttpRequest) { xhttp = new XMLHttpRequest(); } else { // code for IE6, IE5 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } //2.2. 发送请求 xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet); xhttp.send(); //2.3. 获取响应 xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { //处理响应的结果 } };
由于我们发送的是 GET 请求,所以需要在 URL 后拼接从输入框获取的用户名数据。而我们在 第一步
绑定的匿名函数中通过以下代码可以获取用户名数据
// 获取用户名的值 var username = this.value; //this : 给谁绑定的事件,this就代表谁
而携带数据需要将 URL 修改为:
xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username);
第三步:处理响应:是否显示提示信息
当 this.readyState == 4 && this.status == 200
条件满足时,说明已经成功响应数据了。
此时需要判断响应的数据是否是 "true" 字符串,如果是说明用户名已经占用给出错误提示;如果不是说明用户名未被占用清除错误提示。代码如下
//判断 if(this.responseText == "true"){ //用户名存在,显示提示信息 document.getElementById("username_err").style.display = ''; }else { //用户名不存在 ,清楚提示信息 document.getElementById("username_err").style.display = 'none'; }
综上所述,前端完成代码如下:
//1. 给用户名输入框绑定 失去焦点事件 document.getElementById("username").onblur = function () { //2. 发送ajax请求 // 获取用户名的值 var username = this.value; //2.1. 创建核心对象 var xhttp; if (window.XMLHttpRequest) { xhttp = new XMLHttpRequest(); } else { // code for IE6, IE5 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } //2.2. 发送请求 xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username); xhttp.send(); //2.3. 获取响应 xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { //alert(this.responseText); //判断 if(this.responseText == "true"){ //用户名存在,显示提示信息 document.getElementById("username_err").style.display = ''; }else { //用户名不存在 ,清楚提示信息 document.getElementById("username_err").style.display = 'none'; } } }; }
4,axios
Axios 对原生的AJAX进行封装,简化书写。
Axios官网是:https://www.axios-http.cn
4.1 基本使用
axios 使用是比较简单的,分为以下两步:
-
引入 axios 的 js 文件
<script src="js/axios-0.18.0.js"></script>
-
使用axios 发送请求,并获取响应结果
-
发送 get 请求
axios({ method:"get", url:"http://localhost:8080/ajax-demo1/aJAXDemo1?username=zhangsan" }).then(function (resp){ alert(resp.data); })
-
发送 post 请求
axios({ method:"post", url:"http://localhost:8080/ajax-demo1/aJAXDemo1", data:"username=zhangsan" }).then(function (resp){ alert(resp.data); });
-
axios()
是用来发送异步请求的,小括号中使用 js 对象传递请求相关的参数:
-
method
属性:用来设置请求方式的。取值为get
或者post
。 -
url
属性:用来书写请求的资源路径。如果是get
请求,需要将请求参数拼接到路径的后面,格式为:url?参数名=参数值&参数名2=参数值2
。 -
data
属性:作为请求体被发送的数据。也就是说如果是post
请求的话,数据需要作为data
属性的值。
then()
需要传递一个匿名函数。我们将 then()
中传递的匿名函数称为 ==回调函数==,意思是该匿名函数在发送请求时不会被调用,而是在成功响应后调用的函数。而该回调函数中的 resp
参数是对响应的数据进行封装的对象,通过 resp.data
可以获取到响应的数据。
4.2 快速入门
4.2.1 后端实现
定义一个用于接收请求的servlet,代码如下:
@WebServlet("/axiosServlet") public class AxiosServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("get..."); //1. 接收请求参数 String username = request.getParameter("username"); System.out.println(username); //2. 响应数据 response.getWriter().write("hello Axios~"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("post..."); this.doGet(request, response); } }
4.2.2 前端实现
-
引入 js 文件
<script src="js/axios-0.18.0.js"></script>
-
发送 ajax 请求
-
get 请求
axios({ method:"get", url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan" }).then(function (resp) { alert(resp.data); })
-
post 请求
axios({ method:"post", url:"http://localhost:8080/ajax-demo/axiosServlet", data:"username=zhangsan" }).then(function (resp) { alert(resp.data); })
-
整体页面代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="js/axios-0.18.0.js"></script> <script> //1. get /* axios({ method:"get", url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan" }).then(function (resp) { alert(resp.data); })*/ //2. post 在js中{} 表示一个js对象,而这个js对象中有三个属性 axios({ method:"post", url:"http://localhost:8080/ajax-demo/axiosServlet", data:"username=zhangsan" }).then(function (resp) { alert(resp.data); }) </script> </body> </html>
4.3 请求方法别名
为了方便起见, Axios 已经为所有支持的请求方法提供了别名。如下:
-
get
请求 :axios.get(url[,config])
-
delete
请求 :axios.delete(url[,config])
-
head
请求 :axios.head(url[,config])
-
options
请求 :axios.option(url[,config])
-
post
请求:axios.post(url[,data[,config])
-
put
请求:axios.put(url[,data[,config])
-
patch
请求:axios.patch(url[,data[,config])
而我们只关注 get
请求和 post
请求。
入门案例中的 get
请求代码可以改为如下:
axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) { alert(resp.data); });
入门案例中的 post
请求代码可以改为如下:
axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) { alert(resp.data); })
5,JSON
5.1 概述
==概念:JavaScript Object Notation
。JavaScript 对象表示法.==
如下是 JavaScript
对象的定义格式:
{ name:"zhangsan", age:23, city:"北京" }
接下来我们再看看 JSON
的格式:
{ "name":"zhangsan", "age":23, "city":"北京" }
通过上面 js 对象格式和 json 格式进行对比,发现两个格式特别像。只不过 js 对象中的属性名可以使用引号(可以是单引号,也可以是双引号);而 json
格式中的键要求必须使用双引号括起来,这是 json
格式的规定。json
格式的数据有什么作用呢?
作用:由于其语法格式简单,层次结构鲜明,现多用于作为==数据载体==,在网络中进行数据传输。如下图所示就是服务端给浏览器响应的数据,这个数据比较简单,如果现需要将 JAVA 对象中封装的数据响应回给浏览器的话,应该以何种数据传输呢?
大家还记得 ajax
的概念吗? 是 ==异步的 JavaScript 和 xml==。这里的 xml就是以前进行数据传递的方式,如下:
<student> <name>张三</name> <age>23</age> <city>北京</city> </student>
再看 json
描述以上数据的写法:
{ "name":"张三", "age":23, "city":"北京" }
上面两种格式进行对比后就会发现 json
格式数据的简单,以及所占的字节数少等优点。
5.2 JSON 基础语法
5.2.1 定义格式
JSON
本质就是一个字符串,但是该字符串内容是有一定的格式要求的。 定义格式如下:
var 变量名 = '{"key":value,"key":value,...}';
JSON
串的键要求必须使用双引号括起来,而值根据要表示的类型确定。value 的数据类型分为如下
-
数字(整数或浮点数)
-
字符串(使用双引号括起来)
-
逻辑值(true或者false)
-
数组(在方括号中)
-
对象(在花括号中)
-
null
示例:
var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
5.2.2 代码演示
创建一个页面,在该页面的 <script>
标签中定义json字符串
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> //1. 定义JSON字符串 var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}' alert(jsonStr); </script> </body> </html>
通过浏览器打开,页面效果如下图所示
现在我们需要获取到该 JSON
串中的 name
属性值,应该怎么处理呢?
如果它是一个 js 对象,我们就可以通过 js对象.属性名
的方式来获取数据。JS 提供了一个对象 JSON
,该对象有如下两个方法:
-
parse(str)
:将 JSON串转换为 js 对象。使用方式是: ==var jsObject = JSON.parse(jsonStr);
== -
stringify(obj)
:将 js 对象转换为 JSON 串。使用方式是:==var jsonStr = JSON.stringify(jsObject)
==
代码演示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> //1. 定义JSON字符串 var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}' alert(jsonStr); //2. 将 JSON 字符串转为 JS 对象 let jsObject = JSON.parse(jsonStr); alert(jsObject) alert(jsObject.name) //3. 将 JS 对象转换为 JSON 字符串 let jsonStr2 = JSON.stringify(jsObject); alert(jsonStr2) </script> </body> </html>
5.2.3 发送异步请求携带参数
后面我们使用 axios
发送请求时,如果要携带复杂的数据时都会以 JSON
格式进行传递,如下
axios({ method:"post", url:"http://localhost:8080/ajax-demo/axiosServlet", data:"username=zhangsan" }).then(function (resp) { alert(resp.data); })
请求参数不可能由我们自己拼接字符串吧?肯定不用,可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象)
转换为 JSON
串,再将该 JSON
串作为 axios
的 data
属性值进行请求参数的提交。如下:
var jsObject = {name:"张三"}; axios({ method:"post", url:"http://localhost:8080/ajax-demo/axiosServlet", data: JSON.stringify(jsObject) }).then(function (resp) { alert(resp.data); })
而 axios
是一个很强大的工具。我们只需要将需要提交的参数封装成 js 对象,并将该 js 对象作为 axios
的 data
属性值进行,它会自动将 js 对象转换为 JSON
串进行提交。如下:
var jsObject = {name:"张三"}; axios({ method:"post", url:"http://localhost:8080/ajax-demo/axiosServlet", data:jsObject //这里 axios 会将该js对象转换为 json 串的 }).then(function (resp) { alert(resp.data); })
==注意:==
js 提供的
JSON
对象我们只需要了解一下即可。因为axios
会自动对 js 对象和JSON
串进行想换转换。发送异步请求时,如果请求参数是
JSON
格式,那请求方式必须是POST
。因为JSON
串需要放在请求体中。
5.3 JSON串和Java对象的相互转换
学习完 json 后,接下来聊聊 json 的作用。以后我们会以 json 格式的数据进行前后端交互。前端发送请求时,如果是复杂的数据就会以 json 提交给后端;而后端如果需要响应一些复杂的数据时,也需要以 json 格式将数据响应回给浏览器。
在后端我们就需要重点学习以下两部分操作:
-
请求数据:JSON字符串转为Java对象
-
响应数据:Java对象转为JSON字符串
接下来给大家介绍一套 API,可以实现上面两部分操作。这套 API 就是 Fastjson
5.3.1 Fastjson 概述
Fastjson
是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON
库,是目前Java语言中最快的 JSON
库,可以实现 Java
对象和 JSON
字符串的相互转换。
5.3.2 Fastjson 使用
Fastjson
使用也是比较简单的,分为以下三步完成
-
导入坐标
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
-
Java对象转JSON
String jsonStr = JSON.toJSONString(obj);
将 Java 对象转换为 JSON 串,只需要使用
Fastjson
提供的JSON
类中的toJSONString()
静态方法即可。 -
JSON字符串转Java对象
User user = JSON.parseObject(jsonStr, User.class);
将 json 转换为 Java 对象,只需要使用
Fastjson
提供的JSON
类中的parseObject()
静态方法即可。
5.3.3 代码演示
-
引入坐标
-
创建一个类,专门用来测试 Java 对象和 JSON 串的相互转换,代码如下:
public class FastJsonDemo { public static void main(String[] args) { //1. 将Java对象转为JSON字符串 User user = new User(); user.setId(1); user.setUsername("zhangsan"); user.setPassword("123"); String jsonString = JSON.toJSONString(user); System.out.println(jsonString);//{"id":1,"password":"123","username":"zhangsan"} //2. 将JSON字符串转为Java对象 User u = JSON.parseObject("{\"id\":1,\"password\":\"123\",\"username\":\"zhangsan\"}", User.class); System.out.println(u); } }
6,案例
6.1 需求
npm仓库查询地址
使用Axios + JSON 完成品牌列表数据查询和添加。页面效果还是下图所示:
6.2 查询所有功能
如上图所示就该功能的整体流程。前后端需以 JSON 格式进行数据的传递;由于此功能是查询所有的功能,前端发送 ajax 请求不需要携带参数,而后端响应数据需以如下格式的 json 数据
6.2.1 环境准备
将 02-AJAX\04-资料\3. 品牌列表案例\初始工程
下的 brand-demo
工程拷贝到我们自己 工作空间
,然后再将项目导入到我们自己的 Idea 中。工程目录结构如下:
==注意:==
-
在给定的原始工程中已经给定一些代码。而在此案例中我们只关注前后端交互代码实现
-
要根据自己的数据库环境去修改连接数据库的信息,在
mybatis-config.xml
核心配置文件中修改
6.2.2 后端实现
在 com.itheima.web
包下创建名为 SelectAllServlet
的 servlet
,具体的逻辑如下:
-
调用 service 的
selectAll()
方法进行查询所有的逻辑处理 -
将查询到的集合数据转换为 json 数据。我们将此过程称为 ==序列化==;如果是将 json 数据转换为 Java 对象,我们称之为 ==反序列化==
-
将 json 数据响应回给浏览器。这里一定要设置响应数据的类型及字符集
response.setContentType("text/json;charset=utf-8");
SelectAllServlet
代码如下:
@WebServlet("/selectAllServlet") public class SelectAllServlet extends HttpServlet { private BrandService brandService = new BrandService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 调用Service查询 List<Brand> brands = brandService.selectAll(); //2. 将集合转换为JSON数据 序列化 String jsonString = JSON.toJSONString(brands); //3. 响应数据 application/json text/json response.setContentType("text/json;charset=utf-8"); response.getWriter().write(jsonString); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
6.2.3 前端实现
-
引入 js 文件
在 brand.html
页面引入 axios
的 js 文件
<script src="js/axios-0.18.0.js"></script>
-
绑定
页面加载完毕
事件
在 brand.html
页面绑定加载完毕事件,该事件是在页面加载完毕后被触发,代码如下
window.onload = function() { }
-
发送异步请求
在页面加载完毕事件绑定的匿名函数中发送异步请求,代码如下:
//2. 发送ajax请求 axios({ method:"get", url:"http://localhost:8080/brand-demo/selectAllServlet" }).then(function (resp) { });
-
处理响应数据
在 then
中的回调函数中通过 resp.data
可以获取响应回来的数据,而数据格式如下
现在我们需要拼接字符串,将下面表格中的所有的 tr
拼接到一个字符串中,然后使用 document.getElementById("brandTable").innerHTML = 拼接好的字符串
就可以动态的展示出用户想看到的数据
而表头行是固定的,所以先定义初始值是表头行数据的字符串,如下
//获取数据 let brands = resp.data; let tableData = " <tr>\n" + " <th>序号</th>\n" + " <th>品牌名称</th>\n" + " <th>企业名称</th>\n" + " <th>排序</th>\n" + " <th>品牌介绍</th>\n" + " <th>状态</th>\n" + " <th>操作</th>\n" + " </tr>";
接下来遍历响应回来的数据 brands
,拿到每一条品牌数据
for (let i = 0; i < brands.length ; i++) { let brand = brands[i]; }
紧接着就是从 brand
对象中获取数据并且拼接 数据行
,累加到 tableData
字符串变量中
tableData += "\n" + " <tr align=\"center\">\n" + " <td>"+(i+1)+"</td>\n" + " <td>"+brand.brandName+"</td>\n" + " <td>"+brand.companyName+"</td>\n" + " <td>"+brand.ordered+"</td>\n" + " <td>"+brand.description+"</td>\n" + " <td>"+brand.status+"</td>\n" + "\n" + " <td><a href=\"#\">修改</a> <a href=\"#\">删除</a></td>\n" + " </tr>";
最后再将拼接好的字符串写到表格中
// 设置表格数据 document.getElementById("brandTable").innerHTML = tableData;
整体页面代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="addBrand.html"><input type="button" value="新增"></a><br> <hr> <table id="brandTable" border="1" cellspacing="0" width="100%"> </table> <script src="js/axios-0.18.0.js"></script> <script> //1. 当页面加载完成后,发送ajax请求 window.onload = function () { //2. 发送ajax请求 axios({ method:"get", url:"http://localhost:8080/brand-demo/selectAllServlet" }).then(function (resp) { //获取数据 let brands = resp.data; let tableData = " <tr>\n" + " <th>序号</th>\n" + " <th>品牌名称</th>\n" + " <th>企业名称</th>\n" + " <th>排序</th>\n" + " <th>品牌介绍</th>\n" + " <th>状态</th>\n" + " <th>操作</th>\n" + " </tr>"; for (let i = 0; i < brands.length ; i++) { let brand = brands[i]; tableData += "\n" + " <tr align=\"center\">\n" + " <td>"+(i+1)+"</td>\n" + " <td>"+brand.brandName+"</td>\n" + " <td>"+brand.companyName+"</td>\n" + " <td>"+brand.ordered+"</td>\n" + " <td>"+brand.description+"</td>\n" + " <td>"+brand.status+"</td>\n" + "\n" + " <td><a href=\"#\">修改</a> <a href=\"#\">删除</a></td>\n" + " </tr>"; } // 设置表格数据 document.getElementById("brandTable").innerHTML = tableData; }) } </script> </body> </html>
6.3 添加品牌功能
如上所示,当我们点击 新增
按钮,会跳转到 addBrand.html
页面。在 addBrand.html
页面输入数据后点击 提交
按钮,就会将数据提交到后端,而后端将数据保存到数据库中。
具体的前后端交互的流程如下:
==说明:==
前端需要将用户输入的数据提交到后端,这部分数据需要以 json 格式进行提交,数据格式如下:
6.3.1 后端实现
在 com.itheima.web
包下创建名为 AddServlet
的 servlet
,具体的逻辑如下:
-
获取请求参数
由于前端提交的是 json 格式的数据,所以我们不能使用
request.getParameter()
方法获取请求参数-
如果提交的数据格式是
username=zhangsan&age=23
,后端就可以使用request.getParameter()
方法获取 -
如果提交的数据格式是 json,后端就需要通过 request 对象获取输入流,再通过输入流读取数据
-
-
将获取到的请求参数(json格式的数据)转换为
Brand
对象 -
调用 service 的
add()
方法进行添加数据的逻辑处理 -
将 json 数据响应回给浏览器。
AddServlet
代码如下:
@WebServlet("/addServlet") public class AddServlet extends HttpServlet { private BrandService brandService = new BrandService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收数据,request.getParameter 不能接收json的数据 /* String brandName = request.getParameter("brandName"); System.out.println(brandName);*/ // 获取请求体数据 BufferedReader br = request.getReader(); String params = br.readLine(); // 将JSON字符串转为Java对象 Brand brand = JSON.parseObject(params, Brand.class); //2. 调用service 添加 brandService.add(brand); //3. 响应成功标识 response.getWriter().write("success"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
6.3.2 前端实现
在 addBrand.html
页面给 提交
按钮绑定点击事件,并在绑定的匿名函数中发送异步请求,代码如下:
//1. 给按钮绑定单击事件 document.getElementById("btn").onclick = function () { //2. 发送ajax请求 axios({ method:"post", url:"http://localhost:8080/brand-demo/addServlet", data:??? }).then(function (resp) { // 判断响应数据是否为 success if(resp.data == "success"){ location.href = "http://localhost:8080/brand-demo/brand.html"; } }) }
现在我们只需要考虑如何获取页面上用户输入的数据即可。
首先我们先定义如下的一个 js 对象,该对象是用来封装页面上输入的数据,并将该对象作为上面发送异步请求时 data
属性的值。
// 将表单数据转为json var formData = { brandName:"", companyName:"", ordered:"", description:"", status:"", };
接下来获取输入框输入的数据,并将获取到的数据赋值给 formData
对象指定的属性。比如获取用户名的输入框数据,并把该数据赋值给 formData
对象的 brandName
属性
// 获取表单数据 let brandName = document.getElementById("brandName").value; // 设置数据 formData.brandName = brandName;
==说明:其他的输入框都用同样的方式获取并赋值。==但是有一个比较特殊,就是状态数据,如下图是页面内容
我们需要判断哪儿个被选中,再将选中的单选框数据赋值给 formData
对象的 status
属性,代码实现如下:
let status = document.getElementsByName("status"); for (let i = 0; i < status.length; i++) { if(status[i].checked){ // formData.status = status[i].value ; } }
整体页面代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加品牌</title> </head> <body> <h3>添加品牌</h3> <form action="" method="post"> 品牌名称:<input id="brandName" name="brandName"><br> 企业名称:<input id="companyName" name="companyName"><br> 排序:<input id="ordered" name="ordered"><br> 描述信息:<textarea rows="5" cols="20" id="description" name="description"></textarea><br> 状态: <input type="radio" name="status" value="0">禁用 <input type="radio" name="status" value="1">启用<br> <input type="button" id="btn" value="提交"> </form> <script src="js/axios-0.18.0.js"></script> <script> //1. 给按钮绑定单击事件 document.getElementById("btn").onclick = function () { // 将表单数据转为json var formData = { brandName:"", companyName:"", ordered:"", description:"", status:"", }; // 获取表单数据 let brandName = document.getElementById("brandName").value; // 设置数据 formData.brandName = brandName; // 获取表单数据 let companyName = document.getElementById("companyName").value; // 设置数据 formData.companyName = companyName; // 获取表单数据 let ordered = document.getElementById("ordered").value; // 设置数据 formData.ordered = ordered; // 获取表单数据 let description = document.getElementById("description").value; // 设置数据 formData.description = description; let status = document.getElementsByName("status"); for (let i = 0; i < status.length; i++) { if(status[i].checked){ // formData.status = status[i].value ; } } //console.log(formData); //2. 发送ajax请求 axios({ method:"post", url:"http://localhost:8080/brand-demo/addServlet", data:formData }).then(function (resp) { // 判断响应数据是否为 success if(resp.data == "success"){ location.href = "http://localhost:8080/brand-demo/brand.html"; } }) } </script> </body> </html>
==说明:==
查询所有
功能和 添加品牌
功能就全部实现,大家肯定会感觉前端的代码很复杂;而这只是暂时的,后面学习了 vue
前端框架后,这部分前端代码就可以进行很大程度的简化。