分享
 
 
 

基于J2EE的Blog平台

王朝java/jsp·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

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>

用到的所有的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/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.knowsky.com)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有