天天看点

webroller 源代码分析 【转载】

[size=large]Roller中一些概念和定义 [/size]

最近在研究Roller源代码.发现Roller文档和源代码中有一些容易让人混淆和模糊的概念(至少对我来说),在这里记下来.

Roller: 博客服务器, 能够同时支持多个用户(博客)的服务器软件,同时可以支持组网志(Group Weblog,意思是多个用户共同编辑的网志)

Weblog 网志. 一个用户可以拥有多个网志,(包括组网志),在文档中就是Weblogs,在源代码中就是website. 注意: 源代码中的website指的是一个用户下面的weblog, 而不是Roller服务器的website.

Front page 主页面: 每个Roller服务器都有一个主页面, 就是进站页面. 注意和每个用户的主页面(通常叫做主菜单 Main Menu)区分.

管理员: Roller安装的时候默认将管理员权限授予第一个注册用户,由他来管理或者继续授权给别人.

Main Menu 主菜单: 一个用户的主页面, 包含对网志的管理等功能面板

Handle 句柄: 这里Handle指的是网志的代码,例如有一个网志adminblog, 代码就是"adminblog", Roller服务器程序通过这个代码来标志这个网志,进行定位.

Editor interface 编辑接口: Roller文档中使用编辑接口这个名词, 这个概念很模糊, 基本上,如果你进入了编辑接口模式,那么一个状态栏就会出现, 一般来说, 左边是你登陆的名字和正在编辑的网志, 右边是主页面,主菜单,退出登录等连接. 只要有状态栏出现, 你就位于编辑接口中.

weblog navbar 导航栏: 当你在一个网志中需要回到编辑接口中时,可以通过导航栏, 每个网志的主题(theme)都会包含导航栏.

editor page 编辑页面: Roller中编辑页面指的是网志中创建新条目的页面. 用户可以输入新条目的标题,标签,内容等等.

permalink 永久连接: Roller中指向该条目的只读的永久性连接. 这个连接在用户第一次保存条目的时候,根据当时的标题设定,并且以后不能更改.

Category 目录/分类: 在Roller中一个条目只能隶属一个目录/分类. (在Atom 协议中, 一个条目可以有多个目录/分类, Roller只保留一个)

Tag 标签: Web2.0 标签, 在Roller中, 标签用来标记条目所描述的内容和分类, 多个标签用空格分开, 当用户输入时, Roller会使用已有的标签来提示.

[size=large]Hibernate generator options[/size]

以前用Hibernate时,PK的generator项一直用的是native,也没想为什么,就是看到书上是这么用的。今天看了看roller的源代码,发现它用的竟然是uuid.hex。上网查了查,大家都说uuid.hex的数据库无关性最好。

generator的option有很多,用的比较多的有assigned, native, sequence, identity, uuid.hex。assigned是默认选项,顾名思义,这个选项就是table的PK由应用程序提供,不受Hibernate的管理,有些DB- independent的PK产生机制就是利用此选项,这些模式有1)在数据库中建一个表,每行保存一个表当前最大的PK值。2)利用数据库的 stored procedure call来产生PK。sequence, identity实际上用的是DB内置的PK产生机制,只不过不同的DB用不同的option罢了,所以一般用native比较好。还有一个 increment选项,他的功能和前几个差不多,但是他仅对当前Hibernate程序唯一,故不能用在集群上。uuid.hex是在内存产生的全局唯一的字符串,128bits,表示为16进制后需32个字符,占用空间远大于前面仅需4bytes的int的native等,所以在查询,索引上的效率较差。但由于其完全的数据库无关,所以此选项可提高程序的可移植性

[size=large]domain model[/size]

和上一篇隔了很长时间了,现在接着写。

主要谈一下roller的domain model,下图是david johnson画的E-R图,包含了所有的数据库表结构

我们一个一个来讲这些表:

(1)website => Weblog(表示website表对应领域模型里的Weblog类,下同)

代表一个blog,一个网站可有多个blog,每个blog拥有全局唯一的handle,这个blog于是可以用http://www.domain.com/handle来访问。

(2)webpage=> WeblogTemplate

代表主题中的一个template网页,roller自带了几个主题,如basic,metal等,每个主题由几个velocity模板和CSS定义组成,webpage表中的一行就代表了一个个velocity模板或CSS。

(3)weblogentry=>WeblogEntry 代表blog中的一篇文章

