Session

来源:互联网 发布:女生微信 知乎 编辑:程序博客网 时间:2024/04/28 03:54

Session

Session简单介绍

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

Session和Cookie的主要区别

  • Cookie是把用户的数据写给用户的浏览器。
  • Session技术把用户的数据写到用户独占的session中。
  • Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

Session实现原理

问题:服务器是如何实现一个session为一个用户浏览器服务的?
这里写图片描述
服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。
例,用户网上购物时,购买所需商品之后,再去结账,需要从session中取出该用户的所购买商品信息。用代码表示为:
购物网站首页——index.jsp。

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>    <a href="/day07/SessionDemo1">购买</a><br/>    <a href="/day07/SessionDemo2">结账</a></body></html>

购买Servlet——SessionDemo1。

// 购买public class SessionDemo1 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {         HttpSession session = request.getSession();         session.setAttribute("name", "洗衣机");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

结账Servlet——SessionDemo2。

// 结账public class SessionDemo2 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setCharacterEncoding("UTF-8");        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        HttpSession session = request.getSession();        String product =  (String) session.getAttribute("name");        out.print("您购买的商品是:"+product);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

当用户第一次点击购物超链接时,服务器会创建一个新的sesion,并且把session的Id以cookie的形式发送给客户端浏览器,如下图所示:
这里写图片描述
当用户回退过去点击结账超链接,再次请求服务器,此时就可以看到浏览器在请求服务器时,会把存储到cookie中的session的Id一起传递到服务器端了,如下图所示:
这里写图片描述
可发现session是基于cookie的,但是此cookie是没有设有效期的,把浏览器关了,session的id号就没有了,下次再访问服务器,服务器针对其创建一个新的session。
由此引出一个新的问题:如何实现多个IE浏览器共享同一session?——即关浏览器再开浏览器能拿到同一个session或者两个浏览器共享同一个session。
应用:关掉IE浏览器后,再开IE浏览器,上次购买的商品还在。
购买Servlet——SessionDemo1应修改为:

// 购买public class SessionDemo1 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {         HttpSession session = request.getSession();         String sessionId = session.getId();         Cookie cookie = new Cookie("JSESSIONID", sessionId);         cookie.setPath("/day07");         cookie.setMaxAge(30*60);         response.addCookie(cookie);         session.setAttribute("name", "洗衣机");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

由于此cookie调用setMaxAge()方法设置有效期为30分钟,所以会把session的id以cookie的形式存储到浏览器, 所以即使把浏览器关了,session的id号也没有消失,下次再访问服务器,服务器会根据id号找到对应的session为用户服务。
注意:request对象的getSession()方法还有一个重载方法:getSession(boolean)。例如:

request.getSession(false); 

意味着不创建session,只获取session。
问题:在什么情况下才会使用此代码?
答:显示购物车时会用到此代码。

session对象的生命周期

问题:用户开一个浏览器访问一个网站,服务器是不是针对这次会话创建一个session?
答:不是的。session的创建时机是在程序中第一次去执行request.getSession();这个代码,服务器才会为你创建session。
问题:关闭浏览器,会话结束,session是不是就销毁了呢?
答:不是的。session是30分钟没人用了才会死,服务器会自动摧毁session。

  • 在web.xml文件中可以手工配置session的失效时间。例如:

    <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">  <display-name>day07</display-name>  <!-- 设置Session的有效时间:以分钟为单位-->  <session-config>    <session-timeout>10</session-timeout>  </session-config>  <welcome-file-list>    <welcome-file>index.html</welcome-file>    <welcome-file>index.htm</welcome-file>    <welcome-file>index.jsp</welcome-file>    <welcome-file>default.html</welcome-file>    <welcome-file>default.htm</welcome-file>    <welcome-file>default.jsp</welcome-file>  </welcome-file-list></web-app>
  • 当需要在程序中手动设置Session失效时,可以手工调用session.invalidate方法,摧毁session。

    HttpSession session = request.getSession();// 手工调用session.invalidate方法,摧毁sessionsession.invalidate();

总结:开一个浏览器访问服务器,第一次去执行request.getSession();那个代码,服务器为你创建session。然后故意不关闭浏览器,跑去玩了1个小时,session也没了,即使会话没结束,服务器发现session没人用也会将其摧毁掉。

浏览器禁用Cookie后的session处理

如何在Firefox中启用和禁用Cookie?

我使用的火狐浏览器,关于如何在Firefox中启用和禁用Cookie?可参考启用和禁用Cookie。

