jforum源碼分析
這次針對(duì)的是jforum2.1.8,大概jforum團(tuán)隊(duì)已經(jīng)沒(méi)心情理這個(gè)版本了,都沖著jforum3去了。選擇這個(gè)版本,主要是因?yàn)閖forum在java論壇類(lèi)應(yīng)用中算是佼佼者了,很多人都拿這個(gè)來(lái)...
文/For A
怎么才算好的源碼分析呢?當(dāng)然我這個(gè)肯定不算。我想大概分為幾個(gè)層面吧,寫(xiě)寫(xiě)注釋那算最基本的了,寫(xiě)寫(xiě)要點(diǎn)思路和難點(diǎn),算是還不錯(cuò)拉,再難的就是跳出源碼舉一反三,形成自己的一套思路吧。
這次針對(duì)的是jforum2.1.8,大概jforum團(tuán)隊(duì)已經(jīng)沒(méi)心情理這個(gè)版本了,都沖著jforum3去了。選擇這個(gè)版本,主要是因?yàn)閖forum在java論壇類(lèi)應(yīng)用中算是佼佼者了,很多人都拿這個(gè)來(lái)做二次開(kāi)發(fā),而jforum3使用的是另外一套架構(gòu)了,而且還沒(méi)完全release,所以斟酌一下,還是選擇這個(gè)經(jīng)典的版本。
關(guān)于jforum的介紹網(wǎng)上已經(jīng)很多了,這里也簡(jiǎn)單抄錄一段:JForum 是一個(gè)功能強(qiáng)大 ,易于管理的論壇。它的設(shè)計(jì)完全遵從MVC設(shè)計(jì)模式,能夠在任何Servlet容器與EJB服務(wù)器上運(yùn)行。而且可以輕松的定制與擴(kuò)展JForum論壇。
上面這段簡(jiǎn)述還是中肯的。另外,jforum是模仿phpbb寫(xiě)的,使用的是classic-blue風(fēng)格,但不能自己選擇風(fēng)格,要的話(huà)只能自己修改了。
再說(shuō)幾句,說(shuō)jforum比較優(yōu)秀是因?yàn)閖ava開(kāi)源的論壇系列精品少,而且jforum的bug也真的不少,不信試試就知道了。不過(guò)作為一個(gè)成型的組件,功能強(qiáng)大并且適合二次開(kāi)發(fā),還是應(yīng)該列入考慮范圍的。
不管怎樣,jforum是個(gè)不錯(cuò)的學(xué)習(xí)范本,至少讓你覺(jué)得寫(xiě)個(gè)山寨框架不是什么難事,而事實(shí)也的確是這樣的。重要的一點(diǎn)是,不要輕易拿出來(lái)害人就是了:)這里先列舉出可能一些分析點(diǎn):
web.xml
初始化流程
處理請(qǐng)求流程(mvc)
文件監(jiān)控
緩存實(shí)現(xiàn)
數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)實(shí)現(xiàn)
權(quán)限控制
首先了解一個(gè)web應(yīng)用,首要的就是知道處理流程。首先來(lái)看看入口web.xml,里邊的內(nèi)容還是挺清晰的,可以看到里邊有個(gè)監(jiān)聽(tīng)器ForumSessionlistener,*.page的過(guò)濾器ClickstreamFilter,還有2個(gè)*.page的處理器,其中InstallServlet是安裝相關(guān)的,JForum則是前端處理器。基本上整個(gè)流程就是client request -> ForumSessionlistener -> ClickstreamFilter -> JForum -> server response.
ForumSessionlistener實(shí)現(xiàn)了HttpSessionlistener接口,但是只是對(duì)session destory做了處理,在這個(gè)過(guò)程中,保存session的歷史記錄到DB,并清除用戶(hù)信息和相關(guān)的security信息。
ClickstreamFilter實(shí)現(xiàn)了Filter接口,主要的任務(wù)就交給BotChecker了,是用來(lái)檢測(cè)client是不是一個(gè)robot來(lái)的。
主要的工作還是在JForum上面,不過(guò)先來(lái)看看jforum是怎么檢測(cè)robot的?
BotChecker只有一個(gè)靜態(tài)工具方法isBot,首先是檢測(cè)是否請(qǐng)求robot.txt(這是標(biāo)準(zhǔn)的robot協(xié)議文件),接下去判斷User-Agent頭部,最后是判斷remotehost。而已知的robot都是寫(xiě)在文件clickstream-jforum.xml里邊的(包括agent和host),并通過(guò)ConfigLoader加載進(jìn)來(lái)的(SAX方式)。
可以看到JForum和InstallServlet都繼承了JForumBaseServlet這個(gè)HttpServlet,而JForumBaseServlet包括2個(gè)重要的方法init和startApplication。眾所周知,init是servlet初始化時(shí)調(diào)用的方法,JForumBaseServlet里邊的init方法的流程是:
調(diào)用父類(lèi)的init(正常情況這是必須調(diào)用的) -> 配置log4j -> startSystemglobals(加載全局參數(shù)配置SystemGlobals.properties -> 加載數(shù)據(jù)庫(kù)配置database.driver.config(如MySQL就是WEB-INF/config/database/mysql/mysql.properties) -> 加載自定義配置(默認(rèn)的是jforum-custom.conf)) -> 配置緩存引擎 -> 配置freemarker模板引擎 -> 加載模塊配置modulesMapping.properties -> 加載url映射配置urlPattern.properties -> 加載I18n配置(languages/*) -> 加載頁(yè)面映射配置(templatesMapping.properties) -> 加載BBcode配置bb_config.xml -> 結(jié)束
jforum實(shí)現(xiàn)了自己的mvc,整個(gè)mvc的脈絡(luò)就是client request -> 解析url(urlPattern.properties),獲取module/action/param -> 通過(guò)module獲取相應(yīng)的module class,并通過(guò)action識(shí)別并調(diào)用相應(yīng)的方法(modulesMapping.properties) -> 使用dao完成業(yè)務(wù)邏輯 -> 調(diào)用template進(jìn)行渲染(templatesMapping.properties),其實(shí)整個(gè)mvc和struts沒(méi)什么兩樣的,具體的流程以后再提。
JForumBaseServlet里邊的startApplication方法的流程是:
加載通用sql文件sql.queries.driver(就是/database/generic/generic_queries.sql) -> 加載特定sql文件(如mysql就是/database/mysql/mysql.sql) -> 加載Quartz定時(shí)任務(wù)配置 -> 加載登錄驗(yàn)證器(驗(yàn)證方式) -> 加載Dao實(shí)現(xiàn)方式 -> 加載文件修改監(jiān)聽(tīng)器 -> 加載查詢(xún)索引管理器 -> 加載定時(shí)統(tǒng)計(jì)任務(wù)
jforum實(shí)現(xiàn)了自己的orm,當(dāng)然不是hibernate那種,是類(lèi)似ibatis的那種sql mapping,并提供了多套的sql文件來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)無(wú)關(guān)的特性,整個(gè)流程也是比較清晰的,加載數(shù)據(jù)庫(kù)配置 -> 加載sql mapping file -> 設(shè)置DAO實(shí)現(xiàn) -> 通過(guò)named sql找到對(duì)應(yīng)的sql(在*.sql里邊對(duì)應(yīng)著) -> 運(yùn)行出數(shù)據(jù)
繼續(xù)重點(diǎn)。JForum的init流程如下:
JForumBaseServlet.init -> JForumBaseServlet.startApplication -> 啟動(dòng)數(shù)據(jù)庫(kù) -> 預(yù)加載一些數(shù)據(jù)到緩存中(ForumRepository[Categories,Forums,同時(shí)在線(xiàn)最大人數(shù),最后登錄用戶(hù),注冊(cè)用戶(hù)數(shù)等等],用戶(hù)等級(jí),表情數(shù)據(jù),屏蔽列表) -> 結(jié)束
上面簡(jiǎn)單提到了Jforum處理請(qǐng)求的過(guò)程,現(xiàn)在在來(lái)看看這個(gè)過(guò)程,就是service方法,這次采用代碼概要的方式展示:
// 初始化JForumExecutionContext
JForumExecutionContext ex = JForumExecutionContext.get();
// 包裝request和response
request = new WebRequestContext(req);
response = new WebResponseContext(res);
// 檢查數(shù)據(jù)庫(kù)狀態(tài)
this.checkDatabaseStatus();
// 創(chuàng)建JForumContext并設(shè)置到JForumExecutionContext中去
.......
JForumExecutionContext.set(ex);
// 刷新session
utils.refreshSession();
// 加載用戶(hù)權(quán)限
SecurityRepository.load(SessionFacade.getUserSession().getUserId());
// 預(yù)加載模板需要的上下文
utils.prepareTemplateContext(context, forumContext);
// 從request中解析module name
String module = request.getModule();
// module name -> module class
String moduleClass = module != null ? ModulesRepository.getModuleClass(module) : null;
// 判斷是否在ban list里邊
......
boolean shouldBan = this.shouldBan(request.getremoteAddr());
// 主角出場(chǎng)
out = this.processCommand(out, request, response, encoding, context, moduleClass);
// 掃尾工作,例如db的rollback
this.handleFinally(out, forumContext, response);
processCommand會(huì)調(diào)用Command的process方法:
// 獲取一個(gè)module實(shí)例(繼承了Command)
Command c = this.retrieveCommand(moduleClass);
// 進(jìn)入process
Template template = c.process(request, response, context);
// 這里開(kāi)始是process方法
//獲取action
String action = this.request.getAction();
//如果不是ignore的,就調(diào)用這個(gè)action
if (!this.ignoreAction) {this.getClass().getMethod(action, NO_ARGS_CLASS).invoke(this, NO_ARGS_OBJECT);}
//如果是轉(zhuǎn)發(fā)的,就把TemplateName清空
if (JForumExecutionContext.getredirectTo() != null) {this.setTemplateName(TemplateKeys.EMPTY);}
//不是轉(zhuǎn)發(fā)且attribute里邊存在template,則設(shè)置為templateName
else if (request.getAttribute("template") != null) {this.setTemplateName((String)request.getAttribute("template"));}
//是否coustomContent?例如下載,驗(yàn)證碼子類(lèi)的不需要頁(yè)面的操作
if (JForumExecutionContext.isCustomContent()) {return null;}
//返回一個(gè)template
return JForumExecutionContext.templateConfig().getTemplate(
new StringBuffer(SystemGlobals.getValue(ConfigKeys.TEMPLATE_DIR)).
append('/').append(this.templateName).toString());
}
// 從process出來(lái),回到processCommand
// 設(shè)置content type
response.setContentType(contentType);
//生成頁(yè)面并flush
if (!JForumExecutionContext.isCustomContent()) {
out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), encoding));
template.process(JForumExecutionContext.getTemplateContext(), out);
out.flush();
}
}
這是一般的流程,就像上面提到的customContent,就是要自己處理了,可以參考CaptchaAction.generate().
-
無(wú)相關(guān)信息