Morning@Weblog

12/31/2004

本年度的最后一篇blog

Filed under: — site admin @ 4:03 pm

这是本年度这里的最后一篇blog,似乎带有一点充数的成分。记得当初在这个blog的开篇中说过,这里是一个只谈技术的地方,不过由于最近工作繁忙,加之状态不好,实在没啥心情谈论技术问题。所以,就当是破个例,在2004年的最后一天里。

孟岩是一个让我不得不佩服的人,“双重压力”下依然还能够如愿的考上博士,在这里要恭喜他一下!我发觉每次听他说话,和交流,都会有一种莫名的感染力,可以让自己振奋的,找到方向的,而又是实实在在毫不浮华的感觉。我想,在他身上,还有很多很多东西都值得我学习。很幸运能够结识这样一位好长辈。

BTW:Happy new year to all my friends:-)

12/22/2004

办公桌与团队的氛围

Filed under: — site admin @ 7:28 pm

想了解team的氛围和凝聚力到底如何吗?想知道team member对自己所处集体的信心怎样吗?那就仔细观察一下他们的办公桌吧,这里也许会有如下三种可能:
- 干净的没有任何多余的东西,比如一些小摆设;
- 胡乱堆放着无数文案,以及其他杂物,脑袋被埋没其中;
- 经过精心布置,一走近就给人以格外温馨的气氛;

若是第一种情形,我要很遗憾的告诉你,结果是令人失望的。若是后两种情况,不论是生活习惯过于邋遢,还是过于讲究,最终都只证明了一点——这张桌子的所有人,已将这里当作了自己的“家”,否则他/她就无需这么随意,或者刻意了。

在一个如此温馨的氛围中,大家可以毫无保留的进行彼此间的交流,甚或争论,自然是颇能说明些问题的。这也是一个有战斗力的team,最为可贵的一点了。

12/21/2004

CVS Repository里的私人物件

Filed under: — site admin @ 10:09 am

项目在进行到需要将多人的工作集成在一起的阶段时,有时会发现cvs repository里有一些“私人文件”,这当然是大家无意间commit上去的,但是即使现在清除了,很可能以后还会再次出现,因此:
- 如果这些私人物品并不重要,为了保持repository的统一和整洁,应该尽量删去;
- 如果这些私人物品确实重要,可以将其置于本地工作目录之外,以免不小心再次commit;
- 如果希望这些文件在本地的workspace中可见,以便于访问,不妨在eclipse中设置“虚拟”文件夹;
- 如果实在不希望把它们放到其他地方,那么只好在commit时十分小心了。

总之。自己的“私人文件”出现在他人的本地工作路径中,不是一件好事情。实际上,这也反映出一个问题,那就是集成间隔的粒度还是过粗,否则,便可以在更短的时间里发现这些conflicts,相应的清理工作量也会更小一些。

12/17/2004

DBUnit导出数据库的中文问题

Filed under: — site admin @ 5:51 pm

DBUnit在导出数据库记录时,默认情况下采用的是"UTF-8″编码,因此会导致乱码。我在mysql和oracle下分别做了试验,如果是用java application,针对mysql的url字串需要指定编码格式,比如:

jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=GB2312

而对于oracle,则只需要直接给出url就可以了。

查看了一下DBUnit的源码,FlatXmlDataSet的write方法有三种形式:

public static void write(IDataSet dataSet, Writer writer)
public static void write(IDataSet dataSet, Writer writer, String encoding)
public static void write(IDataSet dataSet, OutputStream out)

如果传入OutputStream对象,就会有问题(具体原因待查),而如果传入FileWriter,则一切正常:

IDataSet fullDataSet = connection.createDataSet();
FlatXmlDataSet.write(fullDataSet, new FileWriter("full.xml"), “GB2312″);

encoding参数可以不指定,DBUnit会自动判断,在我的机器上,最后生成的xml,encoding是"GB1030″。

另一方面,如果使用build脚本来实现导出功能,则会遇到一些麻烦:

首先,对于mysql的url字串,如果不将&替换成&,则会遇到The reference to entity “characterEncoding” must end with the ‘;’ delimiter的错误。

其次,通过查找DBUnit的源码发现,dbunit task中的export,是使用OutputStream作为FlatXmlWriter的传入参数的:

OutputStream out = new FileOutputStream(_dest);
FlatXmlWriter writer = new FlatXmlWriter(out);
writer.setDocType(_doctype);
writer.write(dataset);

不得已,只好将其改为FileWriter的,重新打包后即可彻底解决中文乱码的问题了。

