中间层
中间层是联系上层访问接口和低层数据结构的纽带。它的主要功能就是根据ID(对应于数据库中的编号)到缓存中找相应的对象。如果缓存中有该对象就直接得到,没有则去读数据库生成一个新的对象,再把该对象放入缓存中,以便下次访问时能直接得到。
假如Forum表示论坛,Thread表示论坛贴子的线索,Message表示论坛贴子,它们的关系是:Forum包括数条Thread,Thread包括数条Message,且DbForum类、DbForumThread类和DbForumMessage类的实例对象都包含一个 DbForumFactory类的实例对象factory。DbForum类、DbForumThread类和DbForumMessage类被DbForumFactory生产出来,同时它们也通过DbForumFactory来访问缓存。而在DbForumFactory中则包含一个DatabaseCacheManager类的实例对象cacheManager。它负责管理所有的缓存对象,这些缓存对象就是ForumCache类、ForumThreadCache类和ForumMessageCache类的实例。ForumCache类、 ForumThreadCache类和ForumMessageCache类继承自同一个抽象类DatabaseCache,而在DatabaseCache类中,有一个LongCache型的成员变量cache。这样中间层就和低层的数据结构结合起来了。
现在以thread线索对象的获得为例,说明中间层是如何运作的。请看代码摘要:
DbForum.java
public class DbForum implements Forum, Cacheable
{
......
public ForumThread getThread(long threadID)throws
ForumThreadNotFoundException{
return factory.getThread(threadID, this);
}
......
}
DbForumFactory.java
public class DbForumFactory extends ForumFactory {
......
protected DbForumThread getThread(long threadID, DbForum forum)throws
ForumThreadNotFoundException{
DbForumThread thread = cacheManager.threadCache.get(threadID);
return thread;
}
......
}
ForumThreadCache.java
public class ForumThreadCache extends DatabaseCache {
......
public DbForumThread get(long threadID)throws
ForumThreadNotFoundException{
//缓存中寻找以threadID为编号的DbForumThread对象
DbForumThread thread = (DbForumThread)cache.get(threadID);
if (thread == null) {
//如果在缓存中找不到该对象
//新建一个以threadID为编号的DbForumThread对象
thread = new DbForumThread(threadID, factory);
//将新建对象加入缓存
cache.add(threadID, thread);
}
return thread;
}
......
}
DbForumThread.java
public class DbForumThread implements ForumThread, Cacheable {
......
protected DbForumThread(long id, DbForumFactory factory)throws
ForumThreadNotFoundException{
this.id = id;
this.factory = factory;
//读取数据库,其中id对应数据库中的jiveThreadProp表中的threadID字段
loadFromDb();
isReadyToSave = true;
}
......
}
从上面的代码可以看到,当调用DbForum类 的getThread(long threadID)方法获得一个编号为threadID的线索对象时,实际上调用的是DbForumFactory类中的getThread(long threadID, DbForum forum)方法,而GetThread方法则是调用ForumThreadCache类的get方法来完成任务的。ForumThreadCache类里get(long threadID)方法则根据threadID到缓存中找相应的线索对象,如果缓存中有该对象就直接得到,没有则新建一个DbForumThread对象,再把该对象放入缓存中。看到这里也许有人会奇怪,好像程序中根本没有连接数据库的语句。我们可以从DbForumThread类的代码中找到答案。原来Jive在新建一个DbForumThread对象时,就已经用loadFromDb()方法把数据读出来了。另一方面,如果在缓存中找到了DbForumThread对象,程序根本就不会新建DbForumThread对象,因而就好象没有数据库的操作,这实际上就是通过缓存机制所要达到的目的。
Message帖子对象的获得与Thread对象的获得类似,因此就不再重复了。从上面介绍可以看出,只要得到论坛线索的编号threadID,就可以得到对应的线索对象,不管它是从缓存中来,还是从数据库中来。那么threadID是如何从Jsp页面传到中间层的呢?让我们来看上层访问接口的运行机制吧。
上层访问接口
上层访问接口的主要功能是连接JSP页面和中间层。换句话说,就是把JSP页面中要调用的Thread、Message对象的ID传递到中间层。下面给出访问Thread相关类的类图。其中的forum.jsp是显示论坛内容的页面。在这里,我们把forum.jsp看成是一个特殊的类,它里面有一个ForumThreadIterator类的实例变量threads和DbForum类的实例变量forum,故它和ForumThreadIterator类及DbForum类的关系应是关联关系。
先来看forum.jsp和DbForum 类的部分代码:
forum.jsp
<%
// ResultFilter结果过滤类
ResultFilter filter = new ResultFilter();
filter.setStartIndex(start);
filter.setNumResults(range);
//调用Dbforum的threads()方法,获得ForumThreadIterator对象实例
ForumThreadIterator threads = forum.threads(filter);
......
while (threads.hasNext()) {
//对thead进行遍历
ForumThread thread = (ForumThread)threads.next();
//得到thread的ID
long threadID = thread.getID();
//得到线索的根帖子rootMessage
ForumMessage rootMessage = thread.getRootMessage();
//得到帖子主题和作者等信息
String subject = rootMessage.getSubject();
User author = rootMessage.getUser();
......
}
%>
DbForum.java
public class DbForum implements Forum, Cacheable {
......
public ForumThreadIterator threads(ResultFilter resultFilter) {
//生成SQL语句
String query = getThreadListSQL(resultFilter, false);
//得到threadID块
long [] threadBlock = getThreadBlock(query.toString(),
resultFilter.getStartIndex());
......
//返回ForumThreadBlockIterator对象
return new ForumThreadBlockIterator(threadBlock, query.toString(),
startIndex, endIndex, this.id, factory);
}
protected long[] getThreadBlock(String query, int startIndex) {
int blockID = startIndex / THREAD_BLOCK_SIZE;
int blockStart = blockID * THREAD_BLOCK_SIZE;
String key = query + blockID;
//根据Key的值到缓存中取得ThreadID的数组
CacheableLongArray longArray =(CacheableLongArray)threadListCache.get(key);
//在缓存中则返回
if (longArray != null) {
long [] threads = longArray.getLongArray();
return threads;
}
// 否则到数据库中取ThreadID的块,以数组形式返回
else {
LongList threadsList = new LongList(THREAD_BLOCK_SIZE);
Connection con = null;
Statement stmt = null;
...数据库操作 ...
}
long [] threads = threadsList.toArray();
//将 ThreadID的块加入缓存
threadListCache.add(key, new CacheableLongArray(threads));
return threads;
}
......
}
在forum.jsp中有一个ResultFilter类的实例resultFilter。它给出页面显示Thread的起始位置和数量,并作为参数传入forum.threads()中,用于构造相关的SQL语句。当调用forum.threads(filter)时,程序将生成的SQL语句传入到getThreadBlock()方法中得到一个threadID的块,也就是一组threadID。之所以要读threadID块,是因为显示论坛时并不是显示一条线索就行了,而是一下显示十几条。这样做可以避免反复读数据库,而且threadID不是thread对象,并不占太大空间。
应该说使用了块以后,减轻了数据库的访问量,因而论坛的效率有了很大的提高。不仅如此,Jive又把块放入了缓存中。在getThreadBlock()方法里,Jive用Cache类的实例对象threadListCache来缓存threadID块,而关键字就是SQL语句加上blockID。也就是说,只要SQL语句和blockID相同,就可以在缓存中取出相同的threadID块。当然,缓存中找不到,还是要到数据库中读出来加入缓存的,这样论坛的效率又得到了进一步的提升。
ForumThreadBlockIterator类继承自ForumThreadIterator抽象类,而ForumThreadIterator类又实现了Iterator接口,因此得到ForumThreadBlockIterator的实例对象threads后,就可以在用threads.next()方法对它进行编历了。ForumThreadBlockIterator类的功能就是逐个读取ThreadID,然后根据ThreadID返回Thread对象,由此上层访问接口就和中间层衔接起来了。
小结
Jive的缓存机制值得学习的地方有很多,比如读取线索时不是读一条而是读一个block;显示线索的起始位置和数量用专门的一个类来管理,并且动态生成SQL语句;用一个专门的类来负责管理缓存;把论坛缓存对象的功能抽象出来形成一个缓存的抽象类DatabaseCache,让它去跟低层数据结构联系起来等。这些都体现了面向对象的设计原则,即提高软件的可维护性和可复用性。
同时,Jive也告诉我们,要想编好程序,只懂条件语句和循环语句可不行,还要必须选择好的数据结构,掌握好的面向对象的设计原则,熟悉设计模式思想方法,这样才能编写出强壮、高效的代码。