作者:xuefengl(dev2dev ID)
摘要:
本文講述如何在J2EE平臺上創(chuàng)建一個Blog系統(tǒng),以.Text的功能和界面為原型,Springframework為框架,實現(xiàn)一個運行在WebLogic Server上的靈活的多層結(jié)構(gòu)的Blog平臺。
目錄:
1. 設(shè)計目標(biāo)
2. 開發(fā)環(huán)境
2.1. 選擇平臺和框架
2.2. 配置服務(wù)器
2.3. 編寫Ant腳本
3. 系統(tǒng)設(shè)計
3.1. 持久層設(shè)計
3.1.1. 設(shè)計Domain對象
3.1.2. 配置iBatis
3.1.3. 使用DAO模式
3.2. 邏輯層設(shè)計
3.3. Web層設(shè)計
3.3.1. 使用MVC模式
3.3.2. 實現(xiàn)Skin
4. 附加功能
4.1.1. 實現(xiàn)圖片上傳
4.1.2. 生成縮略圖
4.1.3. 實現(xiàn)RSS
4.1.4. 實現(xiàn)全文搜索
4.1.5. 發(fā)送Email
5. 測試
6. 中文支持
7. 總結(jié)
8. 源代碼下載
9. 相關(guān)資源下載
10. 參考
11. 關(guān)于作者
設(shè)計目標(biāo) (目錄)
Blog(WebLog)在Internet上越來越流行。許多網(wǎng)友都有了自己的Blog,通過Blog展示自己,結(jié)識更過的網(wǎng)友。比較著名的Blog平臺是基于ASP.net的開源項目.Text。但是它的邏輯全部以存儲過程的形式放在數(shù)據(jù)庫中。雖然存儲過程能大大提高數(shù)據(jù)操作的效率,但是存儲過程本身是結(jié)構(gòu)化的程序,無法發(fā)揮面向?qū)ο蟮耐Γ膊槐阌趯崿F(xiàn)代碼復(fù)用。因此,我決定實現(xiàn)一個基于J2EE體系的多層結(jié)構(gòu)的Blog平臺,功能和界面和.Text非常類似,暫命名為Crystal Blog。實現(xiàn)的功能有:發(fā)表和編輯文章;多用戶支持;全文檢索;RSS支持;圖片管理;SMTP郵件發(fā)送等常見功能。界面如下:

選擇平臺和框架 (目錄)
由于使用J2EE平臺,我們準(zhǔn)備采用WebLogic Server 8.1作為運行平臺,使用WebLogic Workshop8.1這個強大的集成化IDE作為開發(fā)工具。
數(shù)據(jù)庫選擇MS SQL Server 2000 SP3,建立一個名為blog的數(shù)據(jù)庫存儲所有的用戶數(shù)據(jù)。由于我們并沒有針對特定數(shù)據(jù)庫編碼,稍后我們會使用其他數(shù)據(jù)庫測試。在系統(tǒng)設(shè)計之前,選擇一個優(yōu)秀的框架能大大提高開發(fā)效率。Spring是一個輕量級的J2EE框架。它覆蓋了從后臺數(shù)據(jù)庫的JDBC封裝到前臺Web框架的幾乎所有方?面。并且,Spring的各個模塊耦合非常松散,我們既可以用它作為整個應(yīng)用程序的框架,也可以僅僅使用它的某一個模塊。此外,Spring非常強大的集成功能使我們可以輕易地集成Struts編寫的Web端,或者使用Hibernate作為后端的O/R Mapping方案。
Spring的核心思想便是IoC和AOP,Spring本身是一個輕量級容器,和EJB容器不同,Spring的組件就是普通的Java Bean,這使得單元測試可以不再依賴容器,編寫更加容易。Spring負(fù)責(zé)管理所有的Java Bean組件,同樣支持聲明式的事務(wù)管理。我們只需要編寫好Java Bean組件,然后將它們“裝配”起來就可以了,組件的初始化和管理均由Spring完成,只需在配置文件中聲明即可。這種方式最大的優(yōu)點是各組件的耦合極為松散,并且無需我們自己實現(xiàn)Singleton模式。
由于后臺要使用關(guān)系數(shù)據(jù)庫存儲數(shù)據(jù),使用O/R Mapping必不可少。iBatis是又一個類似于Hibernate的O/R Mapping方案,特點是小巧,配置簡單,查詢靈活,完全符合我們的要求。
除了Spring和iBatis,用到的第三方組件還有:用于全文搜索的Lucene引擎,用于文件上傳的common-file-upload 1.0,用于輸出RSS的RSSLibJ1.0 RC2。
由于使用Spring這個輕量級框架,就無需EJB服務(wù)器,只需要Web服務(wù)器即可。因此,系統(tǒng)可以運行在WebLogic Server,Tomcat和Resin等支持Servlet和JSP的Web服務(wù)器上。
系統(tǒng)設(shè)計 (目錄)
很顯然,多層結(jié)構(gòu)的J2EE架構(gòu)能保證系統(tǒng)的靈活性和可擴展性。我們?nèi)匀徊捎帽硎緦?邏輯層/持久層三層設(shè)計。

