内容摘要:
Jive作为学习设计模式的好教材,难度自然不低,我们就以Jive(J道版)为例程,带着大家来一次“面向过程式”的逐步阅读源代码,好像一次游览一般。本次浏览只限于匿名登陆(即为客人身份)下浏览index.jsp。
作者:蔡永航
(注:James Shen的学弟) 南京工业大学工商管理三年级学生
|
目前论坛中有如图所示的板块:
一个版块名叫Java,一个版块名叫C#。为了简化难度,两个论坛中均为刚刚建立,并且里面各有一篇帖子。
下面我们就开始我们的“客人”旅程吧。我们将上图划分为6块:
一.准备:
虽然index.jsp由上面的6块组成,但是在index开始的地方包含了6块都需要共享的信息,如下:
<%@
page import="java.util.*,
com.jivesoftware.forum.*,
com.jivesoftware.forum.util.*"
errorPage="error.jsp"
%>
<%
// global.jsp is a collection of
utility methods and global variables.
// Page authorization and the creation
of the authToken and forumFactory
// variables is handled there.
%>
<%@
include file="global.jsp"
%>
<%
// The title of this page. The header
page assumes the "title" variable.
String title
= JiveGlobals.getJiveProperty("websiteconf.name");
%>
这里我们感兴趣只有<%@
include file="global.jsp"
%>这一行,让我们潜入global.jsp一窥究竟。
global.jsp:
<jsp:useBean
id="myEnv"
scope="application"
class="com.jdon.web.UserEnvFront"/>
<jsp:setProperty
name="myEnv"
property="*"/>
<%@
page import="java.util.*,
com.jivesoftware.util.*,
com.jivesoftware.forum.*,
com.jivesoftware.forum.util.*"
%>(1)
<%
// Check to see if a Jive authorization
token exists
boolean isGuest
= false;
(2)
Authorization authToken
= SkinUtils.getUserAuthorization(request,
response);
(3)
if (authToken
== null)
{ (4)
authToken =
AuthorizationFactory.getAnonymousAuthorization();
isGuest=true;
}
//init forumfactory and pageUser
myEnv.registeUserInit(authToken); (5)
User pageUser
= myEnv.getPageUser();
// The last time the user visited this
page
Date lastVisited
= new
Date(SkinUtils.getLastVisited(request,response));
// The number of messages a user wants
to show per page (6)
int userMessageRange
= myEnv.du.getMessageRange(request,response,pageUser);
//default userMessageRange is 15.
%>
(1)在这个JSP页面的开始处初始化了Bean
com.jdon.web.UserEnvFront
,并且将作用范围设为application,作用于整个程序。同时使用<jsp:setProperty
name="myEnv" property="*"/>设置其中字段的值(笔者注:对于本文,此句不起作用可以忽略)。
(2)在页面中有个字段isGuest,以判别用户是注册用户还是匿名(客人)用户,初始值为false。
(3)调用类SkinUtils中的静态方法getUserAuthorization(request,
response)以返回代表用户权限的对象,那么让我们来看看getUserAuthorization(request,
response):
getUserAuthorization(request,
response):
public
static Authorization
getUserAuthorization
(HttpServletRequest request,
HttpServletResponse response)
{
HttpSession session
= request.getSession();
// Check 1: check for the Jive
authentication token in the user's session.
Authorization authToken
= (Authorization)session.getAttribute(JIVE_AUTH_TOKEN);
if (authToken
!= null)
{
return authToken;
}
// Check 2: check the jive cookie for
username and password
Cookie cookie
= getCookie(request,
JIVE_AUTOLOGIN_COOKIE);
if (cookie
!= null)
{
try {
// at this point, we found a cookie so
grab the username and
// password from it, create an
authorization token and store
// that in the session
String[] values
= decodePasswordCookie(cookie.getValue());
String username
= values[0];
String password
= values[1];
// Try to validate the user based on
the info from the cookie.
// Catch any exceptions
authToken =
AuthorizationFactory.getAuthorization(username,password);
}
catch (Exception
e) {}
// put that token in the user's
session:
if (authToken
!= null)
{
session.setAttribute(JIVE_AUTH_TOKEN,
authToken);
}
// return the authorization token
return authToken;
}
return null;
}
在getUserAuthorization(request,
response)中,首先从request中取得session,检查目前用户的session中是否存在“JIVE_AUTH_TOKEN”,如果存在的话直接返回Authorization的对象authToken。不存在的话也有可能注册用户刚刚打开浏览器进入论坛(此时的注册用户以前曾经选择将用户信息保存在cookie中),还没有初始化;也有可能是匿名用户登陆,所以接下来程序分别对这两种情况进行处理。先处理用户为注册用户刚刚登陆,程序通过方法getCookie()取得用户客户端存放的cookie,并用decodePasswordCookie()对其进行一些解码的处理,处理完成后取得用户的用户名和密码,将其交给AuthorizationFactory的静态方法getAuthorization()以判别用户是否存在,并产生相应的权限对象,在接下来的代码中通过
if
(authToken !=
null)
{
session.setAttribute(JIVE_AUTH_TOKEN,
authToken);
}
判断此时authToken是否为null,不是的话将authToken放入session中,便于程序以后直接从session中取得。那么接下来的处理匿名用户登陆就更简单了,由于匿名用户登陆后Authorization的对象authToken一路为空,并且方法的三次判断都和它不沾边,所以直接返回null。
(4)让我们浮上水面,呼吸一下新鲜空气吧。此时global.jsp中的authToken为空,符合if语句的条件,if语句内部通过工厂AuthorizationFactory的静态方法getAnonymousAuthorization()获得匿名用户所具备的权限,并将isGuest的值设为true,表明用户为匿名用户。
(5)通过myEnv的registeUserInit()方法将UserEnvFront中代表当前用户个人信息的对象pageUser进行赋值,但这不是最主要的,最主要的是该方法中的this.forumFactory
= ForumFactory.getInstance(authToken);这句话非常非常关键。让我们潜入ForumFactory的静态方法getInstance()。
public
static ForumFactory
getInstance(Authorization authorization)
{
//If no valid authorization passed in,
return null.
if (authorization
== null)
{
return null;
}
if (factory
== null)
{
synchronized(initLock)
{
if (factory
== null)
{
// Note, the software license
expressely forbids
// tampering with this check.
//LicenseManager.validateLicense("Jive
Forums Basic", "2.0");
String classNameProp
=
JiveGlobals.getJiveProperty("ForumFactory.className");
//default classNameProp =
com.jivesoftware.forum.database.DbForumFactory
if (classNameProp
!= null)
{
className =
classNameProp;
}
try {
//Load the class and create an
instance.
Class c
= Class.forName(className);
factory =
(ForumFactory)c.newInstance();
}
catch (Exception
e) {
System.err.println("Failed
to load ForumFactory class "
+ className
+ ".
Jive cannot function normally.");
e.printStackTrace();
return null;
}
}
}
}
//Now, create a forum factory proxy.
return new
ForumFactoryProxy(authorization, factory,
factory.getPermissions(authorization));
}
这个方法接受一个Authorization参数,当传入的Authorization参数为空时方法将直接返回null,我认为这是避免当Jive没有产生任何关于用户的认证信息却调用了此方法,得到工厂,从而Jive无法通过权限对用户的访问进行控制。大家睁大眼睛了,精彩的地方来了。这里采用了设计模式中的“单例”模式。这里的变量factory在类中为静态变量private
static ForumFactory factory = null;但jive第一次运行的时候,这个变量毫无疑问为null,当用户执行到ForumFactory.getInstance(authToken)这句话的时候,程序判断factory为空,此时程序将类中的类型为Object的实例initLock作为同步的锁对象,保证当同步块中的代码没有执行完的时候没有其他用户能够执行此代码块,严格保证factory的唯一性,在同步区块中程序从系统的属性中读取到论坛将用何种工厂方法类进行数据持久化管理所需的相关对象的创建。如果你没有做任何修改该类因该是com.jivesoftware.forum.database.DbForumFactory。在程序的最后生成的工厂方法又被new
ForumFactoryProxy(authorization,
factory,factory.getPermissions(authorization))包装(这根据了设计模式中的“代理”模式)。这样做可以根据用户的权限将适合用户权限的内容显示给用户,例如,未登陆用户看不见某个特定的论坛,但是其方法的名称与DbForumFactory的完全相同,即继承自相同的类ForumFactory,这样对调用ForumFactoryProxy类中的方法的程序来说是透明的,就好像直接调用DbForumFactory一般。为了便于大家理解“代理”模式我举个不恰当的事情(其他的不是不想写,是我根本写不出来),大家都看过古代皇帝吃饭前有个老太监负责尝尝皇帝的菜有没有下毒,皇帝和老百姓吃的方法都一样,用嘴嘛,普通人吃饭时不需要别人尝菜的。这个时候老太监就像一个代理,看看菜有没有毒,没毒的话,皇帝再用那种普通人的吃菜方法将菜吃掉。有毒的话,老太监要么死掉,皇帝这个时候不会傻到再去吃那个菜吧;老太监要么叫人将菜进行特殊处理再给皇上吃,此时估计皇上饿疯掉了。这里就是随口举个例子,帮助理解,千万别进行柯南式的推理,笑笑便算了。
(6)int
userMessageRange =
myEnv.du.getMessageRange(request,response,pageUser)中的userMessageRange代表浏览论坛的用户希望每页显示的帖子的数目,在作为客人的条件下访问论坛,这一值默认为15。
二.论坛的显示
这里便是index.jsp的核心部分了,上文图中标号为2的部分,这里一旦明白,index.jsp剩余的代码就像小人书一般容易理解了。代码精简如下:
<%
Iterator forums
= myEnv.getForumFactory().forums();
while (forums.hasNext())
{
Forum forum
= (Forum)forums.next();
String description
= forum.getDescription();
%>
剩下的代码与String
description =
forum.getDescription();大同小异
<%
}
%>
别看这段代码简单,背后可大有乾坤啊。
Iterator
forums =
myEnv.getForumFactory().forums();返回一个迭代器,myEnv.getForumFactory()就是我们刚刚得到的ForumFactoryProxy,调用其中的forums()方法,如下
public
Iterator forums()
{
return new
IteratorProxy(JiveGlobals.FORUM, factory.forums(),
authorization, permissions);
}
可以看到,在ForumFactoryProxy的forums()方法中对用户的权限进行了控制(代码中为橙色的地方),老太监来尝菜了,呵呵~~。但最重要的是青绿色的代码,factory.forums()此处即com.jivesoftware.forum.database.DbForumFactory.forums()返回的为实现了Iterator接口的对象,接着又对其进行代理的包装,结合上出的橙色代码对得到的结果进行控制,以显示符合用户浏览权限的内容。下面让我们潜入com.jivesoftware.forum.database.DbForumFactory.forums(),代码如下:
public
Iterator forums()
{
if (forums
== null)
{
LongList forumList
= new
LongList();(1)
Connection con
= null;
PreparedStatement pstmt
= null;
try {
con =
ConnectionManager.getConnection();
pstmt =
con.prepareStatement(GET_FORUMS);
//
//private static final String GET_FORUMS = "SELECT forumID FROM
jiveForum";
ResultSet rs
= pstmt.executeQuery();
while (rs.next())
{
forumList.add(rs.getLong(1));(2)
}
}
catch(
SQLException sqle
) {
sqle.printStackTrace();
}
finally {
try {
pstmt.close(); }
catch (Exception
e) {
e.printStackTrace(); }
try {
con.close(); }
catch (Exception
e) {
e.printStackTrace(); }
}
this.forums
= forumList.toArray();
}
return new
DatabaseObjectIterator(JiveGlobals.FORUM,
forums, this);(3)
}
(1)此处使用了LongList对象,看着名字就怪,util库中的LinkedList我想大家都用过的吧,其实这也是那么回事,区别在于util库中的LinkedList可以接受任何类型的对象存放于其中,而LongList单单允许数据类型为long的变量存放于其中。
(2)程序通过简单的SQL语句,将所有论坛的的forumID从数据库中取出,然后将其加入到类型的为LongList的forunList中,此时的forum中保存的是所有论坛的forunID号。并且将其转化为数组this.forums
= forumList.toArray()我想这么做是为了提升程序的效率。
(3)接着,头疼的东西来了
new
DatabaseObjectIterator(JiveGlobals.FORUM,
forums, this);这里的this就是DbForumFactory自身的引用,让我们开始下潜到类DatabaseObjectIterator中。
DatabaseObjectIterator代码(只保留了与显示论坛有关的代码):
/**
*
$RCSfile: DatabaseObjectIterator.java,v
$
*
$Revision: 1.1.1.1
$
*
$Date: 2002/09/09
13:50:49
$
*
*
New Jive
from Jdon.com.
*
*
This software
is the
proprietary information
of CoolServlets,
Inc.
*
Use is
subject to
license terms.
*/
package
com.jivesoftware.forum.database;
import
java.util.Iterator;
import
com.jivesoftware.forum.Forum;
import
com.jivesoftware.forum.ForumFactory;
import
com.jivesoftware.forum.ForumMessage;
import
com.jivesoftware.forum.ForumMessageNotFoundException;
import
com.jivesoftware.forum.ForumNotFoundException;
import
com.jivesoftware.forum.ForumThread;
import
com.jivesoftware.forum.ForumThreadNotFoundException;
import
com.jivesoftware.forum.Group;
import
com.jivesoftware.forum.GroupManager;
import
com.jivesoftware.forum.GroupNotFoundException;
import
com.jivesoftware.forum.JiveGlobals;
import
com.jivesoftware.forum.UnauthorizedException;
import
com.jivesoftware.forum.User;
import
com.jivesoftware.forum.UserManager;
import
com.jivesoftware.forum.UserNotFoundException;
/**
*
An class
that defines
the logic
to iterate
through an
array of
long unique
*
ID's of
Jive objects.<p>
*
*
One feature
of the
class is
the ability
to recover
from underlying
*
modifications to
the dataset
in some
cases. Consider
the following
sequence
*
of events:
*
<ul>
*
<li> Time
00: An
Iterator for
messages in
a thread
is obtained.
*
<li> Time
01: 3
of the
8 messages
in the
thread are
deleted.
*
<li> Time
02: Iteration
of messages
begins.
*
</ul>
*
*
In the
above example,
the underlying
messages in
the thread
were changed
*
after the
initial iterator
was obtained.
The logic
in this
class will
*
attempt to
automatically compensate
for these
changes by
skipping over
items
*
that cannot
be loaded.
In the
above example,
that would
translate to
the
*
iterator returning
5 messages
instead of
8.
*/
public
class DatabaseObjectIterator
implements Iterator
{
private long
[] elements;
private int
currentIndex =
-1;
private Object
nextElement =
null;
private DatabaseObjectFactory
objectFactory;
/**
* Creates
a new
DatabaseObjectIterator. The
type must
be one
of the
* following:
<ul>
*
<li> JiveGlobals.FORUM
*
<li> JiveGlobals.THREAD
*
<li> JiveGlobals.MESSAGE
*
<li> JiveGlobals.USER
*
<li> JiveGlobals.GROUP
* </ul>
*
* Additionally,
each type
of iterator
requires an
extra object
of a
* certain
type to
perform iteration.
In respective
order for
each
* <tt>type</tt>,
<tt>extraObject</tt>
should be
a ForumFactory,
Forum,
* ForumThread,
UserManager, or
GroupManager. If
<tt>type</tt>
and <tt>
* extraObject</tt>
do not
match, iteration
construction will
fail. <p>
*
* The
implementation of
this method
takes the
type and
extraObject and
* creates
anonymous inner
classes to
handle loading
of each
Jive object.
*/
public DatabaseObjectIterator(int
type, long
[] elements,
final Object
extraObject)
{
this.elements
= elements;
// Load the appropriate proxy factory
depending on the type of object
// that we're iterating through.
switch (type)
{ (1)
// FORUM
case JiveGlobals.FORUM:
// Create an objectFactory to load
forums.
this.objectFactory
= new
DatabaseObjectFactory() {
(2)
ForumFactory factory
= (ForumFactory)extraObject;
public Object
loadObject(long
id) {
try {
Forum forum
= factory.getForum(id);
(3)
return forum;
}
catch (ForumNotFoundException
mnfe) {
}
catch (UnauthorizedException
ue) {
}
return null;
}
};
break;
// Otherwise, an invalid value was
passed in so throw an exception.
default:
throw new
IllegalArgumentException("Illegal
type specified");
}
}
/**
* Returns
true if
there are
more elements
in the
iteration.
*
* @return
true if
the iterator
has more
elements.
*/
public boolean
hasNext() {
// If we are at the end of the list,
there can't be any more elements
// to iterate through.
if (currentIndex+1
>= elements.length
&& nextElement
== null)
{
return false;
}
// Otherwise, see if nextElement is
null. If so, try to load the next
// element to make sure it exists.
if (nextElement
== null)
{
nextElement =
getNextElement();
if (nextElement
== null)
{
return false;
}
}
return true;
}
/**
* Returns
the next
element.
*
* @return
the next
element.
* @throws
NoSuchElementException if
there are
no more
elements.
*/
public Object
next() throws
java.util.NoSuchElementException {
Object element
= null;
if (nextElement
!= null)
{
element =
nextElement;
nextElement =
null;
}
else {
element =
getNextElement();
if (element
== null)
{
throw new
java.util.NoSuchElementException();
}
}
return element;
}
/**
* Not
supported for
security reasons.
*/
public void
remove() throws
UnsupportedOperationException {
throw new
UnsupportedOperationException();
}
/**
* Returns
the next
available element,
or null
if there
are no
more
* elements
to return.
*
* @return
the next
available element.
*/
public Object
getNextElement() {
while (currentIndex+1
< elements.length)
{
currentIndex++;
Object element
= objectFactory.loadObject(elements[currentIndex]);
if (element
!= null)
{
return element;
}
}
return null;
}
}
/**
*
An interface
for loading
Jive database
objects.
*/
interface
DatabaseObjectFactory {
/**
* Returns
the object
associated with
<code>id</code>
or null
if the
* object
could not
be loaded.
*
* @param
id the
id of
the object
to load.
* @return
the object
specified by
<code>id</code>
or null
if it
could not
*
be loaded.
*/
public Object
loadObject(long
id);
}
(1)DatabaseObjectIterator实现了Iterator接口,可以为forum,thread,message,group做迭代,我们现在单单看为forum做迭代的部分。此时的type为FORUM。
(2)this.objectFactory
= new DatabaseObjectFactory() {}实现了该类中的内部类DatabaseObjectFactory中的loadObject()方法。loadObject()中实现了论坛内容读取的逻辑。
(3)Forum
forum =
factory.getForum(id)这句话又跳回到DbForumFactory中的getForum方法,这句话背后可大有文章,涉及到Jive核心的缓存策略,由于比较负责,您可以参考http://www.jdon.com
网站中关于Jive缓存分析的文章,为了大家思路上能够连续,记住一句话,缓存中有的从缓从中直接读取,缓存中没有的从数据库读取(废话!)。这里大家不妨认为Jive没有缓存,getForum(id)的行为就是将对应ID的fouum从数据库中取出,经过一系列赋值(例如:name
= 数据库中的name字段),得到一个Forum对象后返回,此forum对象包含着个一系列forum的属性,可以通过getXXX()方法得到。
(4)代码中没有这个标号,DatabaseObjectIterator本身作为一个迭代器,通过hasNext(),next()对包含了论坛ID的数组变量elements进行滚动,然后将取得论坛ID从数据库中检索出来,读取象相应的信息以生成forum对象返回给调用的index.jsp页面,这样jsp页面中就可以简单的通过forum.getName()等方法获得论坛的信息。
最后别忘了生成的DatabaseObjectIterator还要经过IteratorProxy的包装,通过IteratorProxy中的hasNext()和next()调用DatabaseObjectIterator中的hasNext()和next(),这是为了将用户无权访问的信息过滤掉。
下面的代码,就难度较低了,大家明白了上面的,剩下的就能很快理解(其实是我偷懒,重复的和简单的分析不想做了)
|