Servlet的学习之Session(3)
在上一篇《Servlet的学习之Session(2)》我们知道了Session能实现一个会话过程中保存数据或者多个会话中实现同一个Session的关键因素就是Cookie,只是Cookie是否临时的还是保存硬盘中一段时间而已。
但不是所有的用户的浏览器都会保持着接收Cookie,当有些用户的浏览器禁用Cookie或者第三方安全工具阻止了Cookie之后,那么Session就再也无法保存数据了。
我们通过IE浏览器的【工具】--->【Internet选项】--->【隐私】--->【高级】,勾选“替代自动cookie处理”,将“第一方Cookie”和“第三方Cookie”都选择为“阻止”。这样就阻止了Cookie。如果我们再去访问会创建的Session的Servlet,那么就会看到如下提示:
当浏览器禁用Cookie之后,即使是在一个浏览器的一个会话过程中,Session都不起任何作用,因为这时候浏览器连要缓存在IE浏览器中Cookie都不接收。所以每次调用request.getSession()方法都是创建新的Session。其实,getSession()这个方法首先是Session查询从浏览器是否能发来包含JSESSIONID的cookie;如果没有这个cookie,那么浏览器会查询请求URL,这个请看下一段。如果两种方式都没有能找到以前的Session,那么getSession()最终会创建新的Session。
如果我们想在浏览器禁用Cookie之后能继续使用同一个Session,那么Cookie不能用,我们就要从Session使用Cookie的原理来解决。Session使用Cookie的原理在于能保存JSESSIONID,而我们如果在点击的超链接的URL中加入JSESSIONID的值,那么调用request.getSession()方法的Servlet就可以直接从该URL获取JSESSIONID,再去配对服务器端相同JSESSIONID的值的Session,这样就可以再次获取服务器端Session中保存的数据。这个在超链接中加入JSESSIONID的方法叫做URL重写。URL重写,用来解决客户端浏览器禁用Cookie之后Session的共享问题。
URL重写主要有两个方法:
Response.encodeURL()方法
Response.encodeRedirectURL()方法
请认真阅读这两个方法的API文档:
如果是在一个页面中的可以点击的超链接要使用encodeURL()方法,而在一个Servlet中如果有要重定向的路径必须使用encodeRedirectURL()方法,而不能使用encodeURL()方法。无论哪种方法,经过URL重写之后都会在URL路径的最后增加JSESSIONID和值。
我们再将前篇《Servlet的学习之Session(1)》中最后的购物车例子进行改写,使得当用户浏览器禁用cookie之后依然能完成功能。
当我们禁用cookie之后,再使用这个例子,那么浏览器会抛出空指针异常,原因在于最后购物车Servlet获取我们存放物品的LinkedList上,因为禁用了cookie,使得我们无法获得上一次的Session,因此每次使用getSession()方法都要获取新的Session,而新的Session中并不会有我们设置的“productCart”这个属性,因此每次获得的LinkedList其实都不存在:
1 LinkedList<Book> link = (LinkedList<Book>) session.getAttribute("productCart"); 2 3 writer.write("您所希望购买的商品如下:<br>"); 4 for(ListIterator<Book> literator = link.listIterator();literator.hasNext();){ 5 Book b = literator.next(); 6 writer.write(b.getName()+"<br>"); 7 }
那么我们就需要在购物车Servlet的前一个Servlet,也就是进行重定向到购物车Servlet的那个Servlet中,将重定向的URL地址进行重写:
1 String reUrl = response.encodeRedirectURL("/SessionProject/servlet/SessionDemo3"); 2 response.sendRedirect(reUrl);
只要简单使用encodeRedirectURL或者encodeURL方法就能将地址增加JSESSIONID:
记住,鉴于用户浏览器会禁用cookie,因此我们需要在整个web工程中对于会使用到getSession()方法的超链接、重定向或者转发等等的URL地址都要进行URL重写。
同时!!在所有Servlet的中,最开始的Servlet,也可以称为首页Servlet的代码的最开始加入request.getSession(); 这样一句“废话”,这是因为将URL重写的前提是必须要有Session,或者说已经建立了Session。
这里将《Servlet的学习之Session(1)》使用URL改写后的例子重新贴出代码,这里略去商品的bean类和使用Map作为数据库的代码,请到《Servlet的学习之Session(1)》博客查阅:
在首页展示商品的Servlet:
1 response.setCharacterEncoding("UTF-8"); 2 response.setContentType("text/html;charset=utf-8"); 3 PrintWriter writer = response.getWriter(); 4 5 request.getSession(); 6 7 Map<String,Book> map = BookDatabaseFactory.getMap(); 8 for(Map.Entry<String, Book> en : map.entrySet()){ 9 10 String reUrl = response.encodeURL("/SessionProject/servlet/SessionDemo2?id="+en.getKey()); 11 writer.write(en.getValue().getName() + 12 "<a href='"+reUrl+"' target='_blank'>购买</a> <br>"); 13 }
处理购买商品的Servlet:
1 String bookId = request.getParameter("id"); 2 Map<String,Book> map = BookDatabaseFactory.getMap(); 3 Book book = map.get(bookId); 4 5 HttpSession session = request.getSession(); 6 LinkedList<Book> link = (LinkedList<Book>) session.getAttribute("productCart"); //禁用cookie之后这里会空指针异常 7 if(link == null) { 8 link = new LinkedList<Book>(); 9 session.setAttribute("productCart", link); 10 } 11 link.add(book); 12 //将所点击的商品交给购物车页面显示 13 String reUrl = response.encodeRedirectURL("/SessionProject/servlet/SessionDemo3"); 14 response.sendRedirect(reUrl);
显示购物车的Servlet:
1 response.setCharacterEncoding("utf-8"); 2 response.setContentType("text/html;charset=utf-8"); 3 PrintWriter writer = response.getWriter(); 4 5 HttpSession session = request.getSession(); 6 LinkedList<Book> link = (LinkedList<Book>) session.getAttribute("productCart"); //禁用cookie之后这里会空指针异常 7 8 writer.write("您所希望购买的商品如下:<br>"); 9 for(ListIterator<Book> literator = link.listIterator();literator.hasNext();){ 10 Book b = literator.next(); 11 writer.write(b.getName()+"<br>"); 12 } 13 /* 当用户禁用cookie之后再使用覆盖cookie就不能用了 14 * //当浏览器关闭后重新打开依然有之前想要购买的物品 15 Cookie cookie = new Cookie("JSESSIONID", session.getId()); 16 cookie.setMaxAge(60*60); 17 cookie.setPath("/SessionProject"); 18 response.addCookie(cookie);*/
通过以上两个地方的URL重写(已用红色字体标出),我们就能在用户禁用cookie之后继续能获得之前保存数据的Session,从而对该Session中的数据继续进行操作。我们甚至可以从首页查看源文件看到所有的超链接中可以看到都加入了JSESSIONID:
但是,这个方法只能在一个会话过程中有效。也就是说当用户将浏览器关闭之后,重新打开,之前的数据就不复存在了,因为这种方法无法在新的会话中获取之前的Session。因此不能像《Servlet的学习之Session(2)》中使用cookie方法一样无论多少个会话都能获取之前相同的Session。
补充:上面的例子我们以用户禁用cookie为前提,因此在首页显示商品的页面上所有的超链接我们都进行URL重写,这点我们从页面查看源文件也能看出。如果我们上面的例子在用户没有禁用cookie的时候,第一次访问时,我们查看源文件,所有的超链接依然还是有URL重写后将所有的超链接都附上JSESSIONID;但是我们再次刷新时,这些JSESSIONID都会消失:
这是因为服务器很“聪明”,只要你没有禁用cookie,它就不需要进行URL重写,准确的说是进行了URL重写,但是因为没有禁用cookie,因此重写后的URL还是跟原来一样,这点在最开始的encodeRedirectURL和encodeURL的API截图中也可以看出。