一、会话跟踪技术的概述
对于
会话跟踪
这四个词,我们需要拆开来进行解释,首先要理解什么是
会话
,然后再去理解什么是
会 话跟踪:
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在 一次会话中可以包含多次请求和响应。
1.从浏览器发出请求到服务端响应数据给前端之后,一次会话
(
在浏览器和服务器之间
)
就被建立
了
2.会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
3.浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为
会
话
。
用实际场景来理解下会话,比如在我们访问京东的时候,当打开浏览器进入京东首页后,浏览器和
京东的服务器之间就建立了一次会话,后面的搜索商品
,
查看商品的详情
,
加入购物车等都是在这一
次会话中完成。
思考
:
下图中总共建立了几个会话
?
每个浏览器都会与服务端建立了一个会话,加起来总共是
3
个会话。
会话跟踪
:
一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在
同一次会话的多次请求间
共享数据
。
服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的
6
个请求来自
3
个浏览器
服务器需要用来识别请求是否来自同一个浏览器
服务器用来识别浏览器的过程,这个过程就是
会话跟踪
服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据
那么我们又有一个问题需要思考,一个会话中的多次请求为什么要共享数据呢
?
有了这个数据共享
功能后能实现哪些功能呢
?
购物车
:
加入购物车
和
去购物车结算
是两次请求,但是后面这次请求要想展示前一次请求所添加
的商品,就需要用到数据共享。
页面展示用户登录信息
:
很多网站,登录后访问多个功能发送多次请求后,浏览器上都会有当前
登录用户的信息
[
用户名
]
,比如百度、京东、码云等。
网站登录页面的
记住我
功能
:
当用户登录成功后,勾选
记住我
按钮后下次再登录的时候,网站就
会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次请求,他们之间也涉及
到共享数据。
登录页面的验证码功能
:
生成验证码和输入验证码点击注册这也是两次请求,这两次请求的数据
之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共
享数据。
通过这几个例子的讲解,相信大家对
会话追踪
技术已经有了一定的理解,该技术在实际开发中也非常
重要。那么接下来我们就需要去学习下
会话跟踪
技术,在学习这些技术之前,我们需要思考
:
为什么现
在浏览器和服务器不支持数据共享呢
?
浏览器和服务器之间使用的是
HTTP
请求来进行数据传输
HTTP
协议是
无状态
的,每次浏览器向服务器请求时,服务器都会将该请求视为
新的
请求
HTTP
协议设计成无状态的目的是让每次请求之间相互独立,互不影响
请求与请求之间独立后,就无法实现多次请求之间的数据共享
分析完具体的原因后,那么该如何实现会话跟踪技术呢
?
具体的实现方式有
:
(1)
客户端会话跟踪技术:
Cookie
(2)
服务端会话跟踪技术:
Session
这两个技术都可以实现会话跟踪,它们之间最大的区别
:
Cookie
是存储在浏览器端而
Session
是存储
在服务器端
具体的学习思路为
:
CooKie
的基本使用、原理、使用细节
Session
的基本使用、原理、使用细节
Cookie
和
Session
的综合案例
小结
在这节中,我们主要介绍了下什么是会话和会话跟踪技术,需要注意的是
:
HTTP
协议是无状态的,靠
HTTP
协议是无法实现会话跟踪
想要实现会话跟踪,就需要用到
Cookie
和
Session
这个
Cookie
和
Session
具体该如何使用,接下来就先从
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"
)
publicclass
AServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
}
}
(3)
在
Servlet
中创建
Cookie
对象,存入数据,发送给前端
@WebServlet
(
"/aServlet"
)
publicclass
AServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
发送
Cookie
//1.
创建
Cookie
对象
Cookiecookie
=
new
Cookie
(
"username"
,
"zs"
);
//2.
发送
Cookie
,
response
response
.
addCookie
(
cookie
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
4
)启动测试,在浏览器查看
Cookie
对象中的值
访问
http://localhost:8080/cookie
-
demo/aServlet
chrome
浏览器查看
Cookie
的值,有两种方式
,
分布式
:
方式一
:
方式二
:
选中打开开发者工具或者 使用快捷键
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
@WebServlet
(
"/bServlet"
)
publicclass
BServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
}
}
2.
在
BServlet
中使用
request
对象获取
Cookie
数组,遍历数组,从数据中获取指定名称对应的
值
@WebServlet
(
"/bServlet"
)
publicclass
BServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
获取
Cookie
//1.
获取
Cookie
数组
Cookie
[]
cookies
=
request
.
getCookies
();
//2.
遍历数组
for
(
Cookiecookie
:
cookies
){
//3.
获取数据
String
name
=
cookie
.
getName
();
if
(
"username"
.
equals
(
name
)){
String
value
=
cookie
.
getValue
();
System
.
out
.
println
(
name
+
":"
+
value
);
break
;
}
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
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 = new Cookie("key","value");
发送
Cookie
到客户端使用的是
Reponse
对象
:response.addCookie(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
的存活时间。
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
,
参数值为
:
1.
正数:将
Cookie
写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
2.
负数:默认值,
Cookie
在当前浏览器内存中,当浏览器关闭,则
Cookie
被销毁
3.
零:删除对应
Cookie
接下来,咱们就在
AServlet
中去设置
Cookie
的存活时间。
@WebServlet
(
"/aServlet"
)
publicclass
AServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
发送
Cookie
//1.
创建
Cookie
对象
Cookiecookie
=
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
(
HttpServletRequestrequest
,
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.Cookie存储中文
首先,先来演示一个效果,将之前
username=zs
的值改成
username=
张三
,把汉字
张三
存入到
Cookie
中,看是什么效果
:
@WebServlet
(
"/aServlet"
)
publicclass
AServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
发送
Cookie
String
value
=
"
张三
"
;
Cookiecookie
=
new
Cookie
(
"username"
,
value
);
//
设置存活时间
,
1
周
7
天
cookie
.
setMaxAge
(
60
*
60
*
24
*
7
);
//2.
发送
Cookie
,
response
response
.
addCookie
(
cookie
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
启动访问测试,访问
http://localhost:8080/cookie
-
demo/aServlet
会发现浏览器会提示错误信
息
通过上面的案例演示,我们得到一个结论
:
Cookie
不能存储中文,但是如果有这方面的需求,这个时候该如何解决呢
?
这个时候,我们可以使用之前学过的一个知识点叫
URL
编码
,所以如果需要存储中文,就需要进行转
码,具体的实现思路为
:
1.
在
AServlet
中对中文进行
URL
编码,采用
URLEncoder.encode()
,将编码后的值存入
Cookie
中
2.
在
BServlet
中获取
Cookie
中的值
,
获取的值为
URL
编码后的值
3.
将获取的值在进行
URL
解码
,
采用
URLDecoder.decode()
,就可以获取到对应的中文值
(1)
在
AServlet
中对中文进行
URL
编码
@WebServlet
(
"/aServlet"
)
publicclass
AServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
发送
Cookie
String
value
=
"
张三
"
;
//
对中文进行
URL
编码
value
=
URLEncoder
.
encode
(
value
,
"UTF-8"
);
System
.
out
.
println
(
"
存储数据:
"
+
value
);
//
将编码后的值存入
Cookie
中
Cookiecookie
=
new
Cookie
(
"username"
,
value
);
//
设置存活时间
,
1
周
7
天
cookie
.
setMaxAge
(
60
*
60
*
24
*
7
);
//2.
发送
Cookie
,
response
response
.
addCookie
(
cookie
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
(2)
在
BServlet
中获取值,并对值进行解码
@WebServlet
(
"/bServlet"
)
publicclass
BServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
获取
Cookie
//1.
获取
Cookie
数组
Cookie
[]
cookies
=
request
.
getCookies
();
//2.
遍历数组
for
(
Cookiecookie
:
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
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
至此,我们就可以将中文存入
Cookie
中进行使用。
小结
Cookie
的使用细节中,我们讲了
Cookie
的
存活时间
和
存储中文
:
- 存活时间,需要掌握setMaxAage()API的使用
- 存储中文,需要掌握URL编码和解码的使用
三、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
对象提供的功能
:
void setAttribute(String name, Object o)
Object getAttribute(String name)
void removeAttribute(String name)
介绍完
Session
相关的
API
后,接下来通过一个案例来完成对
Session
的使用,具体实现步骤为
:
需求
:
在一个
Servlet
中往
Session
中存入数据,在另一个
Servlet
中获取
Session
中存入的数据
1.
创建名为
SessionDemo1
的
Servlet
类
@WebServlet
(
"/demo1"
)
publicclass
SessionDemo1
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
2.
创建名为
SessionDemo2
的
Servlet
类
@WebServlet
(
"/demo2"
)
publicclass
SessionDemo2
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
3.
在
SessionDemo1
的方法中
:
获取
Session
对象、存储数据
@WebServlet
(
"/demo1"
)
publicclass
SessionDemo1
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
存储到
Session
中
//1.
获取
Session
对象
HttpSessionsession
=
request
.
getSession
();
//2.
存储数据
session
.
setAttribute
(
"username"
,
"zs"
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
4.
在
SessionDemo2
的方法中
:
获取
Session
对象、获取数据
@WebServlet
(
"/demo2"
)
publicclass
SessionDemo2
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
获取数据,从
session
中
//1.
获取
Session
对象
HttpSessionsession
=
request
.
getSession
();
//2.
获取数据
Object
username
=
session
.
getAttribute
(
"username"
);
System
.
out
.
println
(
username
);
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
5.
启动测试
(1)
创建名为
SessionDemo1
的
Servlet
类
(2)
创建名为
SessionDemo2
的
Servlet
类
(3)SessionDemo1:
获取
Session
对象、存储数据
(4)SessionDemo2:
获取
Session
对象、获取数据
(5)
启动测试,
- 先访问http://localhost:8080/cookie-demo/demo1 ,将数据存入Session
- 在访问http://localhost:8080/cookie-demo/demo2 ,从Session中获取数据
- 查看控制台
通过案例的效果,能看到
Session
是能够在一次会话中两次请求之间共享数据。
小结
至此
Session
的基本使用就已经完成了,重点要掌握的是
:
HttpSession session = request.getSession();
void setAttribute(String name, Object o)
Object getAttribute(String name)
注意
:
Session
中可以存储的是一个
Object
类型的数据,也就是说
Session
中可以存储任意数据
类型。
介绍完
Session
的基本使用之后,那么
Session
的底层到底是如何实现一次会话两次请求之间的数据
共享呢
?
3.2 Session的原理分析
这句话其实不太能详细的说明
Session
的底层实现,接下来,咱们一步步来分析下
Session
的具体实
现原理
:
(1)
前提条件
Session
要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取
Session
的对象是
同一个。
那么它们是一个对象么?要验证这个结论也很简单,只需要在上面案例中的两个
Servlet
中分别打印
下
Session
对象
SessionDemo1
@WebServlet
(
"/demo1"
)
public class
SessionDemo1
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
存储到
Session
中
//1.
获取
Session
对象
HttpSessionsession
=
request
.
getSession
();
System
.
out
.
println
(
session
);
//2.
存储数据
session
.
setAttribute
(
"username"
,
"zs"
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
this
.
doGet
(
request
,
response
);
SessionDemo2
@WebServlet
(
"/demo2"
)
publicclass
SessionDemo2
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
获取数据,从
session
中
//1.
获取
Session
对象
HttpSessionsession
=
request
.
getSession
();
System
.
out
.
println
(
session
);
//2.
获取数据
Object
username
=
session
.
getAttribute
(
"username"
);
System
.
out
.
println
(
username
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
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
的原理,我们只需要记住
3.3 Session的使用细节
节我们会主要讲解两个知识,第一个是
Session
的钝化和活化,第二个是
Session
的销毁,首先来
学习什么是
Session
的钝化和活化?
1.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是存储在客户端,是可以长期保存
2.Session销毁
session
的销毁会有两种方式
:
对于这个失效时间,是可以通过配置进行修改的
<?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"
)
publicclass
SessionDemo2
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequestrequest
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//
获取数据,从
session
中
//1.
获取
Session
对象
HttpSessionsession
=
request
.
getSession
();
System
.
out
.
println
(
session
);
//
销毁
session
.
invalidate
();
//2.
获取数据
Object
username
=
session
.
getAttribute
(
"username"
);
System
.
out
.
println
(
username
);
}
@Override
protected
void
doPost
(
HttpServletRequestrequest
,
HttpServletResponseresponse
)
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.1 需求分析
需求说明:
1.
完成用户登录功能,如果用户勾选
“
记住用户
”
,则下次访问登录页面
自动
填充用户名密码
2.
完成注册功能,并实现
验证码
功能
4.2 用户登录功能
1. 需求:
- 用户登录成功后,跳转到列表页面,并在页面上展示当前登录的用户名称
- 用户登录失败后,跳转回登录页面,并在页面上展示对应的错误信息
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
携带到列表页面。
3.
具体实现
(1)
完成
Dao
层的代码编写
(1.1)com.itheima.mapper`包下
:
publicinterface
UserMapper
{
/**
*
根据用户名和密码查询用户对象
*@paramusername
*@parampassword
*@return
*/
@Select
(
"select*fromtb_userwhereusername=#{username}andpassword =#{password}"
Userselect
(
@Param
(
"username"
)
String
username
,
@Param
(
"password"
)
String
password
);
/**
*
根据用户名查询用户对象
*@paramusername
*@return
*/
@Select
(
"select*fromtb_userwhereusername=#{username}"
)
UserselectByUsername
(
String
username
);
/**
*
添加用户
*@paramuser
*/
@Insert
(
"insertintotb_uservalues(null,#{username},#{password})"
)
void
add
(
Useruser
);
}
(1.2)
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)
UserMapper.xml
放入到 resources/com/itheima/mapper`目录下
:
<?xmlversion="1.0"encoding="UTF-8"?>
PUBLIC"-//mybatis.org//DTDMapper3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace
=
"com.itheima.mapper.UserMapper"
>
(2)
完成
Service
层的代码编写
(2.1)
在
com.itheima.service
包下,创建
UserService
类
publicclass
UserService
{
//1.
使用工具类获取
SqlSessionFactory
SqlSessionFactoryfactory
=
SqlSessionFactoryUtils
.
getSqlSessionFactory
();
/**
*
登录方法
*@paramusername
*@parampassword
*@return
*/
public
Userlogin
(
String
username
,
String
password
){
//2.
获取
SqlSession
SqlSessionsqlSession
=
factory
.
openSession
();
//3.
获取
UserMapper
UserMappermapper
=
sqlSession
.
getMapper
(
UserMapper
.
class
);
//4.
调用方法
Useruser
=
mapper
.
select
(
username
,
password
);
//
释放资源
sqlSession
.
close
();
return
user
;
}
}
(3)
完成页面和
Web
层的代码编写
(3.1)
将
静态页面
拷贝到项目的
webapp
目录下
:
(3.2)
将
login.html
内容修改成
login.jsp
<%
@
pagecontentType
=
"text/html;charset=UTF-8"
language
=
"java"
%>
lang
=
"en"
>
(3.4)
在
brand.jsp
中标签下添加欢迎当前用户的提示信息
:
(3.5)
修改
login.jsp
,将错误信息使用
EL
表达式来获取
(4)
启动,访问测试
(4.1)
进入登录页面,输入错误的用户名或密码
(4.2)
输入正确的用户和密码信息
小结
- 在LoginServlet中,将登录成功的用户数据存入session中,方法在列表页面中获取当前登录 用户信息进行展示
- 在LoginServlet中,将登录失败的错误信息存入到request中,如果存入到session中就会出
现这次会话的所有请求都有登录失败的错误信息,这个是不需要的,所以不用存入到
session
中
4.3 记住我-设置Cookie
1.
需求
:
如果用户勾选
“
记住用户
”
,则下次访问登陆页面自动填充用户名密码。这样可以提升用户的体验。
对应上面这个需求,最大的问题就是
:
如何自动填充用户名和密码
?
2.
实现流程分析
因为
记住我
功能要实现的效果是,就算用户把浏览器关闭过几天再来访问也能自动填充,所以需要将 登陆信息存入一个可以长久保存,并且能够在浏览器关闭重新启动后依然有效的地方,就是我们前面 讲的
Cookie
,
所以
:
- 将用户名和密码写入Cookie中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
- 在页面获取Cookie数据后,设置到用户名和密码框中
- 何时写入Cookie?
用户必须登陆成功后才需要写
用户必须在登录页面勾选了
记住我
的复选框
(1)
前端需要在发送请求和数据的时候,多携带一个用户是否勾选
Remember
的数据
(2)LoginServlet
获取到数据后,调用
Service
完成用户名和密码的判定
(3)
登录成功,并且用户在前端勾选了
记住我
,需要往
Cookie
中写入用户名和密码的数据,并设置
Cookie
存活时间
(4)
设置成功后,将数据响应给前端
3.
具体实现
(1)
在
login.jsp
为复选框设置值
(2)
在
LoginServlet
获取复选框的值并在登录成功后进行设置
Cookie
(3)
启动访问测试,
只有当前用户名和密码输入正确,并且勾选了
Remeber
的复选框,在响应头中才可以看得
cookie
的相 关数据
4.4 记住我-获取Cookie
1.
需求
登录成功并勾选了
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
页面
4.
访问测试,重新访问登录页面,就可以看得用户和密码已经被填充。
4.5 用户注册功能
1.
需求
展示验证码:展示验证码图片,并可以点击切换
校验验证码:验证码填写不正确,则注册失败
2.
实现流程分析
(1)
前端通过表单发送请求和数据给
Web
层的
RegisterServlet
(2)
在
RegisterServlet
中接收请求和数据
[
用户名和密码
]
(3)RegisterServlet
接收到请求和数据后,调用
Service
层完成用户信息的保存
(4)
在
Service
层需要编写
UserService
类,在类中实现
register
方法,需要判断用户是否已经存
在,如果不存在,则完成用户数据的保存
(5)
在
UserMapper
接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户 信息方法
(6)
在
UserService
类中保存成功则返回
true
,失败则返回
false,
将数据返回给
Web
层
(7)Web
层获取到结果后,如果返回的是
true,
则提示
注册成功
,并转发到登录页面,如果返回
false
则提示
用户名已存在
并转发到注册页面
3.
具体实现
(1)Dao
层代码参考资料中的内容完成
(2)
编写
Service
层代码
(3)
完成页面和
Web
层的代码编写
(3.1)
将
register.html
内容修改成
register.jsp
(3.2)
编写
RegisterServlet
(3.3)
需要在页面上展示后台返回的错误信息,需要修改
register.jsp
(3.4)
如果注册成功,需要把成功信息展示在登录页面,所以也需要修改
login.jsp
(3.5)
修改
login.jsp
,将注册跳转地址修改为
register.jsp
(3.6)
启动测试,
如果是注册的用户信息已经存在
:
4.6 验证码-展示
1.
需求分析
展示验证码:展示验证码图片,并可以点击切换
验证码的生成是通过工具类来实现的,具体的工具类参考
1.
登录注册案例
\CheckCodeUtil.java
在该工具类中编写
main
方法进行测试
:
生成完验证码以后,我们就可以知晓
:
验证码就是使用
Java
代码生成的一张图片
验证码的作用
:
防止机器自动注册,攻击服务器
2.
实现流程分析
(1)
前端发送请求给
CheckCodeServlet
(2)CheckCodeServlet
接收到请求后,生成验证码图片,将图片用
Reponse
对象的输出流写回到前
端
思考
:
如何将图片写回到前端浏览器呢
?
(1)Java
中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上
(2)
生成磁盘的过程中
使用的是
OutputStream
流,如何把这个图片生成在页面呢
? (3)
前面在将
Reponse
对象的时候,它
有一个方法可以获取其字节输出流,
getOutputStream() (4)
综上所述,我们可以把写往磁盘的流
对象更好成
Response
的字节流,即可完成图片响应给前端
3.
具体实现
(1)
修改
Register.jsp
页面,将验证码的图片从后台获取
(2)
编写
CheckCodeServlet
类,用来接收请求生成验证码
4.7验证码-校验
1.
需求
- 判断程序生成的验证码 和 用户输入的验证码 是否一样,如果不一样,则阻止注册
- 验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入Session中
思考
:
为什么要把验证码数据存入到
Session
中呢
?
- 生成验证码和校验验证码是两次请求,此处就需要在一个会话的两次请求之间共享数据
- 验证码属于安全数据类的,所以我们选中Session来存储验证码数据。
2.
实现流程分析
(1)
在
CheckCodeServlet
中生成验证码的时候,将验证码数据存入
Session
对象
(2)
前端将验证码和注册数据提交到后台,交给
RegisterServlet
类
(3)RegisterServlet
类接收到请求和数据后,其中就有验证码,和
Session
中的验证码进行对比
(4)
如果一致,则完成注册,如果不一致,则提示错误信息
3.
具体实现
(1)
修改
CheckCodeServlet
类,将验证码存入
Session
对象
(2)
在
RegisterServlet
中,获取页面的和
session
对象中的验证码,进行对比