12/16/2004

DBUnit的大概印象

Filed under: — site admin @ 12:06 pm

DBUnit的主要应用场合是database-driven project,不过这个似乎是普遍现象:现在的软件,但凡稍有些许规模的,又有几个不需要和数据库打交道的呢。

DBUnit的主要职责在于,保证在每个testcase的执行间隙,将数据库设置为某种我们所预期的状态,从而为下一个testcase的执行准备好database related pre-condition。我们平时强调,testcase应该尽量保持彼此间的独立性,减少依赖,这样可以使testcase变得稳定,同时也只有这样,testcase才真正能够说明问题。但是,在与数据库相关的项目中,testcase间的关联很可能会在数据库这一层暴露出来,这样一来,某个测试的失败可能直接导致database corrupt,进而改变了后续testcase的pre-condition,从而影响了这些testcase的正常执行,并且,这可能进一步导致database corrupt。DBUnit着眼于以数据为中心的testcase,包括数据的读写与验证,由于每次操作的只是数据库的某个局部片段,因此testcase间的数据准备所花费的时间并不像想象中那么多。

另外,DBUnit还有import/export的功能,可以将数据库中的记录导出至flat xml dataset,也可以将该dataset导入到数据库中。不过xml默认使用的是UTF-8编码,因此中文支持有问题。

DbUnit同时还可以帮助你验证数据库的snapshot是否与某一组预期的dataset相匹配。

不过个人觉得,像数据准备这样的事情,不用DBUnit的话,“手工”也可以做,一些开源项目就是这么干的,虽然稍微麻烦一点,不过可以做一些helper classes。

12/14/2004

JspWiki的小波折

Filed under: — site admin @ 10:23 am

上个星期差点被JspWiki折腾的不行,主要是由于某些原因导致的JVM资源耗尽,使得后台报出OutOfMemory错误。其实以前也有过类似现象,用着用着浏览器就变得特慢,甚至没反映了,只是当时没怎么在意,觉得重启一下tomcat就可以了。

虽然现在还不能肯定,但最有可能的原因是cache造成的。由于,JspWiki缺省使用的是文件系统管理wiki的,因此为了提高性能,默认会将访问的页面作为缓存置于内存当中。但是随着wiki规模的不断壮大,缓存所占空间就会越来越大(team所使用的wiki服务器只有区区256M)。实际上,每次重新启动tomcat的时候,首次访问wiki总是很慢,虽然这部分代码我还没有仔细看过,不过我猜想应该就是在“重建”缓存信息,包括页面的交叉引用,JspWiki会将某些临时信息放到temp目录下,包括lucene的index信息,这样即使系统重启了,信息依然可以再次被加载。

尽管上回曾经为了这个,把JAVA_OPTS调成了:-Xms128m -Xmx256m,可总觉得不牢靠,果然没过多久就又出问题了。另外,不知道采用VersioningFileProvider是否也有关系,因为我有个“坏”毛病,总喜欢不断的写不断的保存,结果文件目录下面就会产生无数中间结果,不知道这是否也是导致wiki运行变慢的原因。

总之,为了不致再次OutOfMemory,我在目前力所能及的范围内做了最大的努力,比如:通过jspwiki.properties的相关配置关掉了cache,去除了文件历史跟踪记录,取消某些耗时的操作,等等。经过一番折腾,现在wiki到还能基本正常可用,访问起来似乎并不慢。也许最初就应该用数据库存储方式。

唯一的遗憾是,在备份数据文件时,由于一个小小的失误,以前大家写的wiki页面文件的时间信息都丢失了。主要是由于linux下的文件拷贝会将副本文件的时间改为拷贝的时间,而不是原文件的时间,只有mv命令才会保留原有文件时间。这一点似乎与windows不同。

P.S. 前段时间研究和修改JspWiki还是花了些时间的,本来可以写点心得的,只可惜当时不在状态,现在就更没这个兴致去写这些东东了。

12/13/2004

Webwork解读之一

Filed under: — site admin @ 9:52 pm

ServletDispacher>>

众所周知,作为webwork2中的MVC架构的controller,ServletDispacher自然是一个十分重要的类,它负责将到达的request转译成相应的action。其核心方法是service(),当一个http请求到达时,它将依序做如下处理:

