
}
}
if(actionType.equals("logout")){
testSession ts=
(testSession)session.getAttribute("testSession");
if(ts!=null) {
session.removeAttribute("testSession");
if( !ts.User.name.equals("")){ //-2-
actionResult=ts.User.name;
ts.logout();
}
session.invalidate();
}
actionResult=actionResult+" logout success";
}
}
else
actionResult="null";
%>
<head>
<script LANGUAGE="Javascript"></script>
<script>
function doAction(actionType)
{
document.test.actiontype.value=actionType;
document.test.submit();
}
</script>
</head>
<body>
<table width="80%" border="1" align="center" >
<form method="POST" action="logintest.jsp"
name="test">
<tr>
<td height="33" align="right">用戶:</td>
<td width="70%"> <input name="username"
type="text" value="" size="20">
</td>
</tr>
<tr>
<td width="27%" height="22" align="right">密碼:</td>
<td width="73%">
<input name="password" type="password" size="20">
</td>
</tr>
<tr>
<td height="32" colspan="2" align="right">
<div align="center">
<input name="B1" type="button" value="登錄"
onclick="doAction('login')">
<input name="B2" type="reset"
value="重寫">
<input name="B3" type="button" value="注銷"
onclick="doAction('logout')">
</div></td>
</tr>
<tr>
<td width="27%" height="22" align="right">
<input name="actiontype" type="hidden"
value=""></td>
<td width="73%"> <input name="info" type="text"
size="20" value="<%=actionResult%>">
</td>
</tr>
</form>
</table>
</body>
[-1-]:程序的if(actionType.equals("login")){…}部分處理login。[-1.1-]前后部分先通過session.getAttribute("testSession");取得session中保存的會話變量ts(一個testSession對象實例)。假如ts為空,表示當前用戶還沒有login,否則用戶已經login了,則先logout再重新login,并將新testSession對象保存到session里。application.getAttribute("app_vts");所取得的變量app_vts中保存了所有當前登錄用戶的user信息。每個用戶login成功時,即往app_vts中添加一個user對象,這是通過testSession的addappses方法完成的。而當用戶注銷時,先從session.getAttribute("testSession")中取到當前用戶的testSession對象,該對象已含有application.getAttribute("app_vts")對象的引用,通過testSession的logout方法進行注銷處理(見[-2-]標記前后)。TestSession.logout最終是通過調用removeappses方法從全局對象app_vts中移除用戶信息的??偨Y來說,程序利用應用全局對象application來保存跟蹤用戶連接信息的對象(例中為app_vts),該對象記錄著應用中所有用戶的連接、退出信息。
JSP頁面運行的界面如圖:
當我們輸入用戶testuser_1并按<登錄>按鈕,按鈕下面的文本框顯示"testuser_1 login success"。我們利用viewSessiones.jsp來觀察結果,顯示如下:
viewSessiones.jsp文件的內容如下:
<%@ page import="com.testSession, com.user, viewSessiones.jsp文件的作用是將app_vts中的用戶信息顯示出來。
當我們從桌面再啟動一個IE程序,輸入用戶testuser_2并按<登錄>按鈕,按鈕下面的文本框顯示"testuser_2 login success"。我們利用viewSessiones.jsp來觀察結果,顯示如下:
testuser_1 sessionId=A16DCE950C2C664D0AA93E05B27D8E00
testuser_2 sessionId=34B0AF3F1F2573F1C1DD12D62DF06F91
而當我們在第一個IE中按下按鈕<注銷>,logintest.jsp的顯示為:
刷新viewSessiones.jsp來觀察結果,顯示如下:
testuser_2 sessionId=BC487C6A9FD663EA27E797A420B41051
我們在第二個IE中按下按鈕<注銷>,按鈕下面的文本框顯示"testuser_2 login success", 刷新viewSessiones.jsp來觀察結果,顯示出已經沒有連接的用戶信息。
用戶訪問應用,只有一個入口:login。應用的所有用戶登錄都可以被觀察到。用戶離開應用,有三種可能:注銷、轉到其他網站、直接關閉瀏覽器。選擇注銷離開應用,可以被程序觀察到(logout),而后兩種方式的離開應用,卻不會調用logout。要觀察到后兩種方式,就需要使用對象的finalize()方法。
用戶通過轉到其他網站、直接關閉瀏覽器兩種方式離開應用超過Session的timeout時間時,用戶的Session對象會自動失效,即變為無用對象,而列入了垃圾收集器的回收范圍;關聯的,"寄存"在Session中的testSession對象會同時變為無用對象(在其生命期,僅存在Session對它的引用,Session失效了,它的唯一引用者不存在了,也就變成了無用對象)。垃圾收集器運行時,首先會調用testSession的finalize(),testSession就通過在finalize()方法中清除app_vts中存儲的本用戶信息。在testSession類代碼中可看到,finalize()調用類的removeappses()方法執行實際的清除操作。垃圾收集器的運行,除了讓其根據需要自動啟動外,也可通過程序調用來啟動它,比如:System.gc() 就直接啟動系統垃圾收集動作。
可以想見,本例假如不利用類的finalize()方法,我們很難找到另一種簡便的途徑來達到清除用戶信息的目的,因為用戶非正常離開應用的事件對WEB服務端來說是無法感知的。
在testSession類代碼中還有一個比較非凡的地方,實現用戶信息加入和清除的兩個方法addappses 和removeappses都被定義為static synchronized 類型。為什么呢?這是同步的需要。
App_vts是個應用級的全局可共享對象,在同一時刻連接到WEB 上的有多個用戶,這些用戶都可以操作對象app_vts。假如有兩個或以上的用戶同時調用testSession的addappses或removeappses方法(這是完全可能的,因為WEB SERVER是多線程服務),將帶來不可預料的結果。
為了防止兩個或以上的客戶程序(屬于不同線程)同時訪問一個資源,Java提供了一種內建的機制來解決沖突。這種機制就是synchronized(同步)。在一個類中將一個特定的方法設為synchronized(同步的),便可有效地防止沖突,在任何時刻,只能有一個線程調用特定對象的一個synchronized方法(盡管那個線程可以調用多個對象的同步方法),另一個線程只有等上一線程對該方法的調用執行完畢后才能獲得該方法的調用權。大致的工作機制可以這樣認為:每個對象都包含了一把鎖(也叫作"監視器"),它自動成為對象的一部分;調用任何synchronized方法時,對象就會被鎖定,不可再調用那個對象的其他任何synchronized方法,除非第一個方法完成了自己的工作,并解除鎖定。在類testSession中,將 addappses和removeappses這兩個方法設為了synchronized,當調用testSession對象的addappses方法時,便不能再同時調用testSession對象的removeappses方法,反之亦然。
Synchronized又有兩個級別。當我們將一個方法僅僅設為synchronized時,那是對象級的"鎖",雖然一個對象的synchronized方法不可同時調用,卻可以同時調用不同對象的同一個synchronized方法。所以這樣做還沒完全解決問題,因為兩個用戶(各有自己的testSession對象)可以同時調用addappses方法同時操作app_vts。當我們將一個方法設為static synchronized時,則是類級的"鎖"。類包含的"鎖"(自動作為類的Class對象的一部分),可在一個類的范圍內被相互間鎖定起來,從那個類創建的所有對象都共享一把"鎖"。就如testSession實際所做的那樣,addappses 和removeappses被定義為static synchronized 類型,這樣,任一時候,所有線程用戶中肯定只能有一個用戶調用addappses 和removeappses兩者中的一個方法,達到防止沖突的目的。
以上樣例在windows 2000、TOMCAT40、JDK13中通過。
新聞熱點
疑難解答