解决方案:URL重写

禁用Cookie后,你访问服务器,都是通过点击超链接来访问,不以cookie的形式带session_id号过来,通过点击超链接的时候再带过来。
每个用户先访问首页,不同的用户在访问首页的时候,我给你回送首页超链接的时候,在每一个超链接的后面都跟上你的session_id号,也就是说一访问首页,我就帮你创建session,然后在每一个超链接的后面都跟上session_id号再打给你,这时你一点超链接,超链接屁股后面带着各自的session_id号来了,那就不以cookie的形式带session_id号过来嘛,带过来之后服务器端得到你URL带过来的session_id号去获取对应的session。

  • response. encodeRedirectURL(java.lang.String url)——用于对sendRedirect方法后的url地址进行重写。
  • response. encodeURL(java.lang.String url)——用于对表单action和超链接的url地址进行重写。

禁用Cookie后servlet共享Session中的数据。

代表网站首页的Servlet——WelcomeServlet。

public class WelcomeServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        // 帮用户创建session        request.getSession(); // 有这句代码就足够了,没必要创建session对象的引用        /*         * 第一次访问服务器,服务器不知道用户有没有禁用cookie,它会把session的id号以cookie的形式         * 回写,并且也会把url地址重写。第二次访问服务器,若用户没有禁用cookie,就会带cookie过来,         * 服务器发现你带cookie过来了,就不重写url地址了。         */        String url1 = response.encodeURL("/day07/SessionDemo1");        String url2 = response.encodeURL("/day07/SessionDemo2");        out.print("<a href='"+url1+"'>购买</a><br/>");        out.print("<a href='"+url2+"'>结账</a>");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

response对象的encodeURL方法是非常聪明的。用户第一次访问服务器,服务器不知道用户有没有禁用cookie,所以它会把session的id号以cookie的形式回写,并且也会把url地址重写。第二次访问服务器,若用户没有禁用cookie,就会带cookie过来,服务器发现你带cookie过来了,就不会重写url地址了。
购买Servlet——SessionDemo1。

// 购买public class SessionDemo1 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {         HttpSession session = request.getSession();         String sessionId = session.getId();         Cookie cookie = new Cookie("JSESSIONID", sessionId);         cookie.setPath("/day07");         cookie.setMaxAge(30*60);         response.addCookie(cookie);         session.setAttribute("name", "洗衣机");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

结账Servlet——SessionDemo2。

// 结账public class SessionDemo2 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setCharacterEncoding("UTF-8");        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        HttpSession session = request.getSession();        String product =  (String) session.getAttribute("name");        out.print("您购买的商品是:"+product);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

在禁用了cookie的Firefox下的运行效果如下:
首次访问首页。
这里写图片描述
右键→然后点击查看页面源代码。
这里写图片描述
通过查看WelcomeServlet生成的html代码可以看到,每一个超链接后面都带上了session的Id。
然后再打开一个新的浏览器访问首页→右键→点击查看页面源代码。
这里写图片描述
可发现,每次打开一个新的浏览器访问服务器,服务器会为用户创建一个新的session。所以,当浏览器禁用了cookie后,就可以用URL重写这种解决方案解决Session数据共享问题。

session的一些细节问题

IE8及以上浏览器,开2个浏览器访问服务器,默认共享同一个session。内部原理:多个浏览器共享同一个sessionid。

session案例

使用Session完成简单的购物功能

注意:在实际开发中,应使用Cookie完成购物的功能。但是这是一个session案列,我们使用session来完成,假设我们购买的商品是书。我们可参照使用Cookie进行会话管理—— 显示用户上次浏览过的商品的设计思路来设计一个代表图书的JavaBean——Book.java和一个代表数据库的类——Db.java。
创建代表网站首页的Servlet——ListBookServlet,列出所有书。