整個系統(tǒng)以Spring為基礎(chǔ),持久層采用DAO模式和iBatis O/R Mapping,封裝所有數(shù)據(jù)庫操作;中間層是由Spring管理的普通的JavaBean,采用Fa?ade模式;表示層使用Spring提供的MVC框架。由于Spring對其他框架的良好集成,我們采用Velocity作為View。由于Velocity不能調(diào)用Java代碼,從而強制使用MVC模式而不是在View中嵌入邏輯代碼。
配置服務(wù)器 (目錄)
在WebLogic中新建一個Configuration,命名為blog,添加一個數(shù)據(jù)源,命名為jdbc/blog:

整個應(yīng)用程序的目錄結(jié)構(gòu)如下:
crystalblog/
+ doc/ (存放API文檔)
+ report/ (存放JUnit測試結(jié)果)
+ src/ (存放java源程序)
+ web/ (web目錄)
| + manage/ (存放blog管理頁)
| + skin/ (存放blog界面頁)
| + upload/ (存放用戶上傳的圖片)
| + WEB-INF/
| + classes/ (存放編譯的class文件)
| + lib/ (存放用到的所有jar文件)
| + search/ (存放Lucene的index)
| + c.tld (使用jstl必須的文件)
| + dispatcher-servlet.xml (Spring配置文件)
| + web.xml (標(biāo)準(zhǔn)web配置文件)
+ blog.war (打包的可部署應(yīng)用)
+ build.xml (ant腳本)
編寫Ant?腳本 (目錄)
Ant是一個非常棒的執(zhí)行批處理任務(wù)的工具。使用Ant能使編譯、測試、打包、部署和生成文檔等一系列任務(wù)全自動化,從而大大節(jié)省開發(fā)時間。
首先我們把用到的所有.jar文件放到/web/WEB-INF/lib中,然后編寫compile任務(wù),生成的class文件直接放到web/WEB-INF/classes目錄下。如果編譯成功,就進(jìn)行單元測試,單元測試的結(jié)果以文本文件存放在report目錄中。如果測試通過,下一步便是打包成blog.war文件。接著把應(yīng)用部署到服務(wù)器上,直接將web目錄的內(nèi)容復(fù)制到%BEA_HOME%/user_projects/domains/blogdomain/applications/blog/目錄下即可。如果要在Tomcat上部署,直接將整個web目錄復(fù)制到%TOMCAT%/webapps/blog/下。
最后,如果需要,可以用javadoc生成api文檔。
系統(tǒng)設(shè)計 (目錄)
Crystal Blog共分成三層結(jié)構(gòu):后臺數(shù)據(jù)持久層,采用DAO模式;中間邏輯層,采用Facade模式;前端Web層,采用MVC結(jié)構(gòu),使用JSP作為視圖。以下是Rational Rose的UML圖:
設(shè)計Domain對象 (目錄) 設(shè)計Domain對象
Domain層是抽象出的實體。根據(jù)我們要實現(xiàn)的功能,設(shè)計以下實體,它們都是普通的Java Bean:
Account:封裝一個用戶,包括用戶ID,用戶名,口令,用戶設(shè)置等等。
Category:封裝一個分類,一共有3種Category,分別用來管理Article,Image和Link,一個Account對應(yīng)多個Category。
Article:封裝一篇文章,包括Title,Summary,Content等等,一個Category對應(yīng)多個Article。
Feedback:封裝一個回復(fù),包括Title,Username,Url和Content,一個Article對應(yīng)多個Feedback。
Image:封裝一個圖片,Image只包含圖片信息(ImageId,Type),具體的圖片是以用戶上傳到服務(wù)器的文件的形式存儲的。一個Category對應(yīng)多個Image。
Link:封裝一個鏈接,和Category是多對一的關(guān)系。有Title,Url,Rss等屬性。
Message:封裝一個消息,使其他用戶在不知道Email地址的情況下能夠通過系統(tǒng)發(fā)送郵件給某個用戶。
最后,為了唯一標(biāo)識每條數(shù)據(jù)庫記錄,我們需要一個主鍵。在MS SQL Server和Oracle中可以使用自動遞增的主鍵生成方式。但是很多數(shù)據(jù)庫不支持自動遞增的主鍵,考慮到移植性,我們自己定義一個Sequence表,用于生成遞增的主鍵。Sequence表有且僅有7條記錄,分別記錄Account到Message對象的當(dāng)前最大主鍵值。系統(tǒng)啟動時,由SqlConfig負(fù)責(zé)初始化Sequence表。
SequenceDao負(fù)責(zé)提供下一個主鍵,為了提高效率,一次緩存10個主鍵。
配置iBatis (目錄)
接下來,使用iBatis實現(xiàn)O/R Mapping。首先從http://www.ibatis.com下載iBatis 2.0,將所需的jar文件復(fù)制到web/WEB-INF/lib/目錄下。iBatis使用XML配置數(shù)據(jù)庫表到Java對象的映射,先編寫一個sql-map-config.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings cacheModelsEnabled="false" enhancementEnabled="true"
lazyLoadingEnabled="true" maxRequests="32"
maxSessions="10" maxTransactions="5"
useStatementNamespaces="false"
/>
<transactionManager type="JDBC">
<dataSource type="JNDI">
<property name="DataSource" value="jdbc/blog" />
</dataSource>
</transactionManager>
<!-- 如果有其他xml配置文件,可以包含進(jìn)來 -->
<sqlMap resource="Account.xml" />
</sqlMapConfig>
將sql-map-config.xml放到web/WEB-INF/classes/目錄下,iBatis就能搜索到這個配置文件,然后編寫一個初始化類:
public class SqlConfig {
private SqlConfig() {}
private static final SqlMapClient sqlMap;
static {
try {
java.io.Reader reader = Resources.getResourceAsReader ("sql-map-config.xml");
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error initializing SqlConfig. Cause: " + e);
}
}
public static SqlMapClient getSqlMapInstance () {
return sqlMap;
}
}
SqlMapClient封裝了訪問數(shù)據(jù)庫的大部分操作,可以直接使用SqlConfig.getSqlMapInstance()獲得這個唯一實例。
使用DAO模1 式 (目錄)
為了分離邏輯層和數(shù)據(jù)庫持久層,定義一系列DAO接口:AccountDao,CategoryDao,ArticleDao……其實現(xiàn)類對應(yīng)為SqlMapAccountDao,SqlMapCategoryDao,SqlMapArticleDao……這樣就使得邏輯層完全脫離了數(shù)據(jù)庫訪問代碼。如果將來需要使用其它的O/R Mapping方案,直接實現(xiàn)新的DAO接口替代現(xiàn)有的SqlMapXxxDao即可。
以SqlMapAccountDao為例,實現(xiàn)一個login()方法是非常簡單的:
public int login(String username, String password) throws AuthorizationException {
try {
Map map = new HashMap();
map.put("username", username);
map.put("password", password);
Integer I = (Integer)sqlMap.queryForObject("login", map);
if(I==null)
throw new RuntimeException("Failed: Invalid username or password.");
return I.intValue();
}
catch(SQLException sqle) {
throw new RuntimeException("Sql Exception: " + sqle);
}
}
在Account.xml配置文件中定義login查詢:
<select id="login" parameterClass="java.util.Map" resultClass="int">
select [accountId] from [Account] where
[username] = #username# and password = #password#
</select>
邏輯層設(shè)計 (目錄)
由于DAO模式已經(jīng)實現(xiàn)了所有的數(shù)據(jù)庫操作,業(yè)務(wù)邏輯主要是檢查輸入,調(diào)用DAO接口,因此業(yè)務(wù)邏輯就是一個簡單的Facade接口:
public class FacadeImpl implements Facade {
private AccountDao accountDao;
private ArticleDao articleDao;
private CategoryDao categoryDao;
private FeedbackDao feedbackDao;
private ImageDao imageDao;
private LinkDao linkDao;
private SequenceDao sequenceDao;
}
對于普通的getArticle()等方法,F(xiàn)acade僅僅簡單地調(diào)用對應(yīng)的DAO接口:
public Article getArticle(int articleId) throws QueryException {
return articleDao.getArticle(articleId);
}
對于需要身份驗證的操作,如deleteArticle()方法,F(xiàn)acade需要首先驗證用戶身份:
public void deleteArticle(Identity id, int articleId) throws DeleteException {
Article article = getArticleInfo(articleId);
if(article.getAccountId()!=id.getAccountId())
throw new AuthorizationException("Permission denied.");
articleDao.deleteArticle(articleId);
}
要分離用戶驗證邏輯,可以使用Proxy模式,或者使用Spring的AOP,利用MethodInterceptor實現(xiàn),不過,由于邏輯很簡單,完全可以直接寫在一塊,不必使用過于復(fù)雜的設(shè)計。
至此,我們的Blog已經(jīng)實現(xiàn)了所有的后臺業(yè)務(wù)邏輯,并且提供統(tǒng)一的Facade接口。前臺Web層僅僅依賴這個Facade接口,這樣,Web層和后臺耦合非常松散,即使替換整個Web層也非常容易。
Web層設(shè)計 (目錄)
使用MVC模式 (目錄)
對于復(fù)雜的Web層,使用MVC模式是必不可少的。雖然Spring能輕易集成Struts,WebWorks等Web框架,但Spring本身就提供了一個非常好的Web框架,能完全實現(xiàn)MVC模式。
Spring使用一個DispatcherServlet,所有的特定請求都被轉(zhuǎn)發(fā)到DispatcherServlet,然后由相應(yīng)的Controller處理,Controller返回一個ModelAndView對象(因為Java語言的方法調(diào)用只能返回一個結(jié)果,而且不支持ref參數(shù),所以將Model和View對象合在一起返回),Model是一個Java對象,通常是Map,View是視圖的邏輯名字,通常是JSP文件名,但也可以使用Velocity等作為視圖。返回的View通過viewResolver得到真正的文件名。
首先配置Spring的MVC,在web.xml中聲明DispatcherServlet,處理所有以.c結(jié)尾的請求:
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.c</url-pattern>
</servlet-mapping>
</web-app>
Spring會在WEB-INF下查找一個名為dispatcher-servlet.xml的文件,我們需要創(chuàng)建這個文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
</beans>
用到的所有的Java Bean組件都要在這個文件中聲明和配置,以下是配置URL映射的Bean:
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/article.c">articleController</prop>
</props>
</property>
</bean>
凡是匹配/article.c的Request都會被名為articleController的Bean處理,同樣需要聲明這個articleController:
<bean id="articleController" class="example.ViewArticleController">
</bean>
ViewArticleController處理請求,然后生成Model,并選擇一個View:
public class ViewArticleController implements Controller {
private Facade facade;
public void setFacade(Facade facade) { this.facade = facade; }
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 獲得參數(shù):
int articleId = Integer.parseInt(request.getParameter("articleId"));
// 使用facade處理請求:
Article article = facade.getArticle(articleId);
// 生成Model:
Map map = new HashMap();
map.put("article", article);
// 返回Model和視圖名“skin/blueskysimple/article”:
return new ModelAndView("skin/blueskysimple/article", map);
}
}
最后,skin/bluesky/article視圖會將結(jié)果顯示給用戶。
我們注意到,ViewArticleController并不自己查找或者創(chuàng)建Facade,而是由容器通過setFacade(Facade)方法設(shè)置的,這就是所謂的IoC(Inversion of Control)或者Dependency Injection。容器通過配置文件完成所有組件的初始化工作:
<!-- 聲明一個Facade -->
<bean id="facade" class="example.Facade" />
<!-- 聲明一個Controller -->
<bean id="articleController" class="example.ViewArticleController">
<!-- 為articleController設(shè)置facade屬性 -->
<property name="facade">
<!-- 將名為facade的Bean的引用傳進(jìn)去 -->
<ref bean="facade" />
</property>
</bean>
以上配置文件實現(xiàn)的功能大致為:
Facade facade = new Facade();
ViewArticleController articleController = new ViewArticleController();
articleController.setFacade(facade);
但是我們不必編寫以上代碼,只需在xml文件中裝配好我們的組件就可以了。所有組件由Spring管理,并且,缺省的創(chuàng)建模式是Singleton,確保了一個組件只有一個實例。
此外,所有自定義異常都是RuntimeException,Spring提供的AOP使我們能非常方便地實現(xiàn)異常處理。在Web層定義ExceptionHandler,處理所有異常并以統(tǒng)一的error頁面把出錯信息顯示給用戶,因此,在代碼中只需拋出異常,完全不必在Controller中處理異常:
<bean id="handlerExceptionResolver" class="org.crystalblog.web.ExceptionHandler" />
使用Velocity作為View
采用Velocity作為View的最大的優(yōu)點是簡潔,能通過簡單明了的語法直接在Html中輸出Java變量。Velocity本身是一個模板引擎,通過它來渲染Model輸出Html非常簡單,比如顯示用戶名:
<b>${account.username}</b>
換成JSP不得不寫成:
<b><%=account.getUsername()%></b>
而<%...%>標(biāo)記在Dreamwaver中無法直接看到其含義。如果需要在標(biāo)簽中嵌入JSP就更晦澀了:
<a href="somelink?id=<%=id %>">Link</a>
這種嵌套的標(biāo)簽往往使得可視化Html編輯器難以正常顯示,而Velocity用直接嵌入的語法解決了“ <%...%>”的問題。
在Spring中集成Velocity是非常簡單的事情,甚至比單獨使用Velocity更簡單,只需在dispatcher-servlet.xml中申明:
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
</bean>
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath"><value>/</value></property>
<property name="configLocation"><value>/WEB-INF/velocity.properties</value></property>
</bean>
雖然標(biāo)準(zhǔn)的Velocity模板以.vm命名,但我們的所有Velocity模板頁均以.html作為擴展名,不但用Dreamwaver編輯極為方便,甚至可以直接用IE觀看頁面效果。
實現(xiàn)Skin (目錄)
許多Blog系統(tǒng)都允許用戶選擇自己喜歡的界面風(fēng)格。要實現(xiàn)Skin功能非常簡單,為每個Skin編寫Velocity模板,存放在web/skin/目錄下,然后在Controller中返回對應(yīng)的視圖:
String viewpath = skin.getSkin(account.getSkinId()) + "viewArticle";
由于使用了MVC模式,我們已經(jīng)為每個頁面定義好了Model,添加一個新的skin非常容易,只需要編寫幾個必須的.html文件即可,可以參考現(xiàn)有的skin。
SkinManager的作用是在啟動時自動搜索/skin/目錄下的所有子目錄并管理這些skin,將用戶設(shè)定的skinId映射到對應(yīng)的目錄。目前只有一個skin,您可以直接用可視化Html編輯器如Dreamwaver改造這個skin。
附加功能 (目錄)
實現(xiàn)圖片上傳 (目錄)
用戶必須能夠上傳圖片,因此需要文件上傳的功能。比較常見的文件上傳組件有Commons FileUpload(http://jakarta.apache.org/commons/fileupload/a>)和COS FileUpload(http://www.servlets.com/cos),Spring已經(jīng)完全集成了這兩種組件,這里我們選擇Commons FileUpload。
由于Post一個包含文件上傳的Form會以multipart/form-data請求發(fā)送給服務(wù)器,必須明確告訴DispatcherServlet如何處理MultipartRequest。首先在dispatcher-servlet.xml中聲明一個MultipartResolver:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 設(shè)置上傳文件的最大尺寸為1MB -->
<property name="maxUploadSize">
<value>1048576</value>
</property>
</bean>
這樣一旦某個Request是一個MultipartRequest,它就會首先被MultipartResolver處理,然后再轉(zhuǎn)發(fā)相應(yīng)的Controller。
在UploadImageController中,將HttpServletRequest轉(zhuǎn)型為MultipartHttpServletRequest,就能非常方便地得到文件名和文件內(nèi)容:
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 轉(zhuǎn)型為MultipartHttpRequest:
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 獲得文件:
MultipartFile file = multipartRequest.getFile("file");
// 獲得文件名:
String filename = file.getOriginalFilename();
// 獲得輸入流:
InputStream input = file.getInputStream();
// 寫入文件...
}
生成縮略圖 (目錄)
當(dāng)用戶上傳了圖片后,必須生成縮略圖以便用戶能快速瀏覽。我們不需借助第三方軟件,JDK標(biāo)準(zhǔn)庫就包含了圖像處理的API。我們把一張圖片按比例縮放到120X120大小,以下是關(guān)鍵代碼:
public static void createPreviewImage(String srcFile, String destFile) {
try {
File fi = new File(srcFile); // src
File fo = new File(destFile); // dest
BufferedImage bis = ImageIO.read(fi);
int w = bis.getWidth();
int h = bis.getHeight();
double scale = (double)w/h;
int nw = IMAGE_SIZE; // final int IMAGE_SIZE = 120;
int nh = (nw * h) / w;
if( nh>IMAGE_SIZE ) {
nh = IMAGE_SIZE;
nw = (nh * w) / h;
}
double sx = (double)nw / w;
double sy = (double)nh / h;
transform.setToScale(sx,sy);
AffineTransformOp ato = new AffineTransformOp(transform, null);
BufferedImage bid = new BufferedImage(nw, nh, BufferedImage.TYPE_3BYTE_BGR);
ato.filter(bis,bid);
ImageIO.write(bid, "jpeg", fo);
} catch(Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed in create preview image. Error: " + e.getMessage());
}
}
實現(xiàn)RSS (目錄)
RSS是一個標(biāo)準(zhǔn)的XML文件,Rss閱讀器可以讀取這個XML文件獲得文章的信息,使用戶可以通過Rss閱讀器而非瀏覽器閱讀Blog,我們只要動態(tài)生成這個XML文件便可以了。RSSLibJ是一個專門讀取和生成RSS的小巧實用的Java庫,大小僅25k,可以從http://sourceforge.net/projects/rsslibj/下載rsslibj-1_0RC2.jar和它需要的EXMLjar兩個文件,然后復(fù)制到web/WEB-INF/lib/下。
使用RSSLibJ異常簡單,我們先設(shè)置好HttpServletResponse的Header,然后通過RSSLibJ輸出XML即可:
Channel channel = new Channel();
channel.setDescription(account.getDescription());
baseUrl = baseUrl.substring(0, n);
channel.setLink("http://server-name/home.c?accountId=" + accountId);
channel.setTitle(account.getTitle());
List articles = facade.getArticles(accountId, account.getMaxPerPage(), 1);
Iterator it = articles.iterator();
while(it.hasNext()) {
Article article = (Article)it.next();
channel.addItem("http://server-name/article.c?articleId=" + article.getArticleId(),
article.getSummary(), article.getTitle()
);
}
// 輸出xml:
response.setContentType("text/xml");
PrintWriter pw = response.getWriter();
pw.print(channel.getFeed("rss"));
pw.close();
實現(xiàn)全文搜索 (目錄)
全文搜索能大大方便用戶快速找到他們希望的文章,為blog增加一個全文搜索功能是非常必要的。然而,全文搜索不等于SQL的LIKE語句,因為關(guān)系數(shù)據(jù)庫的設(shè)計并不是為全文搜索設(shè)計的,數(shù)據(jù)庫索引對全文搜索無效,在一個幾百萬條記錄中檢索LIKE '%A%'可能會耗時幾分鐘,這是不可接受的。幸運的是,我們能使用免費并且開源的純Java實現(xiàn)的Lucene全文搜索引擎,Lucene可以非常容易地集成到我們的blog中。
Lucene不提供直接對文件,數(shù)據(jù)庫的索引,只提供一個高性能的引擎,但接口卻出人意料地簡單。我們只需要關(guān)心以下幾個簡單的接口:
Document:代表Lucene數(shù)據(jù)庫的一條記錄,也代表搜索的一條結(jié)果。
Field:一個Document包含一個或多個Field,類似關(guān)系數(shù)據(jù)庫的字段。
IndexWriter:用于創(chuàng)建新的索引,也就是向數(shù)據(jù)庫添加新的可搜索的大段字符串。
Analyzer:將字符串拆分成單詞(Token),不同的文本對應(yīng)不同的Analyzer,如HtmlAnalyzer,PDFAnalyzer。
Query:封裝一個查詢,用于解析用戶輸入。例如,將“bea blog”解析為“同時包含bea和blog的文章”。
Searcher:搜索一個Query,結(jié)果將以Hits返回。
Hits:封裝一個搜索結(jié)果,包含Document集合,能非常容易地輸出結(jié)果。
下一步,我們需要為Article表的content字段建立全文索引。首先為Lucene新建一個數(shù)據(jù)庫,請注意這個數(shù)據(jù)庫是Lucene專用的,我們不能也不必知道它的內(nèi)部結(jié)構(gòu)。Lucene的每個數(shù)據(jù)庫對應(yīng)一個目錄,只需要指定目錄即可:
String indexDir = "C:/search/blog";
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);
indexWriter.close();
然后添加文章,讓Lucene對其索引:
String title = "文章標(biāo)題" // 從數(shù)據(jù)庫讀取
String content = "文章內(nèi)容" // 從數(shù)據(jù)庫讀取
// 打開索引:
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);
// 添加一個新記錄:
Document doc = new Document();
doc.add(Field.Keyword("title", title));
doc.add(Field.Text("content", content));
// 建立索引:
indexWriter.addDocument(doc);
// 關(guān)閉:
indexWriter.close();
要搜索文章非常簡單:
然后添加文章,讓對其索引:
String title = "文章標(biāo)題" // 從數(shù)據(jù)庫讀取
String content = "文章內(nèi)容" // 從數(shù)據(jù)庫讀取
// 打開索引:
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);
// 添加一個新記錄:
Document doc = new Document();
doc.add(Field.Keyword("title", title));
doc.add(Field.Text("content", content));
// 建立索引:
indexWriter.addDocument(doc);
// 關(guān)閉:
indexWriter.close();
要搜索文章非常簡單:
Searcher searcher = new IndexSearcher(dir);
Query query = QueryParser.parse(keyword, "content", new StandardAnalyzer());
Hits hits = searcher.search(query);
if(hits != null){
for(int i = 0;i < hits.length(); i++){
Document doc = hits.doc(i);
System.out.println("found in " + doc.get("title"));
System.out.println(doc.get("content"));
}
}
searcher.close();
我們設(shè)計一個LuceneSearcher類封裝全文搜索功能,由于必須鎖定數(shù)據(jù)庫所在目錄,我們把數(shù)據(jù)庫設(shè)定在/WEB-INF/search/下,確保用戶不能訪問,并且在配置文件中初始化目錄:
<bean id="luceneSearcher" class="org.crystalblog.search.LuceneSearcher">
<property name="directory">
<value>/WEB-INF/search/</value>
</property>
</bean>
效果如下:
(圖4:search)
發(fā)送Email (目錄)
Blog用戶可以讓系統(tǒng)將來訪用戶的留言發(fā)送到注冊的Email地址,為了避免使用SMTP發(fā)信服務(wù)器,我們自己手動編寫一個SendMail組件,直接通過SMTP協(xié)議將Email發(fā)送到用戶信箱。
SendMail組件只需配置好DNS服務(wù)器的IP地址,即可向指定的Email信箱發(fā)送郵件。并且,SendMail使用緩沖隊列和多線程在后臺發(fā)送Email,不會中斷正常的Web服務(wù)。具體代碼請看SendMail.java。
測試 (目錄)
服務(wù)器配置為:P4 1.4G,512M DDR,100M Ethernet,Windows XP Professional SP2。
測試服務(wù)器分別為WebLogic Server 8.1,Tomcat 4.1/5.0,Resin 2.1.1。
測試數(shù)據(jù)庫為MS SQL Server 2000 SP3。如果你使用Oracle或者DB2,MySQL等其他數(shù)據(jù)庫并測試成功,請將SQL初始化腳本和詳細(xì)配置過程發(fā)一份給我,謝謝。
由于時間有限,沒有作進(jìn)一步的調(diào)優(yōu)。WebLogic Server和iBatis有很多優(yōu)化選項,詳細(xì)配置可以參考相關(guān)文檔。
中文支持 (目錄)
測試發(fā)現(xiàn),中文不能在頁面中正常顯示,為了支持中文,首先在web.xml加入Filter,用于將輸入編碼設(shè)置為gb2312:
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.crystalblog.web.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
然后用文本工具搜索所有的.htm,.html,.properties文件,將“iso-8859-1”替換為“gb2312”,現(xiàn)在頁面中文已經(jīng)能正常顯示,但是Lucene仍不能正常解析中文,原因是標(biāo)準(zhǔn)的StandardA?nalyzer只能解析英文,可以從網(wǎng)上下載一個支持中文的Analyzer。
總結(jié) (目錄)
Spring的確是一個優(yōu)秀的J2EE框架,通過Spring強大的集成和配置能力,我們能輕松設(shè)計出靈活的多層J2EE應(yīng)用而無需復(fù)雜的EJB組件支持。由于時間倉促,水平有限,文中難免有不少錯誤,懇請讀者指正。
源代碼下載 (目錄)
源代碼可以從http://www.javasprite.com/crystal/download.asp下載。
相關(guān)資源下載 (目錄)
JDK 1.4.2可以從http://java.sun.com下載。
Spring framework 1.1可以從http://www.springframework.org下載。
iBatis 2.0可以從http://www.ibatis.com下載。
Tomcat 4.1/5.0、Ant 1.6可以從http://www.apache.org下載。
Resin 2.1.1可以從http://www.caucho.com下載。
WebLogic Server / Workshop 8.1可以從http://commerce.bea.com下載。
JUnit 3.8可以從http://www.junit.org下載。
MySQL 4及其JDBC驅(qū)動可以從http://www.mysql.com下載。
MS SQL Server 2000 JDBC驅(qū)動可以從http://www.microsoft.com/downloads/details.aspx?FamilyID=07287b11-0502-461a-b138-2aa54bfdc03a&DisplayLang=en下載。
RSSLibJ 1.0可以從http://sourceforge.net/projects/rsslibj/下載。
參考 (目錄)
“Spring Reference”,Rod Johnson等。
“iBatis SQL Maps Guide”。
“Apache Ant 1.6.2 Manual”。
“Lucene Getting Started”。
Springframework的JPetStore示例是非常棒的設(shè)計,本文參考了JPetStore的許多設(shè)計模式。
關(guān)于作者
廖雪峰,北京郵電大學(xué)本科畢業(yè),對J2EE/J2ME有濃厚興趣,歡迎交流:asklxf@163.com。
個人網(wǎng)站:http://www.javasprite.com,歡迎訪問。
個人Blog站點:http://blog.csdn.net/asklxf/,歡迎訪問。