Blog(WebLog)在Internet上越來越流行。許多網友都有了自己的Blog,通過Blog展示自己,結識更過的網友。比較著名的Blog平臺是基于asp.net的開源項目.Text。但是它的邏輯全部以存儲過程的形式放在數據庫中。雖然存儲過程能大大提高數據操作的效率,但是存儲過程本身是結構化的程序,無法發揮面向對象的威力,也不便于實現代碼復用。因此,我決定實現一個基于J2EE體系的多層結構的Blog平臺,功能和界面和.Text非常類似,暫命名為Crystal Blog。實現的功能有:發表和編輯文章;多用戶支持;全文檢索;rss支持;圖片管理;SMTP郵件發送等常見功能。界面如下:
選擇平臺和框架 (目錄)
由于使用J2EE平臺,我們準備采用WebLogic Server 8.1作為運行平臺,使用WebLogic Workshop8.1這個強大的集成化IDE作為開發工具。
數據庫選擇MS SQL Server 2000 SP3,建立一個名為blog的數據庫存儲所有的用戶數據。由于我們并沒有針對特定數據庫編碼,稍后我們會使用其他數據庫測試。在系統設計之前,選擇一個優秀的框架能大大提高開發效率。SPRing是一個輕量級的J2EE框架。它覆蓋了從后臺數據庫的JDBC封裝到前臺Web框架的幾乎所有方?面。并且,Spring的各個模塊耦合非常松散,我們既可以用它作為整個應用程序的框架,也可以僅僅使用它的某一個模塊。此外,Spring非常強大的集成功能使我們可以輕易地集成Struts編寫的Web端,或者使用Hibernate作為后端的O/R Mapping方案。
Spring的核心思想便是IoC和AOP,Spring本身是一個輕量級容器,和EJB容器不同,Spring的組件就是普通的java Bean,這使得單元測試可以不再依賴容器,編寫更加容易。Spring負責管理所有的Java Bean組件,同樣支持聲明式的事務管理。我們只需要編寫好Java Bean組件,然后將它們“裝配”起來就可以了,組件的初始化和管理均由Spring完成,只需在配置文件中聲明即可。這種方式最大的優點是各組件的耦合極為松散,并且無需我們自己實現Singleton模式。
由于后臺要使用關系數據庫存儲數據,使用O/R Mapping必不可少。iBatis是又一個類似于Hibernate的O/R Mapping方案,特點是小巧,配置簡單,查詢靈活,完全符合我們的要求。
除了Spring和iBatis,用到的第三方組件還有:用于全文搜索的LUCene引擎,用于文件上傳的common-file-upload 1.0,用于輸出RSS的RSSLibJ1.0 RC2。
由于使用Spring這個輕量級框架,就無需EJB服務器,只需要Web服務器即可。因此,系統可以運行在WebLogic Server,Tomcat和Resin等支持Servlet和jsp的Web服務器上。
系統設計 (目錄)
很顯然,多層結構的J2EE架構能保證系統的靈活性和可擴展性。我們仍然采用表示層/邏輯層/持久層三層設計。
整個系統以Spring為基礎,持久層采用DAO模式和iBatis O/R Mapping,封裝所有數據庫操作;中間層是由Spring管理的普通的JavaBean,采用Fa?ade模式;表示層使用Spring提供的MVC框架。由于Spring對其他框架的良好集成,我們采用Velocity作為View。由于Velocity不能調用Java代碼,從而強制使用MVC模式而不是在View中嵌入邏輯代碼。
配置服務器 (目錄)
在WebLogic中新建一個Configuration,命名為blog,添加一個數據源,命名為jdbc/blog:
整個應用程序的目錄結構如下:
crystalblog/
+ doc/ (存放API文檔)
+ report/ (存放JUnit測試結果)
+ 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 (標準web配置文件)
+ blog.war (打包的可部署應用)
+ build.xml (ant腳本)
編寫Ant?腳本 (目錄)
Ant是一個非常棒的執行批處理任務的工具。使用Ant能使編譯、測試、打包、部署和生成文檔等一系列任務全自動化,從而大大節省開發時間。
首先我們把用到的所有.jar文件放到/web/WEB-INF/lib中,然后編寫compile任務,生成的class文件直接放到web/WEB-INF/classes目錄下。如果編譯成功,就進行單元測試,單元測試的結果以文本文件存放在report目錄中。如果測試通過,下一步便是打包成blog.war文件。接著把應用部署到服務器上,直接將web目錄的內容復制到%BEA_HOME%/user_projects/domains/blogdomain/applications/blog/目錄下即可。如果要在Tomcat上部署,直接將整個web目錄復制到%TOMCAT%/webapps/blog/下。
最后,如果需要,可以用javadoc生成api文檔。
系統設計 (目錄)
Crystal Blog共分成三層結構:后臺數據持久層,采用DAO模式;中間邏輯層,采用Facade模式;前端Web層,采用MVC結構,使用JSP作為視圖。以下是Rational Rose的UML圖:
設計Domain對象 (目錄)
設計Domain對象
Domain層是抽象出的實體。根據我們要實現的功能,設計以下實體,它們都是普通的Java Bean:
Account:封裝一個用戶,包括用戶ID,用戶名,口令,用戶設置等等。
Category:封裝一個分類,一共有3種Category,分別用來管理Article,Image和Link,一個Account對應多個Category。
Article:封裝一篇文章,包括Title,Summary,Content等等,一個Category對應多個Article。
Feedback:封裝一個回復,包括Title,Username,Url和Content,一個Article對應多個Feedback。
Image:封裝一個圖片,Image只包含圖片信息(ImageId,Type),具體的圖片是以用戶上傳到服務器的文件的形式存儲的。一個Category對應多個Image。
Link:封裝一個鏈接,和Category是多對一的關系。有Title,Url,Rss等屬性。
Message:封裝一個消息,使其他用戶在不知道Email地址的情況下能夠通過系統發送郵件給某個用戶。
最后,為了唯一標識每條數據庫記錄,我們需要一個主鍵。在MS SQL Server和Oracle中可以使用自動遞增的主鍵生成方式。但是很多數據庫不支持自動遞增的主鍵,考慮到移植性,我們自己定義一個Sequence表,用于生成遞增的主鍵。Sequence表有且僅有7條記錄,分別記錄Account到Message對象的當前最大主鍵值。系統啟動時,由SqlConfig負責初始化Sequence表。
SequenceDao負責提供下一個主鍵,為了提高效率,一次緩存10個主鍵。
配置iBatis (目錄)
接下來,使用iBatis實現O/R Mapping。首先從http://www.ibatis.com下載iBatis 2.0,將所需的jar文件復制到web/WEB-INF/lib/目錄下。iBatis使用XML配置數據庫表到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配置文件,可以包含進來 -->
<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封裝了訪問數據庫的大部分操作,可以直接使用SqlConfig.getSqlMapInstance()獲得這個唯一實例。
使用DAO模1 式 (目錄)
為了分離邏輯層和數據庫持久層,定義一系列DAO接口:AccountDao,CategoryDao,ArticleDao……其實現類對應為SqlMapAccountDao,SqlMapCategoryDao,SqlMapArticleDao……這樣就使得邏輯層完全脫離了數據庫訪問代碼。如果將來需要使用其它的O/R Mapping方案,直接實現新的DAO接口替代現有的SqlMapXxxDao即可。
以SqlMapAccountDao為例,實現一個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>
邏輯層設計 (目錄)
由于DAO模式已經實現了所有的數據庫操作,業務邏輯主要是檢查輸入,調用DAO接口,因此業務邏輯就是一個簡單的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()等方法,Facade僅僅簡單地調用對應的DAO接口:
public Article getArticle(int articleId) throws QueryException {
return articleDao.getArticle(articleId);
}
對于需要身份驗證的操作,如deleteArticle()方法,Facade需要首先驗證用戶身份:
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實現,不過,由于邏輯很簡單,完全可以直接寫在一塊,不必使用過于復雜的設計。
至此,我們的Blog已經實現了所有的后臺業務邏輯,并且提供統一的Facade接口。前臺Web層僅僅依賴這個Facade接口,這樣,Web層和后臺耦合非常松散,即使替換整個Web層也非常容易。
Web層設計 (目錄)
使用MVC模式 (目錄)
對于復雜的Web層,使用MVC模式是必不可少的。雖然Spring能輕易集成Struts,WebWorks等Web框架,但Spring本身就提供了一個非常好的Web框架,能完全實現MVC模式。
Spring使用一個DispatcherServlet,所有的特定請求都被轉發到DispatcherServlet,然后由相應的Controller處理,Controller返回一個ModelAndView對象(因為Java語言的方法調用只能返回一個結果,而且不支持ref參數,所以將Model和View對象合在一起返回),Model是一個Java對象,通常是Map,View是視圖的邏輯名字,通常是JSP文件名,但也可以使用Velocity等作為視圖。返回的View通過viewResolver得到真正的文件名。
首先配置Spring的MVC,在web.xml中聲明DispatcherServlet,處理所有以.c結尾的請求:
<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的文件,我們需要創建這個文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
</beans>
<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>
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 {
// 獲得參數:
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視圖會將結果顯示給用戶。
我們注意到,ViewArticleController并不自己查找或者創建Facade,而是由容器通過setFacade(Facade)方法設置的,這就是所謂的IoC(Inversion of Control)或者Dependency Injection。容器通過配置文件完成所有組件的初始化工作:
<!-- 聲明一個Facade -->
<bean id="facade" class="example.Facade" />
<!-- 聲明一個Controller -->
<bean id="articleController" class="example.ViewArticleController">
<!-- 為articleController設置facade屬性 -->
<property name="facade">
<!-- 將名為facade的Bean的引用傳進去 -->
<ref bean="facade" />
</property>
</bean>
以上配置文件實現的功能大致為:
Facade facade = new Facade();
ViewArticleController articleController = new ViewArticleController();
articleController.setFacade(facade);
但是我們不必編寫以上代碼,只需在xml文件中裝配好我們的組件就可以了。所有組件由Spring管理,并且,缺省的創建模式是Singleton,確保了一個組件只有一個實例。
此外,所有自定義異常都是RuntimeException,Spring提供的AOP使我們能非常方便地實現異常處理。在Web層定義ExceptionHandler,處理所有異常并以統一的error頁面把出錯信息顯示給用戶,因此,在代碼中只需拋出異常,完全不必在Controller中處理異常:
<bean id="handlerExceptionResolver" class="org.crystalblog.web.ExceptionHandler" />
使用Velocity作為View
采用Velocity作為View的最大的優點是簡潔,能通過簡單明了的語法直接在Html中輸出Java變量。Velocity本身是一個模板引擎,通過它來渲染Model輸出Html非常簡單,比如顯示用戶名:
<b>${account.username}</b>
換成JSP不得不寫成:
<b><%=account.getUsername()%></b>
而<%...%>標記在Dreamwaver中無法直接看到其含義。如果需要在標簽中嵌入JSP就更晦澀了:
<a href="somelink?id=<%=id %>">Link</a>
這種嵌套的標簽往往使得可視化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>
雖然標準的Velocity模板以.vm命名,但我們的所有Velocity模板頁均以.html作為擴展名,不但用Dreamwaver編輯極為方便,甚至可以直接用IE觀看頁面效果。
實現Skin (目錄)
許多Blog系統都允許用戶選擇自己喜歡的界面風格。要實現Skin功能非常簡單,為每個Skin編寫Velocity模板,存放在web/skin/目錄下,然后在Controller中返回對應的視圖:
String viewpath = skin.getSkin(account.getSkinId()) + "viewArticle";
由于使用了MVC模式,我們已經為每個頁面定義好了Model,添加一個新的skin非常容易,只需要編寫幾個必須的.html文件即可,可以參考現有的skin。
SkinManager的作用是在啟動時自動搜索/skin/目錄下的所有子目錄并管理這些skin,將用戶設定的skinId映射到對應的目錄。目前只有一個skin,您可以直接用可視化Html編輯器如Dreamwaver改造這個skin。
附加功能 (目錄)
實現圖片上傳 (目錄)
用戶必須能夠上傳圖片,因此需要文件上傳的功能。比較常見的文件上傳組件有Commons FileUpload(http://jakarta.apache.org/commons/fileupload/a>)和COS FileUpload(http://www.servlets.com/cos),Spring已經完全集成了這兩種組件,這里我們選擇Commons FileUpload。
由于Post一個包含文件上傳的Form會以multipart/form-data請求發送給服務器,必須明確告訴DispatcherServlet如何處理MultipartRequest。首先在dispatcher-servlet.xml中聲明一個MultipartResolver:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 設置上傳文件的最大尺寸為1MB -->
<property name="maxUploadSize">
<value>1048576</value>
</property>
</bean>
這樣一旦某個Request是一個MultipartRequest,它就會首先被MultipartResolver處理,然后再轉發相應的Controller。
在UploadImageController中,將HttpServletRequest轉型為MultipartHttpServletRequest,就能非常方便地得到文件名和文件內容:
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 轉型為MultipartHttpRequest:
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 獲得文件:
MultipartFile file = multipartRequest.getFile("file");
// 獲得文件名:
String filename = file.getOriginalFilename();
// 獲得輸入流:
InputStream input = file.getInputStream();
// 寫入文件...
}
生成縮略圖 (目錄)
當用戶上傳了圖片后,必須生成縮略圖以便用戶能快速瀏覽。我們不需借助第三方軟件,JDK標準庫就包含了圖像處理的API。我們把一張圖片按比例縮放到120X120大小,以下是關鍵代碼:
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());
}
}
實現RSS (目錄)
RSS是一個標準的XML文件,Rss閱讀器可以讀取這個XML文件獲得文章的信息,使用戶可以通過Rss閱讀器而非瀏覽器閱讀Blog,我們只要動態生成這個XML文件便可以了。RSSLibJ是一個專門讀取和生成RSS的小巧實用的Java庫,大小僅25k,可以從http://sourceforge.net/projects/rsslibj/下載rsslibj-1_0RC2.jar和它需要的EXMLjar兩個文件,然后復制到web/WEB-INF/lib/下。
使用RSSLibJ異常簡單,我們先設置好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();
實現全文搜索 (目錄)
全文搜索能大大方便用戶快速找到他們希望的文章,為blog增加一個全文搜索功能是非常必要的。然而,全文搜索不等于SQL的LIKE語句,因為關系數據庫的設計并不是為全文搜索設計的,數據庫索引對全文搜索無效,在一個幾百萬條記錄中檢索LIKE '%A%'可能會耗時幾分鐘,這是不可接受的。幸運的是,我們能使用免費并且開源的純Java實現的Lucene全文搜索引擎,Lucene可以非常容易地集成到我們的blog中。
Lucene不提供直接對文件,數據庫的索引,只提供一個高性能的引擎,但接口卻出人意料地簡單。我們只需要關心以下幾個簡單的接口:
Document:代表Lucene數據庫的一條記錄,也代表搜索的一條結果。
Field:一個Document包含一個或多個Field,類似關系數據庫的字段。
IndexWriter:用于創建新的索引,也就是向數據庫添加新的可搜索的大段字符串。
Analyzer:將字符串拆分成單詞(Token),不同的文本對應不同的Analyzer,如HtmlAnalyzer,PDFAnalyzer。
Query:封裝一個查詢,用于解析用戶輸入。例如,將“bea blog”解析為“同時包含bea和blog的文章”。
Searcher:搜索一個Query,結果將以Hits返回。
Hits:封裝一個搜索結果,包含Document集合,能非常容易地輸出結果。
下一步,我們需要為Article表的content字段建立全文索引。首先為Lucene新建一個數據庫,請注意這個數據庫是Lucene專用的,我們不能也不必知道它的內部結構。Lucene的每個數據庫對應一個目錄,只需要指定目錄即可:
String indexDir = "C:/search/blog";
IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);
indexWriter.close();
然后添加文章,讓Lucene對其索引:
String title = "文章標題" // 從數據庫讀取
String content = "文章內容" // 從數據庫讀取
// 打開索引:
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);
// 關閉:
indexWriter.close();
要搜索文章非常簡單:
然后添加文章,讓對其索引:
String title = "文章標題" // 從數據庫讀取
String content = "文章內容" // 從數據庫讀取
// 打開索引:
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);
// 關閉:
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();
我們設計一個LuceneSearcher類封裝全文搜索功能,由于必須鎖定數據庫所在目錄,我們把數據庫設定在/WEB-INF/search/下,確保用戶不能訪問,并且在配置文件中初始化目錄:
<bean id="luceneSearcher" class="org.crystalblog.search.LuceneSearcher">
<property name="Directory">
<value>/WEB-INF/search/</value>
</property>
</bean>
效果如下:
發送Email (目錄)
Blog用戶可以讓系統將來訪用戶的留言發送到注冊的Email地址,為了避免使用SMTP發信服務器,我們自己手動編寫一個SendMail組件,直接通過SMTP協議將Email發送到用戶信箱。
SendMail組件只需配置好DNS服務器的IP地址,即可向指定的Email信箱發送郵件。并且,SendMail使用緩沖隊列和多線程在后臺發送Email,不會中斷正常的Web服務。具體代碼請看SendMail.java。
測試 (目錄)
服務器配置為:P4 1.4G,512M DDR,100M Ethernet,Windows xp Professional SP2。
測試服務器分別為WebLogic Server 8.1,Tomcat 4.1/5.0,Resin 2.1.1。
測試數據庫為MS SQL Server 2000 SP3。如果你使用Oracle或者DB2,MySQL等其他數據庫并測試成功,請將SQL初始化腳本和詳細配置過程發一份給我,謝謝。
由于時間有限,沒有作進一步的調優。WebLogic Server和iBatis有很多優化選項,詳細配置可以參考相關文檔。
中文支持 (目錄)
測試發現,中文不能在頁面中正常顯示,為了支持中文,首先在web.xml加入Filter,用于將輸入編碼設置為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”,現在頁面中文已經能正常顯示,但是Lucene仍不能正常解析中文,原因是標準的StandardA?nalyzer只能解析英文,可以從網上下載一個支持中文的Analyzer。
總結 (目錄)
Spring的確是一個優秀的J2EE框架,通過Spring強大的集成和配置能力,我們能輕松設計出靈活的多層J2EE應用而無需復雜的EJB組件支持。由于時間倉促,水平有限,文中難免有不少錯誤,懇請讀者指正。
源代碼下載 (目錄)
源代碼可以從http://www.javASPrite.com/crystal/index.htm下載。
相關資源下載 (目錄)
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驅動可以從http://www.mysql.com下載。
MS SQL Server 2000 JDBC驅動可以從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示例是非常棒的設計,本文參考了JPetStore的許多設計模式。
關于作者
廖雪峰,北京郵電大學本科畢業,對J2EE/J2ME有濃厚興趣,歡迎交流:asklxf@163.com。
個人網站:http://www.javasprite.com,歡迎訪問。
個人Blog站點:http://blog.csdn.net/asklxf/,歡迎訪問。
(出處:http://www.49028c.com)
新聞熱點
疑難解答