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中标签下添加欢迎当前用户的提示信息:
${user.username},欢迎您
(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的相关数据