session对象
-
在web开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
-
session和cookie的主要区别在于:cookie是把用户的数据写给用户的浏览器(保存在客户机);session技术把用户的数据写到用户独占的session中(保存在服务器)。
-
session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。
session实现原理
-
浏览器A第一次访问Servlet1,服务器会创建一个session,每个session都有一个id号,创建好了后,服务器将id号以cookie的形式回送给客户机(这些是服务器自动完成的)。
-
当浏览器未关闭前再次发请求访问Servlet2时,就会带着这个id号去访问服务器,这时候服务器检索下内存中有没有与之对应的session,有就用这个session为其服务。
-
如果想要关掉浏览器再打开还可以使用同一个session,则需要给服务器回送的cookie设置有效时间(服务器自动回送的时候是没有有效期的)。具体做法是通过session对象的getId方法获得该session的id,然后创建一个cookie,该cookie的名字为"JSESSIONID",值就是刚刚获得的id,再将该cookie设置下有效期,(也可以设置下Path),并添加到cookie中即可。但是有效期不得超过30分钟,因为浏览器关掉后,session只保存30分钟。
案例1
要求:通过三个servlet来实现简单的购物功能。
-
创建一个保存书籍信息的类,以及数据类型
public class Book { private String id; private String name; private String author; private String description; public Book() { } public Book(String id, String name, String author, String description) { this.id = id; this.name = name; this.author = author; this.description = description; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
public class Dao { private static Map<String, Book> map = new LinkedHashMap(); // 静态代码块中的内容只执行一次,该类在加载时,往map集合中put一系列书,map也需要设置为静态的 static { map.put("1", new Book("1", "JavaWeb开发", "老张", "一本好书")); map.put("2", new Book("2", "Spring开发", "老倪", "一本好书")); map.put("3", new Book("3", "Hibernate开发", "老童", "一本好书")); map.put("4", new Book("4", "Struts开发", "老毕", "一本好书")); map.put("5", new Book("5", "Ajax开发", "老张", "一本好书")); map.put("6", new Book("6", "Java基础", "老孙", "一本好书")); } public static Map getAll() { return map; } }
-
IndexServlet显示首页,并列出所有书
@WebServlet("/indexServlet") public class IndexServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应的消息体的数据格式以及编码 response.setContentType("text/html;charset=UTF-8"); // 定义 response.getWriter() 对象 PrintWriter out = response.getWriter(); out.write("本网站有如下书:<br/>"); // 获取每一本书对应的键值 Set<Map.Entry<String,Book>> keySet = Dao.getAll().keySet(); for (Map.Entry<String, Book> stringBookEntry : keySet) { Book book = stringBookEntry.getValue(); // 定义标签体内容,指定购买的书:用户点击购买,指定给BuyServlet类去处理 out.write( book.getName() + "<a href='/SessionTest_war_exploded/buyServlet?id=" + book.getId() + "'>购买</a><br/>" ); System.out.println(book.getName() + ":" + book.getId()); } } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
当用户点击购买时,将书的id号带上,并跳转到BuyServlet去处理
@WebServlet("/buyServlet") public class BuyServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获得url中带过来的参数id String id = request.getParameter("id"); // 在Dao中获得该id号的book Book book = (Book)Dao.getAll().get(id); // 获得当前session对象 HttpSession session = request.getSession(); // 设置新的cookie,注意cookie名必须为JSESSIONID,值为该session的id Cookie cookie = new Cookie("JSESSIONID", session.getId()); // 设置cookie的有效期 cookie.setMaxAge(30 * 60); // 设置cookie的路径 cookie.setPath("/SessionTest_war_exploded"); // 将cookie添加到cookies中带给浏览器,下次浏览器访问,就会将此cookie带过来了 response.addCookie(cookie); // 先把书加到容器里,再把容器加到session中。一般先检查用户的session中有没有保存书的容器,没有就创建,有就加 List list = (List)session.getAttribute("list"); if(list == null) { list = new ArrayList(); session.setAttribute("list", list); } list.add(book); // 跳转到显示用户买过哪些商品 response.sendRedirect("/SessionTest_war_exploded/listCartServlet"); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
从上面的程序可以看出,当用户点击购买后,会将书的id号带过来,我们拿到id号后就可以找到相应的书,同时我们将当前session的id保存到cookie中,再带给浏览器,这样下次浏览器访问的时候就会将当前session的id带过来了。拿到相应的书后,放到list中,再把list放到session中,这样下次跳转的时候,浏览器带来的cookie中有当前session的id,我们可以通过getSession()获得当前的session,再把session中保存的list拿出来,就知道用户买了哪些书了。这就是购物车的原理。
@WebServlet("/listCartServlet") public class ListCartServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应的消息体的数据格式以及编码 response.setContentType("text/html;charset=UTF-8"); // 定义 response.getWriter() 对象 PrintWriter out = response.getWriter(); // 获得当前的session HttpSession session = request.getSession(); // 从session中拿出list List<Book> list = (List)session.getAttribute("list"); // 判断用户是否购买过书籍 if(list == null || list.size() == 0) { out.write("对不起,您还没有购买任何商品!"); return; } out.write("您买过如下商品:<br/>"); for(Book book : list) { out.write(book.getName() + "<br/>"); } } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
启动服务器,浏览器访问:http://localhost:8080/SessionTest_war_exploded/indexServlet
-
没有购买过书籍,访问:http://localhost:8080/SessionTest_war_exploded/buyServlet
-
由于session是保存在服务器端的,cookie是保存在浏览器端的。这里的session和cookie只是保存在内存中,并没有因为设置cookie的保存时间问题二长期存储到硬盘中,如果关闭某一端,购买的物品都会失效。(不知是否正确,待证明)
细节说明
-
当客户端关闭后,服务器不关闭,两次获取session,默认情况下不是同一个session。如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
Cookie cookie = new Cookie("JSESSIONID",session.getId()); cookie.setMaxAge(60 * 60); response.addCookie(c);
-
客户端不关闭,服务器关闭后,两次获取的session,不是同一个,但是要确保数据不丢失:
-
session的钝化:
在服务器正常关闭之前,将session对象系列化到硬盘上。
-
session的活化:
在服务器启动后,将session文件转化为内存中的session对象即可。
-
IDEA 不能实现session的活化:
- 从tomcat的文件目录中可以知道,word 目录是用来存储程序运行中动态生成的数据(JSP转换的Java文件、Session被序列化之后的文件 ...... ) 。
- 将项目打包成wer包,放到tomcat中,在终端中运行tomcat服务器,之后正常关闭tomcat服务器,会在
word/Catalina/locathout/项目名称/
目录中生成SESSIONS.ser
文件, 该文件放的就是session对象,重新启动服务器,再次访问该项目,该SESSIONS.ser文件就会被自动读取进内容,且会将SESSIONS.ser文件从硬盘中删除 。 - IDEA 虽然可以进行钝化,但是不能进行活化。在IDEA中启动tomcat,找到对应该项目的目录,可以发现会生成一个word目录,这里和终端中启动tomcat,也是一样的(即生成一个word目录)。同理后面在IDEA中关闭tomcat,对应的项目目录下的
word/Catalina/locathout/项目名称/
目录也会生成SESSIONS.ser
文件。不过再次从IDEA中启动tomcat服务器,那么这里就不一样了,之前word目录会被删除,然后创建一个新的word目录。那之前生成的SESSIONS.ser
文件也会被删除,就不能读取到那个SESSIONS.ser
文件了,也就不能实现session的活化了。
-
-
Session的失效时间
-
服务器关闭
-
session对象调用invalidate()
-
session默认失效时间 30分钟,也可以修改对应的web.xml文件中的如下内容,来自定义失效时间:
<session-config> <session-timeout>30</session-timeout> </session-config>
-
-
Session的特点
-
session用于存储一次会话的多次请求的数据,存在服务器端。
-
session可以存储任意类型,任意大小的数据。
-
-
session与Cookie的区别
- session存储数据在服务器端,Cookie在客户端
- session没有数据大小限制,Cookie有大小限制
- session数据安全,Cookie相对于不安全
案例2
案例需求
- 访问带有验证码的登录页面login.jsp
- 用户输入用户名,密码以及验证码。
- 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
- 如果验证码输入有误,跳转登录页面,提示:验证码错误
- 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
案例分析
- 用户访问login.jsp登录页面,输入用户名、密码和验证码,点击登录
- 用户点击登录后,将请求提交给
LoginServlet.java
处理- 设置request编码
- 获取参数Map集合
- 获取验证码
- 将用户信息封装到User对象
- 判断Java程序生成的验证码和用户输入的验证码是否一致
- 从session中获取Java程序生成的验证码。
- 如果生成的验证码和用户输入的验证码一致,再判断用户名和密码是否正确
- 如果用户名和密码正确,用户登录成功,页面跳转到success.jsp,存储用户登录的数据(采用重定向)。
- 如果用户名和密码不正确,login.jsp页面给用户提示信息:用户名或密码错误。
- 如果生成的验证码和用户输入的验证码不一致,login.jsp页面给用户提示信息,验证码输入错误,并刷新验证码
- 查询用户名和密码是否正确的时候,将从
LoginServlet
中获取到的用户名和密码与UserDao.java
中存储到的用户名和密码对比,判断是否正确。
案例的实现
-
验证码的实现:CheckCodeServlet.java
@WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 定义验证码方框长和宽 int width = 100, height = 50; // 创建一个对象,在内存中画图(验证码图片对象) BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); // 美化图片 // 创建画笔对象 Graphics graphics = bufferedImage.getGraphics(); // 设置画笔的颜色 graphics.setColor(Color.PINK); // 用画笔,将制定的长方形区域,画满(画验证码图片背景 -> 粉红色) graphics.fillRect(0, 0, width, height); // 画验证码图片边框 // 设置画笔的颜色 graphics.setColor(Color.BLACK); graphics.drawRect(0, 0, width - 1, height - 1); // 定义一个包含所有字母和数字的字符串(验证码) String strings = "QqWwEeRrTtYyUuIiOoPpAaSsDdFfGgHhJjKkLlZzXxCcVvBbNnMm1234567890"; // 创建随机数对象,用来获取字符串中的一个字符 Random random = new Random(); // 写入四个字符在验证码方框中 int codeNumber = 4; // 创建字符缓冲区对象,存储验证码 StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i <= codeNumber; i++) { // strings 索引 int index = random.nextInt(strings.length()); // 通过索引获取字符 char indexString = strings.charAt(index); // 将一个验证码字符存储到字符缓冲区 stringBuilder.append(indexString); // 写入一个验证符 graphics.drawString(indexString + "", width / 5 * i, height / 2); } // 验证码 String checkCodeSession = stringBuilder.toString(); // 将验证码存入Session request.getSession().setAttribute("checkCodeSession", checkCodeSession); // 在验证码方框中画干扰线 graphics.setColor(Color.GREEN); int lineNumber = 10; for (int i = 0; i < lineNumber; i++) { // 生成随机坐标点 int x1 = random.nextInt(width); int x2 = random.nextInt(width); int y1 = random.nextInt(height); int y2 = random.nextInt(height); // 画线 graphics.drawLine(x1, y1, x2, y2); } // 将图片输出到页面展示 ImageIO.write(bufferedImage, "png", response.getOutputStream()); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
创建并编写一个登录页面:Login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Login</title> <script type="text/javascript" src="LoginScript.js"></script> <style> div { color: pink } </style> </head> <body> <!-- 创建一个表单,指向 LoginServlet.java --> <form action="/SessionMaster_war_exploded/loginServlet" method="post"> <table> <tr> <td>用户名</td> <td><input type="text" name="username" placeholder="请输入用户名"></td> </tr> <tr> <td>密码</td> <td><input type="password" name="password" placeholder="请输入密码"></td> </tr> <tr> <td>验证码</td> <td><input type="text" name="checkCode" placeholder="请输入验证码"></td> </tr> <!-- 验证码图片,指向CheckCodeServlet.java,该验证码图片占两个单元格 且绑定单击事件,交给LoginScript.js中的ClickImage()方法处理 --> <tr> <td colspan="2"><img id="clickCheckCodeImage" onclick="ClickImage()" src="/SessionMaster_war_exploded/checkCodeServlet"></td> </tr> <tr> <td><input type="submit" value="登录"></td> </tr> </table> </form> <%-- 获取错误信息 --%> <div><%= request.getAttribute("usernameOrPasswordError") == null ? "" : request.getAttribute("usernameOrPasswordError") %></div> <div><%= request.getAttribute("checkCodeError") == null ? "" : request.getAttribute("checkCodeError")%></div> </body> </html>
-
从上面的
Login.jsp
可以看出,验证码是由CheckCodeServlet.java
生成的,且绑定了单击事件,交给LoginScript.js
处理。表单提交后,交给LoginServlet.java
处理。-
编写
LoginScript.js
function ClickImage() { // 验证码,绑定鼠标单击事件,用户点击验证码图片,验证码自动更新 document.getElementById("clickCheckCodeImage").onclick = function code() { this.src = "/SessionMaster_war_exploded/checkCodeServlet?time="+new Date().getTime(); } }
-
编写
LoginServlet.java
@WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置request编码 request.setCharacterEncoding("utf-8"); // 获取参数,保存在Map集合中 Map<String, String[]> parameterMap = request.getParameterMap(); // 获取 Map 集合中的所有键值 Set<String> strings = parameterMap.keySet(); // 获取username、password、checkCode String username = null, password = null, checkCode = null; // 获取Set集合的迭代器对象,并遍历该迭代器对象 Iterator<String> ite = strings.iterator(); while (ite.hasNext()) { // 通过迭代器对象中的每一个值(Map集合中的键),获取value String next = ite.next(); if ("username".equals(next)) { username = parameterMap.get(next)[0]; } else if ("password".equals(next)) { password = parameterMap.get(next)[0]; } else if ("checkCode".equals(next)) { checkCode = parameterMap.get(next)[0]; } } // 通过session对象获取生成的验证码 HttpSession session = request.getSession(); String checkCodeSession = (String) session.getAttribute("checkCodeSession"); // 获取完验证码后,将原有的验证码从session中删除(保证验证码只能被使用一次) session.removeAttribute("checkCodeSession"); // 判断用户输入的验证码是否和生成的验证码一致(不区分大小写) if (checkCodeSession != null && checkCodeSession.equalsIgnoreCase(checkCode)) { // 验证码一致,判断用户名和密码是否正确 boolean flag = false; String[] userInfoArrayList = UserDao.getNamePassword(); for (String userInfo : userInfoArrayList) { if ((username + password).equals(userInfo)) { flag = true; } } if (flag == true) { // 用户名和密码正确 // 存储用户名信息到session session.setAttribute("username", username); // 重定向到 Success.jsp response.sendRedirect(request.getContextPath()+"/Success.jsp"); } else { // 用户名和密码不正确,存储提示信息到request request.setAttribute("usernameOrPasswordError", "用户名或密码错误,请检查后修改!"); // 将该信息转发到登录页面 request.getRequestDispatcher("/Login.jsp").forward(request, response); } } else { if (checkCodeSession == null) { // 验证码只能使用一次,假如登陆成功后,返回前一个页面,验证码图片是没有更新的。 // 要保证验证码只能使用一次。存储提示信息到request request.setAttribute("checkCodeError", "验证码已经失效,请刷新验证码!"); } else { // 验证码不一致,存储提示信息到request request.setAttribute("checkCodeError", "验证码输入错误,请重新输入验证码"); } // 将该信息转发到登录页面 request.getRequestDispatcher("/Login.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
从
LoginServlet.java
可以知道,该类中的判断使用了UserDao.java
中的信息。如果用户登录成功,页面将挑战到Success.jsp
。
-
-
编写
UserDao.java
类-
编写自定义用户信息数据类型:
UserInfo.java
public class UserInfo { private String username; private String password; private String gender; public UserInfo() { } public UserInfo(String username, String password, String gender) { this.username = username; this.password = password; this.gender = gender; } 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; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
-
编写
UserDao.java
类public class UserDao { private static Map<Integer, UserInfo> userInfoArrayList = new LinkedHashMap(); static { userInfoArrayList.put(0, new UserInfo("LeeHua", "2020520", "male")); userInfoArrayList.put(1, new UserInfo("Rainbow", "20181314", "female")); } public static String[] getNamePassword() { String[] namesAndPasswords = new String[2]; // 获取 Map 集合中的所有键值 Set<Integer> strings = userInfoArrayList.keySet(); // 获取Set集合的迭代器对象,并遍历该迭代器对象 Iterator<Integer> ite = strings.iterator(); while (ite.hasNext()) { // 通过迭代器对象中的每一个值(Map集合中的键),获取value Integer next = ite.next(); String username = userInfoArrayList.get(next).getUsername(); String password = userInfoArrayList.get(next).getPassword(); namesAndPasswords[next] = username + password; } return namesAndPasswords; } }
-
-
该案例的代码部分已经基本完成,下面进行测试。
案例测试
-
启动浏览器,访问:http://localhost:8080/SessionMaster_war_exploded/Login.jsp
-
用户名和密码信息,在
UserDao.java
类中,如下:UserInfo("LeeHua", "2020520", "male") UserInfo("Rainbow", "20181314", "female")
-
输入正确的用户名和验证码:如LeeHua、2020520
页面跳转到:http://localhost:8080/SessionMaster_war_exploded/Success.jsp
-
返回前一个页面,会发现,验证码是没有发生改变的,假如没有点击验证码图片来刷新验证码,而继续选择登录,无论用户名或者密码是否正确,都会出现以下情况:
-
页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet
-
页面显示内容如下:
-
-
无论用户名是否正确,都会先判断验证码的正确性,验证码正确的情况下才会继续判断用户名和密码是否正确。
-
验证码错误的情况:
-
输入:
-
页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet
-
页面显示情况:
-
-
密码或用户名错误的情况:
-
输入:
-
页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet
-
页面显示情况:
-