EJB工作原理学习笔记!
java.lang.ClassNotFoundException: javax.xml.parsers.SAXParserFactory
在java体系结构中集成Macromedia Flex(翻译)
第五章 java语言的接口
Java与XML(三)web上的读写应用
Web层改进II-用xmlhttp 无声息提交复杂表单
DBUnit在应用间导出导入数据
RMI、CORBA、IIOP简单实例--2. CORBA
第一个Hibernate程序
第二章 统一建模语言UML简介

页面导航:
正文内容: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的许多设计模式。