UML软件工程组织

Jive源代码情景分析-index.jsp

内容摘要:

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 = false2)

    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接口,可以为forumthreadmessagegroup做迭代,我们现在单单看为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(),这是为了将用户无权访问的信息过滤掉。

    下面的代码,就难度较低了,大家明白了上面的,剩下的就能很快理解(其实是我偷懒,重复的和简单的分析不想做了)

 

版权所有:UML软件工程组织