• Session 对象学习笔记


    session对象

    1. 在web开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。

    2. session和cookie的主要区别在于:cookie是把用户的数据写给用户的浏览器(保存在客户机);session技术把用户的数据写到用户独占的session中(保存在服务器)。

    3. session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

    session实现原理

    1. 浏览器A第一次访问Servlet1,服务器会创建一个session,每个session都有一个id号,创建好了后,服务器将id号以cookie的形式回送给客户机(这些是服务器自动完成的)。

    2. 当浏览器未关闭前再次发请求访问Servlet2时,就会带着这个id号去访问服务器,这时候服务器检索下内存中有没有与之对应的session,有就用这个session为其服务。

    3. 如果想要关掉浏览器再打开还可以使用同一个session,则需要给服务器回送的cookie设置有效时间(服务器自动回送的时候是没有有效期的)。具体做法是通过session对象的getId方法获得该session的id,然后创建一个cookie,该cookie的名字为"JSESSIONID",值就是刚刚获得的id,再将该cookie设置下有效期,(也可以设置下Path),并添加到cookie中即可。但是有效期不得超过30分钟,因为浏览器关掉后,session只保存30分钟。


    案例1

    要求:通过三个servlet来实现简单的购物功能。

    1. 创建一个保存书籍信息的类,以及数据类型

      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;
          }
      
      }
      
    2. 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);
          }
      }
      
    3. 当用户点击购买时,将书的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);
          }
      }
      
    4. 从上面的程序可以看出,当用户点击购买后,会将书的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);
          }
      }
      
    5. 启动服务器,浏览器访问:http://localhost:8080/SessionTest_war_exploded/indexServlet

      20200602155253
    6. 没有购买过书籍,访问:http://localhost:8080/SessionTest_war_exploded/buyServlet

      20200602155457
    7. 点击购买一本书,页面跳转到:http://localhost:8080/SessionTest_war_exploded/indexServlet/buyServlet,然后跳转到:http://localhost:8080/SessionTest_war_exploded/listCartServlet

      20200602155723
    8. 由于session是保存在服务器端的,cookie是保存在浏览器端的。这里的session和cookie只是保存在内存中,并没有因为设置cookie的保存时间问题二长期存储到硬盘中,如果关闭某一端,购买的物品都会失效。(不知是否正确,待证明)

    细节说明

    1. 当客户端关闭后,服务器不关闭,两次获取session,默认情况下不是同一个session。如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。

      Cookie cookie = new Cookie("JSESSIONID",session.getId());
      cookie.setMaxAge(60 * 60);
      response.addCookie(c);
      

    2. 客户端不关闭,服务器关闭后,两次获取的session,不是同一个,但是要确保数据不丢失:

      1. session的钝化:

        在服务器正常关闭之前,将session对象系列化到硬盘上。

      2. session的活化:

        在服务器启动后,将session文件转化为内存中的session对象即可。

      3. 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的活化了。
    3. Session的失效时间

      • 服务器关闭

      • session对象调用invalidate()

      • session默认失效时间 30分钟,也可以修改对应的web.xml文件中的如下内容,来自定义失效时间:

        <session-config>
            <session-timeout>30</session-timeout>
        </session-config>
        

    4. Session的特点

      1. session用于存储一次会话的多次请求的数据,存在服务器端。

      2. session可以存储任意类型,任意大小的数据。

    5. session与Cookie的区别

      1. session存储数据在服务器端,Cookie在客户端
      2. session没有数据大小限制,Cookie有大小限制
      3. session数据安全,Cookie相对于不安全

    案例2

    案例需求

    1. 访问带有验证码的登录页面login.jsp
    2. 用户输入用户名,密码以及验证码。
      • 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
      • 如果验证码输入有误,跳转登录页面,提示:验证码错误
      • 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您

    案例分析

    1. 用户访问login.jsp登录页面,输入用户名、密码和验证码,点击登录
    2. 用户点击登录后,将请求提交给 LoginServlet.java 处理
      1. 设置request编码
      2. 获取参数Map集合
      3. 获取验证码
      4. 将用户信息封装到User对象
      5. 判断Java程序生成的验证码和用户输入的验证码是否一致
        1. 从session中获取Java程序生成的验证码。
        2. 如果生成的验证码和用户输入的验证码一致,再判断用户名和密码是否正确
          1. 如果用户名和密码正确,用户登录成功,页面跳转到success.jsp,存储用户登录的数据(采用重定向)。
          2. 如果用户名和密码不正确,login.jsp页面给用户提示信息:用户名或密码错误。
        3. 如果生成的验证码和用户输入的验证码不一致,login.jsp页面给用户提示信息,验证码输入错误,并刷新验证码
    3. 查询用户名和密码是否正确的时候,将从 LoginServlet 中获取到的用户名和密码与 UserDao.java 中存储到的用户名和密码对比,判断是否正确。

    案例的实现

    1. 验证码的实现: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);
          }
      }
      
    2. 创建并编写一个登录页面: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>
      
    3. 从上面的 Login.jsp 可以看出,验证码是由 CheckCodeServlet.java 生成的,且绑定了单击事件,交给 LoginScript.js 处理。表单提交后,交给 LoginServlet.java 处理。

      1. 编写 LoginScript.js

        function ClickImage() {
            // 验证码,绑定鼠标单击事件,用户点击验证码图片,验证码自动更新
            document.getElementById("clickCheckCodeImage").onclick = function code() {
                this.src = "/SessionMaster_war_exploded/checkCodeServlet?time="+new Date().getTime();
            }
        }
        
      2. 编写 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);
            }
        }
        
      3. LoginServlet.java 可以知道,该类中的判断使用了 UserDao.java 中的信息。如果用户登录成功,页面将挑战到 Success.jsp

    4. 编写 UserDao.java

      1. 编写自定义用户信息数据类型: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; }
        }
        
      2. 编写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;
            }
        
        }
        
    5. 该案例的代码部分已经基本完成,下面进行测试。

    案例测试

    1. 启动浏览器,访问:http://localhost:8080/SessionMaster_war_exploded/Login.jsp

      20200604022302
    2. 用户名和密码信息,在 UserDao.java 类中,如下:

      UserInfo("LeeHua", "2020520", "male")
      UserInfo("Rainbow", "20181314", "female")
      
    3. 输入正确的用户名和验证码:如LeeHua、2020520

      页面跳转到:http://localhost:8080/SessionMaster_war_exploded/Success.jsp

      20200604022625
    4. 返回前一个页面,会发现,验证码是没有发生改变的,假如没有点击验证码图片来刷新验证码,而继续选择登录,无论用户名或者密码是否正确,都会出现以下情况:

      1. 页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet

      2. 页面显示内容如下:

        20200604022935
    5. 无论用户名是否正确,都会先判断验证码的正确性,验证码正确的情况下才会继续判断用户名和密码是否正确。

    6. 验证码错误的情况:

      1. 输入:

        20200604023311
      2. 页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet

      3. 页面显示情况:

        20200604023431
    7. 密码或用户名错误的情况:

      1. 输入:

        20200604023646
      2. 页面跳转到:http://localhost:8080/SessionMaster_war_exploded/loginServlet

      3. 页面显示情况:

        20200604023707

    参考文献

    1. 会话管理之session技术
    Good Good Write Bug, Day Day Up
  • 相关阅读:
    Hibernate性能优化之EHCache缓存
    Hibernate性能优化之SessionFactory重用
    用 NetBeans 快速开发 Java JAX-RS RESTful 服务
    Linux iptables:场景实战一
    Linux iptables:规则组成
    Linux iptables:规则原理和基础
    Linux安全之SYN攻击原理及处理
    Linux常用网络工具:批量主机服务扫描之netcat
    Linux常用网络工具:批量主机服务扫描之nmap
    Linux常用网络工具:路由扫描之mtr
  • 原文地址:https://www.cnblogs.com/liyihua/p/14477509.html
Copyright © 2020-2023  润新知