三、Session
1.2 Session机制
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
1.2.1 什么是Session
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
1.2.2 实现用户登录
Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的Session,
例如:
HttpSessionsession=request.getSession(); // 获取Session对象session.setAttribute("loginTime", newDate()); // 设置Session中的属性out.println("登录时间为:"+(Date)session.getAttribute("loginTime")); // 获取Session属性
request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,request.getSession()方法会返回null,而getSession(true)会先创建Session再将Session返回。
Servlet中必须使用request来编程式获取HttpSession对象,而JSP中内置了Session隐藏对象,可以直接使用。如果使用声明了<%@page session="false" %>,则Session隐藏对象不可用。下面的例子使用Session记录客户账号信息。
源代码如下:
代码1.9session.jsp<%@pagelanguage="java"pageEncoding="UTF-8"%><jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/><jsp:directive.pageimport="java.text.SimpleDateFormat"/><jsp:directive.pageimport="java.text.DateFormat"/><jsp:directive.pageimport="java.util.Date"/><%!DateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器%><%response.setCharacterEncoding("UTF-8"); // 设置request编码Person[] persons= { // 基础数据,保存三个人的信息newPerson("Liu Jinghua","password1", 34, dateFormat.parse ("1982-01-01")), newPerson("Hello Kitty","hellokitty", 23, dateFormat.parse ("1984-02-21")), newPerson("Garfield", "garfield_pass",23, dateFormat.parse ("1994-09-12")), }; Stringmessage=""; // 要显示的消息if(request.getMethod().equals("POST")) { // 如果是POST登录 for(Personperson :persons) { // 遍历基础数据,验证账号、密码// 如果用户名正确且密码正确if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password"))) { // 登录成功,设置将用户的信息以及登录时间保存到Sessionsession.setAttribute("person", person); // 保存登录的Personsession.setAttribute("loginTime", newDate()); // 保存登录的时间 response.sendRedirect(request.getContextPath() +"/welcome.jsp"); return; } } message="用户名密码不匹配,登录失败。"; // 登录失败 } %><!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01Transitional//EN"><html>// ... HTML代码为一个FORM表单,代码略,请看随书光盘</html>
登录界面验证用户登录信息,如果登录正确,就把用户信息以及登录时间保存进Session,然后转到欢迎页面welcome.jsp。welcome.jsp中从Session中获取信息,并将用户资料显示出来。
welcome.jsp代码如下:代码1.10welcome.jsp<%@pagelanguage="java"pageEncoding="UTF-8"%><jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/><jsp:directive.pageimport="java.text.SimpleDateFormat"/><jsp:directive.pageimport="java.text.DateFormat"/><jsp:directive.pageimport="java.util.Date"/><%!DateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器%><%Personperson=(Person)session.getAttribute("person"); // 获取登录的personDateloginTime=(Date)session.getAttribute("loginTime"); // 获取登录时间%>// ... 部分HTML代码略<table><tr><td>您的姓名:</td><td><%=person.getName()%></td></tr><tr><td>登录时间:</td><td><%=loginTime%></td></tr><tr><td>您的年龄:</td><td><%=person.getAge()%></td></tr><tr><td>您的生日:</td><td><%=dateFormat.format(person.getBirthday()) %></td></tr></table>
程序运行效果如图1.8所示。
图1.8 使用Session记录用户信息
注意程序中Session中直接保存了Person类对象与Date类对象,使用起来要比Cookie方便。
当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。
提示:Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。
1.2.3 Session的生命周期
Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。
Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。
1.2.4 Session的有效期
由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。
Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。
1.2.5 Session的常用方法
Session中包括各种方法,使用起来要比Cookie方便得多。Session的常用方法如表1.2所示。
表1.2 HttpSession的常用方法
Tomcat中Session的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。可以修改web.xml改变Session的默认超时时间。例如修改为60分钟:
<session-config><session-timeout>60</session-timeout><!--单位:分钟--></session-config>注意:<session-timeout>参数的单位为分钟,而setMaxInactiveInterval(ints)单位为秒。
1.2.6 Session对浏览器的要求
虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。
注意:新开的浏览器窗口会生成新的Session,但子窗口除外。子窗口会共用父窗口的Session。例如,在链接上右击,在弹出的快捷菜单中选择“在新窗口中打开”时,子窗口便可以访问父窗口的Session。
如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?例如,绝大多数的手机浏览器都不支持Cookie。Java Web提供了另一种解决方案:URL地址重写。
1.2.7 URL地址重写
URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写,例如:
<td><ahref="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>">Homepage</a></td>
该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。重写后的输出可能是这样的:
<td><ahref="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=Java">Homepage</a></td>
即在文件名的后面,在URL参数的前面添加了字符串“;jsessionid=XXX”。其中XXX为Session的id。分析一下可以知道,增添的jsessionid字符串既不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器通过解析URL地址获得Session的id。
如果是页面重定向(Redirection),URL地址重写可以这样写:
<% if(“administrator”.equals(userName)) { response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”)); return; } %>
效果跟response.encodeURL(String url)是一样的:如果客户端支持Cookie,生成原URL地址,如果不支持Cookie,传回重写后的带有jsessionid字符串的地址。
对于WAP程序,由于大部分的手机浏览器都不支持Cookie,WAP程序都会采用URL地址重写来跟踪用户会话。比如用友集团的移动商街等。
注意:TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。
1.2.8 Session中禁止使用Cookie
既然WAP上大部分的客户浏览器都不支持Cookie,索性禁止Session使用Cookie,统一使用URL地址重写会更好一些。Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。
打开项目sessionWeb的WebRoot目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下:
代码1.11 /META-INF/context.xml <?xml version='1.0' encoding='UTF-8'?> <Context path="/sessionWeb"cookies="false"> </Context>
或者修改Tomcat全局的conf/context.xml,修改内容如下:
代码1.12 context.xml <!-- The contents of this file will be loaded for eachweb application --> <Context cookies="false"> <!-- ... 中间代码略 --> </Context>
部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了。
注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。
四、SessionId
(1)关闭浏览器session就被干掉的假象的问题
当在前台取出session时,关闭浏览器后再次访问服务器,这时服务器返回了一个null,此时的返回的session并非之前的那个session而是一个新的session。
-->先来看看session的生命周期:
创建:当getSession()后,session就被创建。
结束:有三种结束生命的情况 >>
1.服务器会默认一个30分钟的生命时间值,若在这个期间,为被访问服务器时就会自动结束生命;
|--可以在服务器下的web.xml文件中的 <session-timeout> 30 </session-timeout> 修改这是默认值,是以分
为单位。
2.session自杀: 调用session.invalidate()方法可以立即杀死session;
3.服务器意外关闭。(服务器正常关闭时session是会被服务器保存在服务器的 session.ser 文件中(在work文
件夹下))
--> 当关闭浏览器时怎么就会有session干掉的假象呢?
由于session是基于Cookie的。浏览器发起请求会携带sessionId到服务器,服务器根据这个id来判读当前访问的是哪个session。
然而浏览器被关闭后由于浏览器的Cookie文件还未设置MaxAge值,所以在此时浏览器的Cookie是会话级别的,是存在浏览器的内存中,当浏览器被关闭浏览器的内存被释放,临时文件被清除,这时的Cookie也随之销毁,则当前这个请求中被没有之前的那个sessionId值,服务器就当时第一次访问,给浏览器创建一个新的session值并返回一个null;
但是之前的那个session并没有被干掉,只是浏览器找不到这个sessionId了。这样一来,此时服务器器就存在了两个session了。
-->解决这个假象的方案
我们可以创建一个新的CooKie,该Cookie的名字为jsession,path为WEB应用的虚拟路径,并设置setMaxAge()的毫秒值,让Cookie保存在客户端的硬盘中,这时即使多次对浏览器进行关开操作是不会清楚客户端硬盘文件的。所以,Cookie就不丢失了,SessionId也不会随浏览器关闭而丢失。
具体代码实现:
Cookie cookie = new Cookie("JSESSIONID", session.getId()); cookie.setPath(request.getContextPath()+"/"); cookie.setMaxAge(30*60); response.addCookie(cookie);
(2)机制讲解
当你第一次访问一个网站的时候,网站服务器会在响应头内加上Set-Cookie:PHPSESSID=nj1tvkclp3jh83olcn3191sjq3(php服务器),或Set-Cookie JSESSIONID=nj1tvkclp3jh83olcn3191sjq3(java服务器)信息,此信息是服务器随机生成的,放在服务器内存里,为了标识唯一的客户端用户,内容不会重复,这就是sessionid。
当浏览器得到这个sessionid会将它放在自己的进程内存里,这里不同的浏览器会有所不同,IE进程间不能共享这个sessionid,也就是新开一个IE将不能共享这个sessionid;而Firefox进程间可以共享。然后你继续发请求给这个网站的时候,浏览器就会把这个sessionid放在请求头里发送给该服务器了,这样服务器得到sessionid后再和自己内存里存放的sessionid对比锁定客户端,从而区分不同客户端,完成会话。
可以看出如果用这种方式,当用户在会话的过程中关闭浏览器结束进程,则这个sessionid将消失,如果用户又打开浏览器想继续这次会话的时候,就会因为发送的请求中没有这个sessionid而使服务器无法辨别该把那个session信息给他,注意(这个时候服务器端的sessionid和sessionid所指向的session都还存在,只是没有正确的sessionid和它匹配而占用服务器内存,只有session过期或服务器重启才释放内存)。
上面这种方式叫会话cookie,把cookie放在浏览器内存里,只能在这个浏览器的内存范围里完成会话,是一种不长久的方式,为了能长久会话,就出现了持久化cookie,把cookie固化在用户的计算机上,现在的cookie不单单能存放sessionid,还能放用户信息,样式表信息等。
如果用户禁止了所有cookie的使用,那么会话cookie和持久化cookie都不能用了,有个方案也可以解决问题,就是URL重写,这里要说下的就是URL重写只能实现会话cookie的效果,持久会话实现不了。
(3)验证总结
- 打开A浏览器,sessionId-A被创建,新建一个A浏览器里的标签页,还是sessionId-A,关闭其中的一个标签页(没有退出浏览器)仍然还能识别出sessionId-A(即 Cookie 中还存留着sessionId-A)。
- 在 1 的基础上,再打开A浏览器(甚至多个)(不是新建标签页),其作用相当于只是一个标签页而已,新建和关闭该A浏览器 Cookie 中还存留着sessionId-A。(前提是只要没有完全关闭完A浏览器)
- 打开B浏览器,则 sessionId-A 就找不到了,则不同的浏览器之间不共享 sessionId。(好像IE有个版本浏览器不按常规出牌)
- 完全关闭某个浏览器,则该浏览器的临时 Cookie 则被清空,导致下次重新打开该浏览器, Cookie 中 sessionId-A 就没有了,但是如果 sessionId-A、sessionVal-A 还没有到达超时状态或者强制失效状态,则 sessionId-A、sessionVal-A 还是在的,只是 Cookie 原先的 sessionId-A 被清空了,没办法去匹配,导致再想读取原先的内容输出为NULL。
- 同个机子、同个服务器、不同的应用之间的 sessionId 不同。