原创技术高手-基于J2EE的Blog平台

来源:互联网 发布:数据产品经理 编辑:程序博客网 时间:2024/03/29 17:27
作者:xuefengl(dev2dev ID)
摘要:

  本文讲述如何在J2EE平台上创建一个Blog系统,以.Text的功能和界面为原型,Springframework为框架,实现一个运行在WebLogic Server上的灵活的多层结构的Blog平台。

目录:

1. 设计目标
2. 开发环境
2.1. 选择平台和框架
2.2. 配置服务器
2.3. 编写Ant脚本
3. 系统设计
3.1. 持久层设计
3.1.1. 设计Domain对象
3.1.2. 配置iBatis
3.1.3. 使用DAO模式
3.2. 逻辑层设计
3.3. Web层设计
3.3.1. 使用MVC模式
3.3.2. 实现Skin
4. 附加功能
4.1.1. 实现图片上传
4.1.2. 生成缩略图
4.1.3. 实现RSS
4.1.4. 实现全文搜索
4.1.5. 发送Email
5. 测试
6. 中文支持
7. 总结
8. 源代码下载
9. 相关资源下载
10. 参考
11. 关于作者

设计目标 (目录)

  Blog(WebLog)在Internet上越来越流行。许多网友都有了自己的Blog,通过Blog展示自己,结识更过的网友。比较著名的Blog平台是基于ASP.net的开源项目.Text。但是它的逻辑全部以存储过程的形式放在数据库中。虽然存储过程能大大提高数据操作的效率,但是存储过程本身是结构化的程序,无法发挥面向对象的威力,也不便于实现代码复用。因此,我决定实现一个基于J2EE体系的多层结构的Blog平台,功能和界面和.Text非常类似,暂命名为CrystalBlog。实现的功能有:发表和编辑文章;多用户支持;全文检索;RSS支持;图片管理;SMTP邮件发送等常见功能。界面如下:


选择平台和框架 (目录)
  由于使用J2EE平台,我们准备采用WebLogic Server 8.1作为运行平台,使用WebLogic Workshop8.1这个强大的集成化IDE作为开发工具。
  数据库选择MS SQL Server 2000SP3,建立一个名为blog的数据库存储所有的用户数据。由于我们并没有针对特定数据库编码,稍后我们会使用其他数据库测试。在系统设计之前,选择一个优秀的框架能大大提高开发效率。Spring是一个轻量级的J2EE框架。它覆盖了从后台数据库的JDBC封装到前台Web框架的几乎所有方?面。并且,Spring的各个模块耦合非常松散,我们既可以用它作为整个应用程序的框架,也可以仅仅使用它的某一个模块。此外,Spring非常强大的集成功能使我们可以轻易地集成Struts编写的Web端,或者使用Hibernate作为后端的O/R Mapping方案。
  Spring的核心思想便是IoC和AOP,Spring本身是一个轻量级容器,和EJB容器不同,Spring的组件就是普通的JavaBean,这使得单元测试可以不再依赖容器,编写更加容易。Spring负责管理所有的JavaBean组件,同样支持声明式的事务管理。我们只需要编写好JavaBean组件,然后将它们“装配”起来就可以了,组件的初始化和管理均由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/RMapping,封装所有数据库操作;中间层是由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 SQLServer和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>

效果如下:


(图4:search)

发送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/download.asp下载。

相关资源下载 (目录)
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/,欢迎访问。
原创粉丝点击