public class ListBookServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        out.print("本网站有如下商品:<br/>");        Map<String, Book> map = Db.getAll();        for (Map.Entry<String, Book> entry : map.entrySet()) {            Book book = entry.getValue();            String url = "/day07/BuyServlet?id="+book.getId();            out.print(book.getName() + "<a href='"+url+"' target='_blank'>购买</a><br/>");        }    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}// 代表数据库class Db {    /*     * 在java里面,有两种类型的容器,一种是单列的容器(诸如List集合) 一种是双列的容器(诸如map集合)     * 要保存网站所有商品,是使用单列的容器还是使用双列的容器?——使用双列的容器     * 在实际开发时,有一个原则:有没有检索数据的需求。如果有检索数据的需求,则通通用双列的容器。 如果没有检索数据的需求,则通通用单列的容器。     */    // 希望存进去的顺序和取出来的顺序一致,就不要用HashMap了,应该用LinkedHashMap——带链表的数据结构,可保证有序    private static Map<String, Book> map = new LinkedHashMap();    // 希望Db这个类一初始化时,就向map初始化一些书    static {        map.put("1", new Book("1", "javaweb开发", "老张", "一本好书!!"));        map.put("2", new Book("2", "jdbc开发", "老张", "一本好书!!"));        map.put("3", new Book("3", "spring开发", "老黎", "一本好书!!"));        map.put("4", new Book("4", "struts开发", "老毕", "一本好书!!"));        map.put("5", new Book("5", "android开发", "老黎", "一本好书!!"));    }    public static Map getAll() {        return map;    }}class Book {    private String id;    private String name;    private String author;    private String description;    public Book() {        super();    }    public Book(String id, String name, String author, String description) {        super();        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;    }}

创建完成购买功能的Servlet——BuyServlet。

public class BuyServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String id = request.getParameter("id");        Book book = (Book) Db.getAll().get(id);        HttpSession session = request.getSession();        // 从session中得到用户用于保存所有书的集合(即得到用户的购物车)        List<Book> list = (List<Book>) session.getAttribute("list");        if(list==null) {            list = new ArrayList<Book>();            session.setAttribute("list", list);        }        list.add(book);        // 记住千万不要用请求转发        // request.getRequestDispatcher("/ListCarServlet").forward(request, response);        /*         * 显示购物车,通常会用到重定向技术。         * request.getContextPath()返回"/day07"         */        String url = request.getContextPath()+"/ListCarServlet";        response.sendRedirect(url);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

注意:显示购物车,记住千万不要用请求转发,而应该使用重定向技术。
最后创建显示用户购买商品的Servlet——ListCarServlet。

public class ListCarServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        HttpSession session = request.getSession(false);        if(session == null) {            out.print("您没有购买任何商品!!!");            return;        }        out.print("您购买了如下商品:<br/>");        List<Book> list = (List<Book>) session.getAttribute("list");        for(Book book : list) {            out.print(book.getName()+"<br/>");        }    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

问题1:若显示购物车,使用的是请求转发,而没使用请求重定向技术。那么运行效果如下:
这里写图片描述
从上面的运行效果图可以看到,如果使用的是请求转发,当在显示用户购买的商品的窗口,点击刷新按钮,那么购买的书会出现多次,原因是当点击刷新按钮时就是把上次的事情再干一次,即再购买一次书,所以这不是我们所想要的。从我的笔记通过Servlet生成验证码图片中记录着刷新有两个作用:

  1. 不管你有没有缓存,都要向服务器发请求。
  2. 刷新就是把上次的事情再干一次。

所以显示购物车,通常会用到请求重定向技术。
问题2:关掉IE浏览器后,再开IE浏览器,上次购买的商品还在。
为了解决这个问题,应手工以cookie形式发送sessionid,以解决关闭浏览器后,上次买的东西还在。创建完成购买功能的Servlet——BuyServlet应修改为:

public class BuyServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String id = request.getParameter("id");        Book book = (Book) Db.getAll().get(id);        HttpSession session = request.getSession();        // 手工以cookie形式发送sessionid,以解决关闭浏览器后,上次买的东西还在        String sessionId = session.getId();        Cookie cookie = new Cookie("JSESSIONID", sessionId);        cookie.setPath("/day07");        cookie.setMaxAge(30*60);        response.addCookie(cookie);        // 从session中得到用户用于保存所有书的集合(即得到用户的购物车)        List<Book> list = (List<Book>) session.getAttribute("list");        if(list==null) {            list = new ArrayList<Book>();            session.setAttribute("list", list);        }        list.add(book);        /*         * 显示购物车,通常会用到重定向技术。         * request.getContextPath()返回"/day07"         */        String url = request.getContextPath()+"/ListCarServlet";        response.sendRedirect(url);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

问题3:若浏览器禁用Cookie后,又该怎么解决呢?
解决方案:URL重写。此时代表网站首页的Servlet——ListBookServlet应修改为:

