会话技术--cookie和session

一、会话跟踪技术的概述

对于 会话跟踪 这四个词,我们需要拆开来进行解释,首先要理解什么是 会话 ,然后再去理解什么是 会 话跟踪:
  • 会话:用户打开浏览器,访问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,分别是ServletAServletB
  • 浏览器发送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 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,BServletrequest中获取 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 ,
  • 设置Cookie存活时间
setMaxAge(int seconds)
参数值为 :
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不能直接存储中文
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 对象提供的功能 :
  • 存储数据到 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
@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 的基本使用就已经完成了,重点要掌握的是 :
  • 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 ( 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对象是同一个
  • demo1demo2请求刷新多次,控制台最终打印的结果都是同一个
那么问题又来了,如果新开一个浏览器,访问 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 的钝化和活化?

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是存储在客户端,是可以长期保存

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分钟,默认值是在Tomcatweb.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" >
</mapper>
(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.3) 创建 LoginServlet
(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 对象中的验证码,进行对比
至此,用户的注册登录功能就已经完成了。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值