基于J2EE的Blog平台开发

来源:互联网 发布:快乐照片软件 编辑:程序博客网 时间:2024/04/26 05:16

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模式
  为了分离逻辑层和数据库持久层,定义一系列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/)和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仍不能正常解析中文,原因是标准的StandardAnalyzer只能解析英文,可以从网上下载一个支持中文的 Analyzer。

总结
  Spring的确是一个优秀的J2EE框架,通过Spring强大的集成和配置能力,我们能轻松设计出灵活的多层J2EE应用而无需复杂的EJB组件支持。由于时间仓促,水平有限,文中难免有不少错误,恳请读者指正。

源代码下载
  源代码可以从javaeedev.googlecode.com/files/crystal-blog.zip下载。

相关资源下载
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的许多设计模式。

源代码javaeedev.googlecode.com/svn/trunk/CrystalBlog/

ZIP下载javaeedev.googlecode.com/files/crystal-blog.zip

 
原创粉丝点击