public class ListBookServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        // 创建Session        request.getSession();        out.print("本网站有如下商品:<br/>");        Map<String, Book> map = Db.getAll();        for (Map.Entry<String, Book> entry : map.entrySet()) {            Book book = entry.getValue();            String url = response.encodeURL("/day07/BuyServlet?id="+book.getId()); // 将超链接的url地址进行重写            out.print(book.getName() + "<a href='"+url+"' target='_blank'>购买</a><br/>");        }    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

代表完成购买功能的Servlet——BuyServlet应修改为:

public class BuyServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String id = request.getParameter("id");        Book book = (Book) Db.getAll().get(id);        HttpSession session = request.getSession(false);        // 手工以cookie形式发送sessionid,以解决关闭浏览器后,上次买的东西还在        // ...        // 从session中得到用户用于保存所有书的集合(即得到用户的购物车)        List<Book> list = (List<Book>) session.getAttribute("list");        if(list==null) {            list = new ArrayList<Book>();            session.setAttribute("list", list);        }        list.add(book);        /*         * 显示购物车,通常会用到重定向技术。         * request.getContextPath()返回"/day07"         */        String url = response.encodeRedirectURL(request.getContextPath()+"/ListCarServlet"); // 对sendRedirect方法后的url地址进行重写        response.sendRedirect(url);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

此时Book类最好实现Serializable(序列化接口),若Book类没有实现Serializable(序列化接口)会报如下异常:

java.io.NotSerializableException: cn.itcast.shopping.Book

为什么Book类最好实现Serializable(序列化接口),我也不知道为何?谁能告诉我。

利用session完成用户登陆

首先创建代表用户的JavaBean——User.java。

public class User {    private String username;    private String password;    public User() {        super();        // TODO Auto-generated constructor stub    }    public User(String username, String password) {        super();        this.username = username;        this.password = password;    }    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;    }}

然后创建网站首页——index.jsp。

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>    欢迎您:${user.username }&nbsp;&nbsp;&nbsp;<a href="/day07/login.html">登录</a>&nbsp;&nbsp;&nbsp;<a href="/day07/LogoutServlet">退出登录</a>    <br/><br/><br/>    <a href="/day07/SessionDemo1">购买</a><br/>    <a href="/day07/SessionDemo2">结账</a></body></html>

接着创建登录页面——login.html。

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title here</title></head><body>    <form action="/day07/LoginServlet" method="post">        用户名:<input type="text" name="username"><br/>        密码:<input type="password" name="password"><br/>        <input type="submit" value="登录">    </form></body></html>

再创建处理用户登录的Servlet——LoginServlet。

public class LoginServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        response.setContentType("text/html;charset=UTF-8");        PrintWriter out = response.getWriter();        String username = request.getParameter("username");        String password = request.getParameter("password");        List<User> list = DB.getAll();        for(User user : list) {            // 用户登录成功            if(user.getUsername().equals(username) && user.getPassword().equals(password)) {                request.getSession().setAttribute("user", user); // 登录成功,向session中存入一个登录标记                response.sendRedirect("/day07/index.jsp");                return;            }        }        out.print("用户名或密码不正确!!!");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}class DB {    public static List<User> list = new ArrayList<User>();    static {        list.add(new User("aaa", "123"));        list.add(new User("bbb", "123"));        list.add(new User("ccc", "123"));    }    public static List<User> getAll() {        return list;    }}

最后创建用户注销的Servlet——LogoutServlet。

// 完成用户注销public class LogoutServlet extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        HttpSession session = request.getSession(false);        if(session == null) {            response.sendRedirect("/day07/index.jsp");            return;        }        session.removeAttribute("user"); // 移除登录标记        response.sendRedirect("/day07/index.jsp");    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

使用Session防止表单重复提交

使用Session防止表单重复提交可参考我的客户端防表单重复提交和服务器端session防表单重复提交。

利用Session实现一次性验证码

一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码。
服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。
密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动处理过程。如下图:
这里写图片描述
该案列可参考我的通过Servlet生成验证码图片 。

三个域对象(ServletContext/request/session)的总结

什么情况下,我们会选择相应的域对象作为我们的容器呢?

  • ServletContext
    这个程序产生数据之后,(数据)除了显示给用户外,不仅等一会儿还要用,还要给别人用,这时就选用ServletContext。(应用:聊天室)
  • request
    这个程序产生数据之后,(数据)如果显示完了就没用了,这时就选用request。
  • session
    这个程序产生数据之后,(数据)除了显示给用户外等一会儿还要用,这时就选用session。

更详细信息可参考我的JSP属性范围。

1 1
原创粉丝点击