(4)roller_weblogentrytag=>WeblogEntryTag roller_weblogentrytagagg=>WeblogEntryTagAggregate

文章的tag和对tag的统计,WeblogEntryTagAggregate实际是每个tag在整个blog中出现的次数的统计,可用来实现tag cloud功能

(5)roller_comment=>WeblogEntryComment 评论

(6)bookmark=>WeblogBookmark,folder=>WeblogBookmarkFolder

bookmark相当于“友情链接”,这些链接能用文件夹组织起来

(7)category=>WeblogCategory 用于组织entry

(8)rolleruser=>User 用户

(9)userrole=>UserRole

用户的角色,有两个值“admin”或“editor”,这是对全局而言的角色,对某个blog的角色由permission决定

(10)permissions=>WeblogPermission

user和blog是多对多的关系,通过permissions表关联,有3中permissions:“admin”, "author", "limited"。

还有其他一些表,都是不太重要或和blog的主要功能关系不大,没研究,就不说了

[size=large]业务层的入口[/size]

WebloggerFactory是整个业务层的入口,一般的用法是:

Weblogger weblogger = WebloggerFactory.getWeblogger();

UserManager um = weblogger.getUserManager();

WeblogManager wm = weblogger.getWeblogManager();

. . . .

它的内部机制如下图:

我们可以看到,他的内部是通过Guice这个IOC容器来实现的,通过JPAWebloggerModule和 HibernteWebloggerModule实现不同的底层实现绑定。比如,当我们在roller.properties中将 guice.backend.module=org.apache.roller.weblogger.business.hibernate.HibernateWebloggerModule 时,调用weblogger.getUserManager(),返回的是HibernateUserManagerImpl实例。

[size=x-small]

访问计数[/size]

一直想知道像访问计数这样频繁发生但数据量很小的数据库更新应该如何处理,看了Roller才恍然大悟。其实很简单,就是缓冲。

这个功能主要是通过HitCountQueue来实现的,它实际上就是一个允许并发访问的List,每次访问一个blog,这个blog的handle就放入这个队列中,代表一次访问。在实例化这个队列的时候,同时开启一个线程ContinuousWorkerThread,每隔3分钟将队列中的数据同步到数据库中。

[size=large]搜索[/size]

实现Roller的搜索功能的包是org.apache.roller.weblogger.business.search及其子包 org.apache.roller.weblogger.business.search.operations。依然是经典的Facade模式,由 Interface IndexManager提供总的访问接口,IndexMangerImpl提够实现,如下图所示。

观看IndexManger接口,可知它主要提够了6个操作:

1)public void removeWebsiteIndex(Weblog website) throws WebloggerException

删除整个Website的index,由RemoveWebsiteIndexOperation提够实现,由一个线程在后台执行的。

2)public void removeEntryIndexOperation(WeblogEntry entry) throws WebloggerException

删除Entry的Index,由RemoveEntryOperation提够实现,由一个线程在前台立即执行,以便及时反应结果

3)public void addEntryIndexOperation(WeblogEntry entry) throws WebloggerException

添加Entry的Index,由AddEntryOperation提够实现,由一个线程在后台执行

4)public void addEntryReIndexOperation(WeblogEntry entry) throws WebloggerException

重新索引Entry,由AddEntryReIndexOperation提够实现,由一个线程在后台执行

5) public abstract void rebuildWebsiteIndex(Weblog website) throws WebloggerException

6) public abstract void rebuildWebsiteIndex() throws WebloggerException

重新索引website,由RebuildWebsiteIndexOperation提够实现,由一个线程在后台执行,其中6是删除所有website 的index,而5仅是删除一个website的index,因为Lucene没有提供delete_all方法,所以实现的时候,每个index的 Document都加上一个额外的Field(FieldConstants.CONSTANT),且有相同的域值(FieldConstants.CONSTANT_V)。

从上图我们可以看到,IndexOperation主要分为读操作(ReadFromIndexOperation)和写操作(WriteToIndexOperation),做这样的区分主要是为了并发,通过一个读写锁,读操作获取读锁,写操作获取写锁,这样就能获得最大的并发效果。

我们可以看到实际上,IndexManager并未提供对Search的支持,这应该算是设计上的一个失败,而且在源代码中,方法的接口直接使用的是 IndexManagerImpl,使得IndexManger这个接口更加没有存在的意义,实际上在Roller中这样的设计上的不优美随处可见,老外的东西也不一定就好呀:-)。

继续阅读