我爱电脑技术论坛's Archiver

65006 发表于 2008-6-20 08:09

关于JSP中基于Session的在线用户统计分析

JSP作为后起之秀能够在服务器编程环境中占据一定地位,是和它良好支持一系列业界标准密切相关的。Session就是它提供的基础设施之一。作为一个程序员,你可以不介意具体在客户端是如何实现,就方便的实现简单的基于session的用户管理。现在对于处理在线用户,有几种不同的处理方法。
/{ Acz,I8aQE%R
~Ip&G8}2| :AZ#Z4lbE6zl
一种是页面刷新由用户控制,服务器端控制一个超时时间比如30分钟,到了时间之后用户没有动作就被踢出。这种方法的优点是,如果用户忘了退出,可以防止别人恶意操作。缺点是,如果你在做一件很耗时间的事情,超过了这个时间限制,submit的时候可能要再次面临登陆。如果原来的叶面又是强制失效的话,就有可能丢失你做的工作。在实现的角度来看,这是最简单的,Server端默认实现的就是这样的模式。
P2C1QG|
O)j7Y5t!fc(H@;~ (Gp|D8K%?f5I
另一种方式是,站点采用框架结构,有一个Frame或者隐藏的iframe在不断刷新,这样你永远不会被踢出,但是服务器端为了判断你是否在线,需要定一个发呆时间,如果超过这个发呆时间你除了这个自动刷新的页面外没有刷新其他页面的话,就认为你已经不在线了。采取这种方式的典型是xici.net。 他的优点是可以可以利用不断的刷新实现一些类似server-push的功能,比如网友之间发送消息。
Too1m7AscZ,C G
y+hoi{"q-?(H 6X/r'F tV qh
不管哪一种模式,为了实现浏览当前所有的在线用户,还需要做一些额外的工作。Servlet API中没有得到Session列表的API。 J.P bp.g

9W6llgvI bO
]9s"wFo2F5H%_ 可以利用的是Listener. Servlet 2.2和2.3规范在这里略微有一些不一样。2.2中HttpSessionBindingListener可以实现当一个HTTPSession中的Attribute变化的时候通知你的类。而2.3中还引入了HttpSessionAttributeListener.鉴于我使用的环境是Visual age for Java 4和JRun server 3.1,他们还不直接支持Servlet 2.3的编程,这里我用的是HttpSessionBindingListener. 5D*vIeQC9_6S
k6zZ\hm

9L&?%I+B7|7{xgeZ:B 需要做的事情包括做一个新的类来实现HttpSessionBindingListener接口。这个接口有两个方法: ^g,jQ$_v s~,M
p7l(_)aJ
F+@u0Mt|.q A~a
public void valueBound(HttpSessionBindingEvent event) :q S Ot[4M$o

Ye.Xtl2K`g 1JY,]?t:p)X1G
public void valueUnbound(HttpSessionBindingEvent event)
kC0t @-D XN#F7j*M
/U.eN.n4k&|%RYx1@+v w w2A9yd
当你执行Session.addAttribute(String,Object)的时候,如果你已经把一个实现了HttpSessionBindingListener接口的类加入为Attribute,Session会通知你的类,调用你的valueBound方法。相反,Session.removeAttribute方法对应的是valueUndound方法。
x^O2?9o%SW
Ly8^K3b^2J[
9T$Q @8@1t XT+\L|Q`sS J
public class HttpSessionBinding implements javax.servlet.http.HttpSessionBindingListener
$U1Uw h B'TU#W\
"Rz,`6T3X5S`Q {
8B|;{;T:B;?%q'_0Z"p
AV c1{|c  ServletContext application = null;
5K#e9P[2HXN vv?
k[)k#WNa&SZ G?3G4C,z?*k
 public HttpSessionBinding(ServletContext application)
c-q7F0L)ADdG} vA7Kd9WfeRM
 {
${ V^^i7z -}4\ VM;SCw{'C U$Ct ]
super();
~#|]2k{R"`-x'P s~3mFfy {C6T)M
if (application ==null)
j$d;Gwx~ !`w8So~+i6X
 throw new IllegalArgumentException("Null application is not accept."); &tP1n'd$hw
(tEu%xN
this.application = application;
(P\5@6~z&Hv~g @8cm#Cu8e8H-g!L
 }
L({@L%Z,_k*\w9TV|
r/sW&Pl6I ilJ yTRZ@&U
 public void valueBound(javax.servlet.http.HttpSessionBindingEvent e)
1Vi"^,p|n~ -x(I2y `H hh
 {
!p-T}3{S5q 4|^r2xfy
Vector activeSessions = (Vector) application.getAttribute("activeSessions");
ySvsP*S/F_(w
Y~M$S9Akg7^%us if (activeSessions == null)
@o(q?/h-z i2V8S;B+] X;O7[W/eX
{ J*say&@"{
U9cJV~ i8sX oI
 activeSessions = new Vector();
zo+Tnw#I$IM _\V
@!us-D.~$F } g[m0ZV]I8D/xB

)f/yIDx | E8j7K&n.`
t-gX_[e x JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user");
F1LbQ Uf r;~0@(K,r y;z-f)@*[
if (sessionUser != null) K7nom}m"f;@%}s

_[3|3VY {
(M4Y(ap_ OOCZ6D!e(~r%y2v X p
 activeSessions.add(e.getSession());
I$X$_ ?z:H}9Yo.?Q
kbp}A#K5Ae4U4_ }
2jzHX1x;i0B C!Hrx^&HaEB;R
application.setAttribute("activeSessions",activeSessions);
C0yS8? p%];i F_C7w}1b O8f
 }
;f c G*k5X(lT-yp 5|W a9u0_
KY(zl.V]-@
 public void valueUnbound(javax.servlet.http.HttpSessionBindingEvent e) p.A~.@:_%Fu~:h
9Q,a(^k6OU|U%S
 {
(Za3a;~3Ik 8GIn/@ z)]
JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user");
tq;hVp%^*A G4g
+Mz|"o4T1[S6mgF if (sessionUser == null)
&Z F ~5B9cqeO
POk Cfls6HF J { b-a D6^*Se

o S do*T  Vector activeSessions = (Vector) application.getAttribute("activeSessions"); (sz@)O \'y~
;niK NzZQ
 if (activeSessions != null) }5d2FJ!i@

&LF6x,QIE;Y  { sd?$CG)tL1H;W
ERds)h-o1J!q%K7Iz}
activeSessions.remove(e.getSession().getId()); 3tQ4aB9{o1I fX*C+`

u}l E^ application.setAttribute("activeSessions",activeSessions); 9w_Q(t!{+nv+T@x9N
,W!b+I(zK
 } :M5m5{:vvc8`

p`*Hf | KT8v }
#z%M:PC~eo #W&?hJe
 } WjWp t"j4@u

7s!\v\T }

65006 发表于 2008-6-20 08:09

假设其中的JDBCUser类是一个任意User类。在执行用户登录时,把User类和HttpSessionBinding类都加入到Session中去。 -N|r8_7I ^

(z$e,\:x)hQ#{ ;g.WnoY!cZuX%W j
这样,每次用户登录后,在application中的attribute "activeSessions"这个vector中都会增加一条记录。每当session超时,valueUnbound被触发,在这个vector中删去将要被超时的session。
k'{g a!ij-}Q:S;X .Ccb+B(Y$b AP ~e&P5b
3SH2s%K2g1xB7R?\{
[(I5^x?b {(U(L5c_;v/g
public void login() F8[M+sl#q
a6Y(~ ~M+] M
throws ACLException,SQLException,IOException J|8T/k,z:y%{N/Rp

-N#nH,Og:i0n:J | { L6o.X/@!_~

6^$} eT/N  /* get JDBC User Class */
0TA#q M#da-M6n+?A4B
vQ MI)m&H(wr0x.e  if (user != null)
'f&WO/k1]4D?7S !b]ZN0C XG&v(J
 { +_*?;zK5u4C@

8['s,{5bV]6]0W logout();
;h XZ W9j qGW/Q4\"p
 } )i^SF9]F I]

H/_gx(c{&UB_m  {
3_#\k]'{ o (n.~'p;o;O z]
// if session time out, or user didn't login, save the target url temporary.
.`z t.lD$CG
y(B0~UUG Mi/MQ B
JDBCUserFactory uf = new JDBCUserFactory(); I(d M#Cm:E;l&R{

XYMI%W,y`
D0iy4q;_f Jg5@ if ( (this.request.getParameter("userID")==null) || (this.request.getParameter("password")==null) )
GQ2B4x7jr9j@
v5ol$Eo {
!E"d.F!r?&F%mb!S
H6g%I(X\p+O  throw new ACLException("Please input a valid userName and password.");
0}\#P8| M2w|7H $`Nx+S#R:U
}
d&ND+O7[ g9w7i
C?pa J3w5A
0E7[N&` g JDBCUser user = (JDBCUser) uf.UserLogin( 2J!m@ d*z fxk j2F
a:bC9_Xt
 this.request.getParameter("userID"),
!s"n-M5j#| S {K {*[5cGu
 this.request.getParameter("password") );
S/f,kC'}qq1M3b (Ab W+_(~$[;z!](g
 user.touchLoginTime(); I jf-cW rd H

J3oG W+NYi  this.session.setAttribute("user",user); 1kXrMdO)t*N
VQJ3t.qV8N,e
 this.session.setAttribute("BindingNotify",new HttpSessionBinding(application)); Og1rH6b1OM

%`.@:TI{~p&J } F%q l f&[9x

J9hVc:A%p ^  }

65006 发表于 2008-6-20 08:10

Login的时候,把User和这个BindingNotofy目的的类都加入到session中去。logout的时候,就要主动在activeSessions这个vector中删去这个session。
/lP}1G"N4bBD'q
/q b;M%y3mEW2nr ~*h/A1Z|PR
H1C:p P&}T#}FaE
public void logout()
.b#g3k0l0h0lk
n'C'?Km(I L8m_!L throws SQLException,ACLException
I |6gnX(cx"m^|:B H8G`'V|c
{
~&Y1e i&V X XE
;t"@^L2Q-q  if (this.user == null && this.session.getAttribute("user")==null) 3@%_/Q"ei(`c f)\7W3Xq;x
0F#n(k'f&V!b
 { 4l2]|Y]:wXQ
A8eu'S)g'u
return;
6DaPLK+u
lL4{ Tm#E  }
U`@HCN
/? u:k \ xw!@ ik8Y6Us5Wn
 Vector activeSessions = (Vector) this.application.getAttribute("activeSessions"); I,N C_K o1D
0[c&J1mp{0yw S
 if (activeSessions != null) D ]EbOj"?{
E+n D7enWL
 { 6}'elyJ
ODY5Rw:^H#v
activeSessions.remove(this.session); -M{e[:qiMc
?B0b;Vv O
application.setAttribute("activeSessions",activeSessions);
H wD9k dc tS(rYB
JO @ ek|E&F+g  } "P T*j'j,DSN
}(O"^ e1EE&[*h(L

@.lg9b9` [qe SK]c o  java.util.Enumeration e = this.session.getAttributeNames();
"NH5A&F/G&r5{{J Wb/B8zy;S
*FD$z1Cd3m
 while (e.hasMoreElements())
x?U1`zy&V /s#aNN g!lD/uFVB
 { N'r/wj!X5A H

\ GZ.SG,L String s = (String)e.nextElement();
nqYIWl hV3|'rh K'H7q
this.session.removeAttribute(s); :uD Q+_0G]wew
m;}ff z
 } T D&l~+z%o*Wu2|N
qgd-y-i!mvA;_
 this.user.touchLogoutTime();
+_1YB@b*Z,{
(xe`2H:e:^.v"z jB*v(L  this.user = null; _#xj W*P
@.w,^Z5u
}
adu Kg"f
];Wf+|H
{2[!HA#rof1r
$X%l `1C.`A$vY 这两个函数位于一个HttpSessionManager类中.这个类引用了jsp里面的application全局对象。这个类的其他代码和本文无关且相当长,我就不贴出来了。 @"p6^0RE:@4B"X

BV[W W+J FH}Z8l
下面来看看JSP里面怎么用。 y{ Gwv&oD
~Z nf!E"Y
*c1o~ K!x1M Bn
假设一个登录用的表单被提交到doLogin.jsp, 表单中包含UserName和password域。节选部分片段:
~CL i!xH P RS L }*{3{P

{FC*Be1N "h1l~Q!O
<% x%k8@/u;y:Ug
4y#s n#@0z FsF
HttpSessionManager hsm = new HttpSessionManager(application,request,response); x$u.~.d t}

HK E0w`5q1] try h,@@b[_B
]2|`-N,x*h4R
{
%O VmPf|
AP&H:v](N)Q  hsm.login(); K0X Yp {4h1G

q"z3k(u'fB;~c$r }
T1TW4i7]:I`m"y
/VRYO5Cg catch ( UserNotFoundException e)
:SA&k'i9` s"[ 8Ce*yr!R KYn
{ $c6s NT;b9ewGiE
yz$}$[?{
 response.sendRedirect("InsufficientPrivilege.jsp?detail=User%20does%20not%20exist."); K.j"p`FgQ x/y

tc1I'qGg!y(P2Hu(q  return;
na&KFs&X'x
8Vs:n tZi P Z+E }
|E&L1vm %^]*i,PsAC
catch ( InvalidPasswordException e2) Ut_zV(n7XH
'~V0}xb9ZM`j
{
5O X.vENqD N
:kkx[ZO'_)`  response.sendRedirect("InsufficientPrivilege.jsp?detail=Invalid%20Password"); HD4Zi6oe-MN

tk0s\ I t I [p  return; :u$R o*c8} p+D%bO

7EE-?1R8~G(\QZ } "L N1D{-Y*Q,E`1@:s
R;id|3H \[
catch ( Exception e3) +\&S4lV*E
|!H:x cg[ N
{ (G&v9}u } m$^x`E

|7\Cs8_Gc  %> Error:<%=e3.toString() %><br>
E zfm)G:ZT.Lu\
#cP&X `;dE cM3Fyk  Press <a href="login.jsp">Here</a> to relogin.
;v8]G;J5NCFSJ
*y#};[ E0E7`.pS@moh0dE  <% return;
.{ h{*`f:jT6^p _;S MlHPmK4T
}
*`0|6V0}w6b4o)b
v;[|C(O response.sendRedirect("index.jsp");
:AP!?-ua)l3m2b
/TA9U5m0^&bi#K %>$uyh!_.O6aE@_
:_@ebfOp8x^

-WIn$]a :on4og @V'qpP
再来看看现在我们怎么得到一个当前在线的用户列表。 2P/XK%U;N}0o

n^N5CSG 2YVu o+o r.qK;`!M

'@ a#_ P(W@9? <body bgcolor="#FFFFFF">
"P$v2T8}P^ [ !q9o8{Pj k@'ym X
<table cellspacing="0" cellpadding="0" width="100%">
1NK1Rs:^(cxt @yQ ],Ws
j|Gq-e\
<tr >
l8z4Pi4h Jzt$|'Jc
L-ZNh&q:z'c <td style="width:24px">SessionId n,g&y!Lw&A
(V L+S}.[m1I
</td>
a YL#i(J5bN?Q7S~ 8J^0G%Y i.hT
<td style="width:80px" >User &M/OTD`t ww,R

NQ$J{8DP/j(Zy </td>
(Bp%f]0QD)kG
E4J8sybh;iV/a?F'Mr <td style="width:80px" >Login Time
&eHX-b9rHb
['Qi s;},f#wvHI </td>
)W1S bM}'kt
1Q0I'pLe+fq.t&h <td style="width:80px" >Last Access Time
+q(h,L$OjH$X"D UJ4k`GWm\
</td> 4^mYb+E

(dC6a h1e&W </tr>
BX9M4j!V*Q
W?Z S8?/} <%
]'bDs C V;{mRt
'?(rII[*{ Vector activeSessions = (Vector) application.getAttribute("activeSessions");
J-b @8d^ mg r5@:x2f#V'{4L,oq:P B
if (activeSessions == null)
W8WCPwHA;H
3F$w8|\PtMrF {
A&P{Y,e]S`"} o*n!|g!A
 activeSessions = new Vector(); ,YSG;e"CB za2L1}t

c7w,@mW|E  application.setAttribute("activeSessions",activeSessions); o-i0Ge4r*G

/q+D9s8_,ul%Gs'F } 0K)FGI _"E5e;Ot*d7U
:b{&~a8_J
:g9~lU6M#C5f p;c
Iterator it = activeSessions.iterator();
a&h7w`0L0kz +FJ]?s{9t1J%O
while (it.hasNext())
Q z-F X^"R %yF.B0G'KN+y k
{ !fH)~J0[a.kG
`/]X z-q,V
 HttpSession sess = (HttpSession)it.next();
#ou:PsP-A p } od|x0o8?
 JDBCUser sessionUser = (JDBCUser)sess.getAttribute("user");
.yhC N-Ww bg7M b7tK4^^+w,m2p
 String userId = (sessionUser!=null)?sessionUser.getUserID():"None"; v ]Yw:^,C

hwlA6Ay|9? %>
)mczjmgD3R#w z jkJ1?#p%{.`
<tr> 6jcbSW0TJ
~W N*T5y
<td nowrap=''><%= sess.getId() %></td> xN}*y;o9~ l e

6N7C;qz-I;qkH(`C <td nowrap=''><%= userId %></td> 3s3| \pI Q M8S

c u/J'{(T&F <td nowrap=''> u{?q/wE

+M c;AO-r"x*E <%= BeaconDate.getInstance( new Java.util.Date(sess.getCreationTime())).
z;_/U?qTo getDateTimeString()%></td> v:W#Z#zy C6KO
3K#J$nY^"t'X0n9f5y.vOG
<td class="<%= stl %>3" nowrap=''> 8lONP&E(T6z%S
)I7f-u6I8a;j@8p
<%= BeaconDate.getInstance( new java.util.Date(sess.getLastAccessedTime())).
~,sO!m,q$c getDateTimeString()%></td>
$P]&_ D$JJ M? Z
9f:slV n {!^j </tr> p/T%kE It"qZ
"g%n'H$xc
<% 1zH%] ]'K3biZ{e

-iD SF M^.Fu6\ }
c{4l6M^W"H ^9p1ZR-_b(a,p+iL
%>
*ONR5~} ["jQ NYEYC)P
</table> 6drI$fSF~!u!D

:T9t;Q:s;]en </body>9d.C*K f_h't

c(E VS*C^'W x Q&pfU#g$qVn6a2w

&aXm j%GpG 以上的代码从application中取出activeSessions,并且显示出具体的时间。其中BeaconDate类假设为格式化时间的类。 8jyz:SD3P

[Ujq$BYF4DT
Wc&U.tt?'Y 这样,我们得到了一个察看在线用户的列表的框架。至于在线用户列表分页等功能,与本文无关,不予讨论。 iPKQE_
+Yq"n3R+M/N}5EJ!Qe
Ti7I5HY/G4Js_fU
这是一个非刷新模型的例子,依赖于session的超时机制。我的同事sonymusic指出很多时候由于各个厂商思想的不同,这有可能是不可信赖的。考虑到这种需求,需要在每个叶面刷新的时候都判断当前用户距离上次使用的时间是否超过某一个预定时间值。这实质上就是自己实现session超时。如果需要实现刷新模型,就必须使用这种每个叶面进行刷新判断的方法。

页: [1]
   

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.