package com;
import java.util.Vector;
import com.user;
public class testSession {
public user User;
private Vector vsid;
public testSession()
{
User=new user();
}
public boolean verify(String username,String password)
throws Exception //验证用户/密码
{
return true;
}
public void setSessionVar(String sesid,Vector sid) {
this.User.sessionId=sesid;
this.vsid=sid;
}
private static synchronized void addappses(user puser,
Vector pvsid) { //记录一个新连接的用户
int pos=-1;
user l_user;
if (puser==null || pvsid==null)
return;
for(int i=0;i<pvsid.size();i++){
l_user=(user)pvsid.get(i);
if(l_user.sessionId.equals(puser.sessionId)){
pos=i;
break;
}
}
if(pos==-1){
pvsid.add(puser);
}
else{
pvsid.set(pos,puser);
}
}
private static synchronized void removeappses(user puser,
Vector pvsid) { //移除一个退出的用户
int pos=-1;
user l_user;
if (puser==null || pvsid==null)
return;
for(int i=0;i<pvsid.size();i++){
l_user=(user)pvsid.get(i);
if(l_user.sessionId.equals(puser.sessionId)){
pos=i;
break;
}
}
if(pos!=-1){
pvsid.remove(pos);
}
}
protected void finalize() {
this.removeappses(this.User,this.vsid);
}
public boolean login(String username) throws Exception
{ //处理登录
this.User.name=username;
this.addappses(this.User,this.vsid);
return true;
}
public boolean logout() throws Exception
{ //处理注销
this. finalize();
this.User=null;
this.vsid=null;
return true;
}
}
每一个用户均建立一个testSession对象,来保存该用户的信息。为了对类testSession进行说明,必须同时引人另一个文件logintest.jsp。这个用于示例的JavaScript/" target="_blank">JSP文件提供一个简单的界面进行登录、注销处理。文件内容如下:
<%@ page import=" com.testSession,
java.util.Vector"%>
<%@page contentType="text/html;charset=GBK" %>
<% request.setCharacterEncoding(response.
getCharacterEncoding());%>
<%
String actionType=request.getParameter("actiontype");
String actionResult="";
if(actionType!=null) {
if(actionType.equals("login")){ // -1-
String userName=request.getParameter("username");
if(userName==null || userName.equals("")){
;
}
else{
String password=request.getParameter("password");
if(password==null)
password="";
testSession ts=
(testSession)session.getAttribute("testSession");
if(ts!=null) { //-1.1-
session.removeAttribute("testSession");
if( !ts.User.name.equals(""))
ts.logout();
}
ts=new testSession();
if(!ts.verify(userName,password)) {
//验证用户与密码,看是否合法用户
actionResult="login fail";
//非法用户,显示错误信息
}
else{ //验证成功
session.setAttribute("testSession",ts);
Vector app_vts=
(Vector)application.getAttribute("app_vts");
if(app_vts==null) {
app_vts=new Vector();
application.setAttribute("app_vts",app_vts);
}
ts.setSessionVar(session.getId(),app_vts);
ts.login(userName);
actionResult=userName+" login success";
}
}
}
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中移除用户信息的。总结来说,程序利用应用全局对象application来保存跟踪用户连接信息的对象(例中为app_vts),该对象记录着应用中所有用户的连接、退出信息。
JavaScript/" target="_blank">JSP页面运行的界面如图:
当我们输入用户testuser_1并按<登录>按钮,按钮下面的文本框显示"testuser_1 login success"。我们利用viewSessiones.jsp来观察结果,显示如下:
testuser_1 sessionId=A16DCE950C2C664D0AA93E05B27D8E00 |
viewSessiones.jsp文件的内容如下:
<%@ page import="com.testSession, com.user,
java.util.Vector"%>
<%@ page contentType="text/html; charset=GBK"%>
<% request.setCharacterEncoding(response.
getCharacterEncoding()); %>
<%
Vector l_vts=(Vector)application.getAttribute("app_vts");
user l_us;
if(l_vts!=null){
for(int i=0;i<l_vts.size();i++){
l_us=(user)l_vts.get(i);
out.println(l_us.name+" sessionId="+l_us.sessionId);
out.println("<br>");
}
}
%> |
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来观察结果,显示出已经没有连接的用户信息。
上面演示中,用户信息的移除是通过调用类的logout()方法来实现的。但假如用户没有点按[注销]按钮而直接关闭IE或转到其他网站,该用户信息不是就一直存留在系统中吗?
让我们看看类testSession中的一个方法:
protected void finalize() {
this.removeappses(this.User,this.vsid);
} |
用户访问应用,只有一个入口: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中通过。 |
评论