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 Beans,这使得单元测试可以不再依赖容器,编写更加容易。Spring负责管理所有的Java Beans组件,同样支持声明式的事务管理。我们只需要编写好Java Beans组件,然后将它们“装配”起来就可以了,组件的初始化和管理均由Spring完成,只需在配置文件中声明即可。这种方式最大的优点是各组件的耦合极为松散,并且无需我们自己实现Singleton模式。
由于后台要使用关系数据库存储数据,使用O/R Mapping必不可少。iBatis是又一个类似于Hibernate的O/R Mapping方案,特点是小巧,配置简单,查询灵活,完全符合我们的要求。
除了Spring和iBatis,用到的第三方组件还有:用于全文搜索的Lucene引擎,用于文件上传的common-file-upload1.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作为视图。
设计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>
用到的所有的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 {
// 获得参数:
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。
十七、中文支持
测试发现,中文不能在页面中正常显示,为了支持中文,首先在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组件支持。
相关资源下载
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的许多设计模式。