- 将multipart request包装成一个单一的HttpServletRequest
- 为调用serviceAction方法准备传入的参数:
 - 利用getNamespaceFromServletPath从servlet path中获得namespace,比如:从/foo/bar/MyAction.action中得到/foo/bar。(getNameSpace)
 - 利用getAction从servlet path中获得action的名称,比如:从MyAction.action中得到MyAction。(getActionName)
 - 将HttpServletRequest的attribute-value对封装成一个RequestMap对象,该对象保存了一个hash set,可以通过其entrySet方法进行访问。(getRequestMap)
 - 将HttpServletRequest的session包装成一个SessionMap对象,该对象保存了一个包含key-value对的hash set,可以通过其entrySet方法进行访问。( getSessionMap)
 - 简单的调用HttpServletRequest的getParameterMap方法。(getParameterMap)
 - 返回一个ApplicationMap对象,该类的entrySet方法返回一个包含了全部app. attr.的hash set(getApplicationMap)
- 调用serviceAction,加载并执行action:
 - 利用createContextMap,将前面的那些参数(包含了request、response、parameters、session、application properties),还有一些其他属性,设置到一个hash map中,以创建一个action context(extraContext),并把它自己也设置进去:

extraContext.put(SERLVET_DISPATCHER, this);

 - 创建ActionProxy,并传入namespace、action name、extra context,并执行ActionProxy的execute方法。

ActionProxy是xwork的一个部件,它对action做了wrap,根据xwork.xml配置文件,它会找到相应的action class,并调用actoin的excute方法。

此外,在ServletDispacher的init方法中还做了一些初始化工作,主要包含对velocity引擎的初始化,以及三个参数的初始化,它们都可以在webwork.properties文件中指定,由com.opensymphony.webwork.config.Configuration
加载:
webwork.configuration.xml.reload,若为true,包括action定义、interceptor定义的xml配置文件,将会在每次request请求时都加载,适合于调试阶段;
webwork.multipart.saveDir,上传文件的临时路径,缺省为javax.servlet.context.tempdir;
webwork.multipart.maxSize,上传文件的最大字节数,缺省为java.lang.Integer.MAX_VALUE;

从ServletDispacher的实现,我们还可以得出两点结论:
*针对每次request的响应,新的Action对象都将会被创建。
*为什么缺省时,webwork要求以*.action作为url pattern。

12/12/2004

blog存在的意义

Filed under: — site admin @ 8:25 pm

光阴就这么在不经意见偷偷的溜走了,一点一点,并没有留下很深的印痕。不知道这算不算是一种虚度,也许很多人都有这样的感受。有时候想想真是有些可惜,甚至是可怜……,所以,blog也就有了它的特殊“意义”——记录下自己的一点一滴,好在将来回顾的时候“有据可依”。至少在我看来,这样会让自己觉得过去的时光不至白费,让自己觉得生活有了些许意义,让自己哪天回忆的时候不会是脑海里空白一片。

写blog也是一种习惯,习惯的建立总是不易,而一旦打破,便会一发而不可收拾,所以,该写的还是要写的,否则对自己的信心也是一种打击。这又让我想起前几天的那番牢骚:)

12/9/2004

有感于良好习惯的打破

Filed under: — site admin @ 9:25 pm

规则的打破是很容易的,有时候甚至是在team member的不经意间的“默许”下潜移默化的发生的。进而,交流归于不畅,目标可以变得模糊,行事可以变得随意,原则可以不成其为原则,一切都显得自然而然,因为已经有过一次了,再来一次又何妨?

合抱之木,生于毫末,九层之台,起于累土,持久的原则与良好的开发习惯的建立,绝非易事啊!

12/7/2004

JBoss和Tomcat的端口冲突

Filed under: — site admin @ 4:37 pm

一直在困惑于奇怪的JBoss和Tomcat的端口冲突问题。不得已,今天终于决定不改动Jboss的设置了,因为它配起来比Tomcat麻烦。于是,我开始拿Tomcat的server.xml开刀,经过修改的Tomcat端口包括:AJP port(8009->8010);Shutdown port(8005->8006);HTTP port(8080->8000)。目前使用无任何异常,希望不会再有问题。

P.S. 这回修改端口的时候,发现Tomcat的8005是用来帧听shutdown的,而JBoss好像也拿它来干这个事情。于是联想到以前总是遇到Jboss不能正常关闭的现象,估计与此有关。也许经过这次调整,就不用强行kill Jboss的线程了,不过这个还有待验证。

12/6/2004

关于Jira的Task

Filed under: — site admin @ 10:53 am

周末试用了一下Jira,感觉还不错,打算向team member推荐在下一个迭代周期开始的时候使用。

Task

