在web開發(fā)中,session是個(gè)非常重要的概念。在許多動(dòng)態(tài)網(wǎng)站的開發(fā)者看來,session就是一個(gè)變量,而且其表現(xiàn)像個(gè)黑洞,他只需要將東西在合適的時(shí)機(jī)放進(jìn)這個(gè)洞里,等需要的時(shí)候再把東西取出來。這是開發(fā)者對(duì)session最直觀的感受,但是黑洞里的景象或者說session內(nèi)部到底是怎么工作的呢?當(dāng)筆者向身邊的一些同事或朋友問及相關(guān)的更進(jìn)一步的細(xì)節(jié)時(shí),很多人往往要么含糊其辭要么主觀臆斷,所謂知其然而不知其所以然。
筆者由此想到很多開發(fā)者,包括我自己,每每都是糾纏于框架甚至二次開發(fā)平臺(tái)之上,而對(duì)于其下的核心和基礎(chǔ)知之甚少,或者有心無力甚至毫不關(guān)心,少了逐本溯源的精神,每憶及此,無不慚愧。曾經(jīng)實(shí)現(xiàn)過一個(gè)簡單的HttpServer,但當(dāng)時(shí)由于知識(shí)儲(chǔ)備和時(shí)間的問題,沒有考慮到session這塊,不過近期在工作之余翻看了一些資料,并進(jìn)行了相關(guān)實(shí)踐,小有所得,本著分享的精神,我將在本文中盡可能全面地將個(gè)人對(duì)于session的理解展現(xiàn)給讀者,同時(shí)盡我所能地論及一些相關(guān)的知識(shí),以期讀者在對(duì)session有所了解的同時(shí)也能另有所悟,正所謂授人以漁。
Session是什么
Session一般譯作會(huì)話,牛津詞典對(duì)其的解釋是進(jìn)行某活動(dòng)連續(xù)的一段時(shí)間。從不同的層面看待session,它有著類似但不全然相同的含義。比如,在web應(yīng)用的用戶看來,他打開瀏覽器訪問一個(gè)電子商務(wù)網(wǎng)站,登錄、并完成購物直到關(guān)閉瀏覽器,這是一個(gè)會(huì)話。而在web應(yīng)用的開發(fā)者開來,用戶登錄時(shí)我需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)以存儲(chǔ)用戶的登錄信息,這個(gè)結(jié)構(gòu)也叫做session。因此在談?wù)搒ession的時(shí)候要注意上下文環(huán)境。而本文談?wù)摰氖且环N基于HTTP協(xié)議的用以增強(qiáng)web應(yīng)用能力的機(jī)制或者說一種方案,它不是單指某種特定的動(dòng)態(tài)頁面技術(shù),而這種能力就是保持狀態(tài),也可以稱作保持會(huì)話。
在一般系統(tǒng)登錄后,都會(huì)設(shè)置一個(gè)當(dāng)前session失效的時(shí)間,以確保在用戶長時(shí)間不與服務(wù)器交互,自動(dòng)退出登錄,銷毀session
具體設(shè)置的方法有三種:
1.在web容器中設(shè)置(以tomcat為例)
在tomcat-7.0\conf\web.xml中設(shè)置,以下是tomcat7.0中默認(rèn)配置:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
tomcat默認(rèn)session超時(shí)時(shí)間為30分鐘,可以根據(jù)需要修改,負(fù)數(shù)或0為不限制session失效時(shí)間
這里要注意這個(gè)session設(shè)置的時(shí)間是根據(jù)服務(wù)器來計(jì)算的,而不是客戶端。所以如果在調(diào)試程序,應(yīng)該是修改服務(wù)器端時(shí)間來測(cè)試,而不是客戶端
2.在工程的web.xml中設(shè)置
<!--時(shí)間單位為分鐘-->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
這里的15是指15分鐘失效
3.通過java代碼設(shè)置
session.setMaxInactiveInterval(30*60);//以秒為單位,即在沒有活動(dòng)30分鐘后,session將失效
三種方式優(yōu)先等級(jí):1 < 2 < 3
在一般系統(tǒng)中,也可能需要在session失效后做一些操作:
1.控制用戶數(shù),當(dāng)session失效后,系統(tǒng)的用戶數(shù)減少一個(gè),控制用戶數(shù)量在一定范圍內(nèi),確保系統(tǒng)的性能
2.控制一個(gè)用戶多次登錄,當(dāng)session有效時(shí),如果相同用戶登錄,就提示已經(jīng)登錄了,當(dāng)session失效后,就可以不同提示,直接登錄
那么如何在session失效后,進(jìn)行一系列的操作呢?
這里就需要用到監(jiān)聽器了,即當(dāng)session因?yàn)楦鞣N原因失效后,監(jiān)聽器就可以監(jiān)聽到,然后執(zhí)行監(jiān)聽器中定義好的程序就可以了
監(jiān)聽器類為:HttpSessionListener類,有sessionCreated和sessionDestroyed兩個(gè)方法
自己可以繼承這個(gè)類,然后分別實(shí)現(xiàn)
sessionCreated指在session創(chuàng)建時(shí)執(zhí)行的方法
sessionDestroyed指在session失效時(shí)執(zhí)行的方法
例子:
public class OnlineUserListener implements HttpSessionListener{ public void sessionCreated(HttpSessionEvent event){ HttpSession session=event.getSession; String id=session.getId()+session.getCreationTime(); SummerConstant.UserMap.put(id,Boolean.TRUE);//添加用戶 } public void sessionDestroyed(HttpSessionEvent event){ HttpSession session=event.getSession; String id=session.getId()+session.getCreationTime(); synchronized(this){ SummerConstant.USERNum--;//用戶數(shù)減- SummerConstant.UserMap.remove(id);//從用戶組中移除掉,用戶組為一個(gè)map } } }
然后只需要把這個(gè)監(jiān)聽器在web.xml中聲明就可以了
<listener>
<listener-class>com.demo.OnlineUserListener</listener-class>
</listener>
1."在一次做非常復(fù)雜的ajax應(yīng)用時(shí),如果一個(gè)會(huì)話已經(jīng)超時(shí),但是此時(shí)再通過ajax請(qǐng)求,那么ajax返回的則是一個(gè)登陸頁面的html,那這下就慘了,頁面上而已就亂了"
2."對(duì)于普通的http請(qǐng)求,可以通過過濾器Filter來判斷session超時(shí),然后跳轉(zhuǎn)到登錄頁面;但是對(duì)于Ajax請(qǐng)求,則不會(huì)如期待的那樣自動(dòng)轉(zhuǎn)到登錄頁面(我試了網(wǎng)站上的許多種方案,都是停留在當(dāng)前頁面)"
3.Ajax請(qǐng)求后臺(tái)數(shù)據(jù)雖然會(huì)被過濾器filter攔截,但是因?yàn)锳jax操作與對(duì)頁面整個(gè)頁面的提交請(qǐng)求不一樣,filter中的重定向并不能使之跳到一個(gè)新的頁面,因此需要我們?nèi)プ鎏厥獾奶幚怼L幚碓砗芎唵?,如果session超時(shí),filter返回一個(gè)超時(shí)標(biāo)識(shí)給客戶端,客戶端檢測(cè)到超時(shí)頭信息,跳轉(zhuǎn)到指定頁面。
4.當(dāng)session超時(shí)時(shí),如果不是ajax請(qǐng)求,很簡單就能實(shí)現(xiàn)跳到指定的頁面;但是ajax請(qǐng)求就會(huì)有問題:如果是ajax類型的彈出框則會(huì)在彈出框中顯示跳轉(zhuǎn)的指定頁面,如果是正常ajax請(qǐng)求,則可能會(huì)顯示源代碼等。
5.http://www.iteye.com/topic/1126552
6.http://www.cnblogs.com/shencheng/archive/2011/01/07/1930227.html
7.區(qū)分ajax請(qǐng)求和普通http請(qǐng)求:session超時(shí)處理、exception處理
---sessionListener
web.xml:
<session-config> <session-timeout>1</session-timeout> <!--單位min,若為-1則永遠(yuǎn)不超時(shí)--> </session-config> <listener> <listener-class>test.MyHttpSessionListener</listener-class> </listener>
Listener:
public class MyHttpSessionListener implements HttpSessionListener { //創(chuàng)建 public void sessionCreated(HttpSessionEvent arg0) { System.out.println("sessionCreated.........."); } //銷毀 public void sessionDestroyed(HttpSessionEvent arg0) { System.out.println("sessionDestroyed........"); } }
---session過期判斷:跳轉(zhuǎn)到登錄頁面
web.xml:(配置在springmvc的攔截之前)
<filter> <filter-name>sessionFilter</filter-name> <filter-class>test.SessionFilter</filter-class> </filter> <filter-mapping> <filter-name>sessionFilter</filter-name> <url-pattern>/user/*</url-pattern> </filter-mapping>
Filter:
public class SessionFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { throw new ServletException("OncePerRequestFilter just supports HTTP requests"); } HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // String userId = (String) req.getSession().getAttribute("userId"); // if ((userId == null) || "".equals(userId)) { // res.sendRedirect(req.getContextPath() + "/loginDemo"); // } System.out.println("判斷: " + req.getSession(false)); if (null == req.getSession(false)) { // if (true == req.getSession(true).isNew()) { // System.out.println("new request"); // } System.out.println("session已經(jīng)過期"); if (req.getHeader("x-requested-with") != null && req.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) { System.out.println("ajax超時(shí)處理.............."); res.addHeader("sessionstatus", "timeout"); res.addHeader("loginPath","loginurl"); res.setCharacterEncoding("utf-8"); res.setContentType("text/html;charset=UTF-8"); OutputStream out = response.getOutputStream(); PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,"utf-8")); pw.println("{\"result\":false,\"errorMessage\":\"您未登錄,請(qǐng)先登錄\"}"); pw.flush(); pw.close(); //前端js處理 return; }else{ System.out.println("http超時(shí)處理............."); res.sendRedirect(req.getContextPath() + "/index.page"); return; } } else{ System.out.println("Session is active!"); } System.out.println("sessionFilter doFilter.........."); chain.doFilter(request, response); } public void destroy() { } public void init(FilterConfig filterConfig) { } }
--前端JS處理:
$(document).ajaxComplete(function(event, xhr, settings) { if(xhr.getResponseHeader("sessionstatus")=="timeOut"){ if(xhr.getResponseHeader("loginPath")){ window.location.replace(xhr.getResponseHeader("loginPath")); }else{ alert("Request time out relogin plase !"); } } }); //全局的ajax訪問,處理ajax清求時(shí)sesion超時(shí) $.ajaxSetup({ contentType:"application/x-www-form-urlencoded;charset=utf-8", complete:function(XMLHttpRequest,textStatus){ //通過XMLHttpRequest取得響應(yīng)頭,sessionstatus, var sessionstatus=XMLHttpRequest.getResponseHeader("sessionstatus"); if(sessionstatus=="timeout"){ //如果超時(shí)就處理 ,指定要跳轉(zhuǎn)的頁面 window.location = "<c:url value="/" />"; } } });
如對(duì)本文有疑問,請(qǐng)?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會(huì)為你解答??! 點(diǎn)擊進(jìn)入論壇