根据我的理解,在Jira中所有要track的issue实际上都可以被称为“task”,只是有些是预先定义好的,比如:bug、feature、improvement。预定义的条目中还有一个直接叫“task”的,我把它理解成“generic task”。我们还可以通过定制自己的task,来加以扩展,比如:test、documentation等等。这为实际的issue tracking的表达,提供了很大的灵活性和适应性,同时也解答了我一直以来的一个疑问:实际的开发过程中,很多东西都是需要跟踪的,不光是bug,feature,improvement,还有一些其他性质的task,甚至是idea,否则很多工作就没法很好的开展。详见这里

当然,这里的预定义的“task”,似乎在概念上还有一点混乱,比如对于bug,按我的理解,叫bug fix似乎更好,因为task是一种达到指定目标的行为,而bug则是task的一个施行对象。

Sub Task、Issue Linking

Jira除了可以自定义task之外,还可以为task指定sub task,这样就可以将某个复杂task细分,从而具有更好的表达力。不过Jira当前似乎只支持一层sub task(最新版本是3.0),但是这样也已经很受用了。另外,对于某些彼此间有关联的task,我们还可以为其定义issue linking,这样我们就可以很好的跟踪一组具有相关性的task了。

12/1/2004

曲折的持续集成自动部署

Filed under: — site admin @ 3:27 pm

第一阶段:原本希望在持续集成平台上可以每天看到一个最新版本的可供访问的web app。最早时想利用jboss或者tomcat的热部署,可是后来发现这很不可靠,尤其涉及ejb。

第二阶段:于是就寄希望于在build脚本中加入自动启停jboss服务器的功能,以便完成干净部署之后,再行启动应用。不过。不过简单的利用exec task执行启动脚本,或者利用java task执行org.jboss.Main都会导致build脚本长时间执行直至jboss停止之后方才结束的现象,即使位于同一个JVM中亦是如此。这是因为ant与jboss同处一个进程所致。为此,我将脚本修改如下:

<target name="stop-jboss">
 <echo>Stoping JBoss</echo>
 <exec executable="${jboss.stop.shell}” spawn="true">
  <arg value="-S” />
 </exec>
</target>
<target name="start-jboss-windows” if="is-windows">
 <echo>Starting JBoss(windows)</echo>
 <exec executable="cmd” spawn="true">
  <arg value="/C"/>
  <arg value="${jboss.start.shell}"/>
  <arg value=">NUL"/>
 </exec>
</target>
<target name="start-jboss-linux” if="is-linux">
 <echo>Starting JBoss(linux)</echo>
 <exec executable="sh” spawn="true">
  <arg value="${jboss.start.shell}"/>
 </exec>
</target>

第三阶段:我在本地的windows环境下似乎屡试不爽(诚然,没有做过太多测试)。可是后来移植到linux服务器之后,发现并非每次都能成功。后来发现是jboss在stop时,并未彻底从内存中消亡。进程依然存在,端口依然打开着。因此导致了再此启动失败。最后实在没辙了,采用强制措施——kill。

for PID in `ps -ef | grep jboss.Main | sed -e ‘/grep/d’ | awk ‘{ print $2 }’`
do
 kill -9 $PID
done

linux的shell确实十分强大,在管道过滤时,还可以支持正则表达式的过滤,总算是领教了。

第四阶段:jboss是没有问题了,可部署了anthillpro的tomcat又有了新问题。由于anthillpro在自动构建期间会产生大量输出,尤其是日志记录。这最终导致了tomcat的catalina.out文件巨大无比,从而使得web浏览器根本无法访问anthillpro的管理界面。本想控制一下anthillpro的日志输出级别(默认是debug),可是修改了log4j的配置文件之后似乎不起作用。后来干脆又采取了强硬措施——定期删除catalina.out文件。些好一个简单的shell脚本,如下:

LOGFILE=$TOMCAT_HOME/logs/catalina.out
if [ -f $LOGFILE ]
then
rm $LOGFILE;
echo “” >$LOGFILE
fi

然后利用crontab功能,在命令行下输入crontab -e,然后输入如下内容:

0 5 * * * /root/rmcatalinaout.sh

rmcatalinaout.sh就是刚才的shell脚本,这代表每天上午5点,该shell脚本将会被执行一次。该设置将以文件形式保存于/var/spool/cron目录下,文件名称即登录的用户名,比如:root。

看起来这个问题似乎是彻底解决了,虽然并不优雅,现在的我,只求不会再生枝节。

Powered by WordPress