老翅寒暑

一个老鸟的自白
随笔 - 74, 文章 - 0, 评论 - 654, 引用 - 17
数据加载中……

2008年6月7日

Kubuntu & Windows XP & OS X 10.5.2 安装笔记

     摘要:   阅读全文

posted @ 2008-06-07 17:58 老翅寒暑 阅读(397) | 评论 (0)编辑

2008年5月15日

地震搜救手册

     摘要: 听说是网友突击翻译出来的,看了觉得有用处.感谢这么多热心人!除了捐款捐血之外,希望这个也是我贡献力量的一个方式,能够让需要的人及时看到!  阅读全文

posted @ 2008-05-15 15:37 老翅寒暑 阅读(171) | 评论 (0)编辑

2008年5月9日

ubuntu 8.04的安装记录

把自己的机器从vista换成了ubuntu 8.04.感觉顿时快了很多阿!
以下是记录安装过程中的一些命令,免得自己忘记.

1.设置更新服务器地址
如果觉得默认的ubuntu 8.04更新服务器不是很快的话,请按照参考文献1的内容配置对自己最快的服务器地址.
务必记住的是,请把最快的更新服务器地址放在/etc/apt/sources.list文件的最前位置.

2.网络部分
首要是安装一个多线程的下载工具,这里推荐MultiGet,一个同济大学校友的产品.有FlashGet的风范.地址在[http://multiget.sourceforge.net/].
电驴\BT等使用命令安装:sudo apt-get install mldonkey-server
然后到[http://sancho-gui.sourceforge.net/]下载mldonkey的图形界面.

浏览器firefox插件方面,我选择安装了如下几个:
Adblock Plus, Firebug, Flashblock, FlashGot, Greasemonkey, ImageZoom, Paragrasp, ScrapBook
具体请参考参考文献4

3.多媒体部分
安装各种解码器和播放器
sudo apt-get install gstreamer0.10-pitfdll gstreamer0.10-ffmpeg gstreamer0.10-plugins-bad gstreamer0.10-plugins-bad-multiverse gstreamer0.10-plugins-ugly gstreamer0.10-plugins-ugly-multiverse
更简单的话,可以输入sudo apt-get install gstreamer0.1* 就可以安装更多.不过用不到的也多

安装w32codecs,
sudo apt-get install w32codecs 如果报告找不到,则需要手工下载安装.这个地址[http://www.debian-multimedia.org/pool/main/w/w32codecs/],不过请注意,使用w32codecs是侵犯windows版权的行为(谁在乎呢?不过别说我没告诉你!).

内置的totem播放器在播放rmvb的时候会有问题,所以要安装mplayer
sudo apt-get install mplayer
要注意,我的机器默认配置使用命令"mplayer -vo x11 ~/d/film/some_film.rmvb"才能正常播放,原因是mplayer中的视频和音频参数设置有问题,请尝试选择正确的配置参数.
另外,请保证~/.mplayer/config文件存在,并内容包含行zoom=yes以便打开图形缩放功能.

4.系统配置和管理
执行本节内容时候,最好请参考所附参考文献2的内容,增强理解.

安装系统管理控制台webmin,避免我们自己一个一个配置文件去修改:
到[http://www.webmin.com/download.html]下载deb安装包,并且安装,图形界面直接使用"GDebi package installer"打开并安装;如果手工安装的话,使用命令:dpkg -i 安装包文件路径 来安装.然后在浏览器中输入https://localhost:10000就可以打开webmin界面.firefox会提示ssl证书不对,请把它加入到exception例外列表中即可.

不过这个时候并不能登录webmin,因为它需要使用root账号,默认的ubuntu是不开启的.所以需要使用命令sudo passwd root来给root设置一个新的复杂的密码,然后用root账号登录webmin即可.

如果觉得不安全,可以在webmin界面中把webmin服务设置成默认不启动,不过记得以后要用的时候,要用命令启动webmin服务:/etc/webmin/start,用完之后用命令/etc/webmin/stop关闭服务.

5.安装windows模拟环境
虽然装了ubuntu,但是完全抛弃windows是不可能的,所以我们需要两个工具wine和virtual box.前者好处是速度快,资源占用少;后者是模拟完全.命令如下:
sudo apt-get install wine
到[http://www.virtualbox.org/wiki/Downloads]下载最新版virtual box(目前已经被sun买下来了)的安装包,直接安装即可.

6.美化字体
请首先在virtualbox中安装一个windows xp环境,然后从中复制字体到你的~/myfonts目录下,然后到
sudo ln -s ~/myfonts /usr/share/fonts
sudo chmod 755 ~/myfonts/*
sudo mkfontscale
sudo mkfontdir
sudo fc-cache -fv
最后把参考文献11中的配置内容存放到/etc/fonts/language-selector.conf,千万不要象有些帖子说的去覆盖或者修改/etc/fonts/font.conf,会启动不起来的.

7.修改ntfs的mount问题
发现ntfs盘不能写入,所以把在/etc/fstab中ntfs的mount行修改为如下:
/dev/sda5 /media/WORK ntfs-3g uid=1000,gid=1000,dmask=022,fmask=133,defaults,locale=zh_CN.UTF-8 0 0


安装一个通知程序,我需要定时给自己通知消息(编程容易忘记时间阿)
sudo apt-get install libnotify-bin
命令使用方法:notify-send "hello"

使用命令crontab -e,然后输入如下内容:
0-30/10 0   * * *   halt -p
30-59/10 22 * * *   halt -p
30-59/10 18 * * 1-5 halt -p
30-59/9 23  * * *   notify-send "系统将在24:00准时关机,请及时保存进度"
0-30/10 22  * * *   notify-send "system will shutdown at 22:30"
45  11  *   *   1-5 notify-send "launch time"
0  12,14,18 * * 1-5 export DISPLAY=:0 && totem --play ~/music.pls




参考文献:
  1.  ubuntu Hardy(8.04)源列表大全
  2. [分享]从头开始对ubuntu进行优化
  3. ubuntu下WEBMIN的安装 [http://blog.chinaunix.net/u1/34948/showart_322026.html]
  4. 提高浏览体验的五十个最佳FireFox扩展插件[http://www.williamlong.info/archives/702.html]
  5. linux下如何定时启动图形应用程序 [http://ubuntu.group.javaeye.com/group/blog/41075]
  6. ubuntu 定时关机 [http://www.linuxdiyf.com/viewarticle.php?id=88888]
  7. Linux/Unix下定时任务系统--cron和crontab(拼凑)
  8. ubuntu下的项目管理工具dia, planner
  9. [http://www.debianadmin.com/enable-and-disable-ubuntu-root-password.html]
  10. Ubuntu下面经常要用到的命令[http://www.denimcc.cn/article.asp?id=383]
  11. 搞定Ubuntu字体,让你感觉像在windows中

posted @ 2008-05-09 16:50 老翅寒暑 阅读(423) | 评论 (0)编辑

ubuntu 8.04下安装和使用systemtap

systemtap是一个linux下的系统调用和跟踪程序,如果您在windows下用过filemon或者processmonitor的话就明白了.systemtap功能比processmonitor更强大一些,定制性更高些.但是显然,和大多数linux程序一样,也更难用!
为了装这个systemtap,翻了好多贴子,最简单的方法如下:
  1. 运行命令:sudo apt-get install linux-image-debug-`uname -r`
  2. 运行命令: sudo ln -s vmlinux-debug-`uname -r` vmlinux-`uname -r`
  3. 运行命令:sudo apt-get install systemtap
如此,就装好了,可以构造一个简单的脚本文件a.stp,如下:
probe syscall.open
{
  printf ("%s(%d) open (%s)\n", execname(), pid(), argstr)
}
 然后用命令运行:sudo stap ./a.stp 如果顺利的话,就可以看到输出了.不过我这里运行到pass5的时候报错:/usr/lib/systemtap/stapio terminated,正在查找原因中.

如果不习惯使用这么高定制性的工具的话,可以改用strace,用法简单:strace -f -o out_trace.txt /opt/eclipse/eclipse 我这里是用来跟踪eclipse运行时候的文件读写记录.


参考文献:
  • http://blog.chinaunix.net/u/12679/showart_529575.html (注:这个方法不好用,我在xubuntu下界面出问题,而且stap无法运行)
  • https://bugs.launchpad.net/ubuntu/+source/systemtap/+bug/106957
  • http://sourceware.org/systemtap/tutorial/node5.html 有关systemtap脚本的编写
  • http://www.ibm.com/developerworks/cn/linux/l-systemtap/ 使用Systemtap调试内核
  • http://www.xxlinux.com/linux/article/development/soft/20051125/67.html 使用truss,strace,ltrace跟踪


posted @ 2008-05-09 12:58 老翅寒暑 阅读(135) | 评论 (0)编辑

2008年3月26日

从许霖案和soff案中发现的有趣问题

写本文的目的不是跟潮流或者引人眼球,而是突然发现了几个有趣的问题。

首先我们看许霖案。案情想必大家也清楚,工行的柜员机出问题了,取1000元只扣1元,于是一个叫做许霖的小伙子就取了17.5万,然后被抓住判了无期。有人就黑色幽默地说用171次才取了这么一点钱,就这种没出息的样子也该判无期。我们不是法律人士,当然也不会去找什么条款,但是作为一样是靠“逻辑”吃饭的程序员,我们可以从逻辑的角度来做一点分析。首先我们来看许霖“作案”的顺序流程:

  1. 去银行取款
  2. 柜员机处在故障状态
  3. 取款171次,获得17.5万
  4. 潜逃
  5. 被抓获

关注过此案的朋友应该注意到,所有的分析文章都是从第3点开始分析的,有说什么柜员机是共犯啊、潜逃中间曾经要联系还款啊、还有什么自首、什么质疑金融机构的定义什么的,不过我看杀伤力都不算大。从技术的角度来看,其实第2点最为致命:如何证明柜员机处于故障状态?

首先我们要找一种可能性,就是柜员机取款1000扣1元算作是“正常工作”的场景。我想大家都知道商场会经常搞促销活动,比如“买一送一”什么的,那么银行有没有可能“促销”呢?如果是一种“正常”的“取1送999”的话,许霖就是无罪的。那么控方如何来证明这个柜员机确实是故障状态呢?最有力的证据应该是柜员机生产商提供的"该批次"柜员机的“产品说明书”,或者提供“当时”由工行和厂商联合认定的作为合同附件的“需求说明书”。

其实归根到底,就是“软件BUG的认定问题”。如何来认定一个软件的行为属于“BUG"还是正常的功能,唯一的标准就是合同随附的“需求说明书”。不过从中国IT现状来看,我想控方99%是提供不出这个东西的。由此引出了附加的问题:

  • “厂商已经如数赔偿”能不能说明导致许霖取款的漏洞就是BUG?
  • 没有该批次的说明书,用其他批次的说明书代替是否可行?或者现在开“证明”来承认是BUG,是否有效(有没有串供嫌疑)?
  • 柜员机有没有“调试模式”以便来每次放款之后测试取款功能正常?如果有,是不是可以说明这就是管理者想要提供的“优惠”?

总结一下就是:需求说明书其实真的很重要。

附加说明:前两天出了一个英国的柜员机“买一送一”事件,但是俺确实是在之前去WC的时候先想到这个问题的

其次我们来看soff案,现在一审宣判出来了,我们不说什么“tx过河拆桥”、什么“司法独立”等等意义不大的话,只从技术方面来谈这个问题。这个案子技术上涉及到了如下的几个方面:

  1. 使用程序外挂调用非开放功能的问题
  2. 未经同意发布对方程序的问题
  3. 擅自清除人家的广告图片问题
  4. 在人家的界面上插广告获利的问题
  5. 绑定插件的问题

soff案的起诉书里边基本都涉及到了以上的4点,而且判决书里边也差不多都对此进行了认定,那么我们就来逐条分析一下:

关于外挂问题,其实无非就是常用的3种方法,一种是微软的detour技术,直接修改函数入口处的字节码;一种就是api注入,通过修改程序入口偏移实现;另外一种就是通过公开的回调接口。其实tx自己也使用钩子截获了鼠标事件,而且去年tx的不兼容vista的事情充分说明tx自己使用了windows不支持的非公开的技术。如果说用外挂技术也可能获罪的话,tx这些算是在干什么?法院的判决把正常的技术行为看作是“非法”的,这对产业、对社会、对技术进步都是一种伤害。不过我们也庆幸,幸好M$没有出来告,否则还是这家法院来判的话,中国的软件业至少搞反病毒的都要坐牢去。至于提出什么“插件的bug被误认为是tx的bug”导致“tx被误解”之说,tx不兼容vista的那件事情,明明是tx的bug却被一些愤青上纲上线说成M$故意打压,造成了M$的名誉被损害,这又怎么说呢?

关于未经同意发布对方程序的问题,其实这个就是一个不折不扣的侵权了。现在网上出现了许多的“打包党”,把人家的安装程序解开,重新加入自己的插件再打包,无论如何都是说不过去的。你看intel为了一个.h的头文件,跟深圳东进公司打了一年的官司呢。

关于清除广告的问题,按理说应该是属于侵权,可是这个关系到一个“修改运行时代码是否合法”的问题。如果说是合法的,那么清除广告也就没问题,但是要说修改运行时代码是非法的,那么windows加载程序的时候要修改程序的入口代码地址是不是也非法?金山词霸截获我程序的api入口来获取字符串内容、看门狗拦截api调用更改我程序的运行方式算不算非法?soff如果用M$的detour库,是不是就算M$而不是soff侵权呢?如果说擅自去掉广告是修改了对方应用程序的外观界面,那么windows桌面主题算干的什么活?如果说再限定说不许修改窗口“里面”的内容,那么如果用跟随一个空白窗口的方式覆盖掉广告是否就是合法的?

关于在人家界面上插广告获利的问题,其实很难判断。如果插广告算是侵权,那么搜狗拼音、google拼音跟我的窗口一起出现一起消失,而且还打自己的logo,算不算给自己做广告呢?如果说像拼音那样的独立窗口就不算,其实soff也用的是自己的独立窗口啊。

至于绑定插件,只要不是用别人的东西来打包,插件不是强制安装而且无法卸载的话,我看也没有什么问题嘛!

总结一下就是:让外行来判定技术的对错问题,伤害太大!

全文的总结:两个案子都是涉及到广大老百姓每个人的切身利益密切相关,同时又都是争议极大、可判可不判的案子。对照杨善除恶、扶助弱小的法律目标:

  • 如果许霖案要判了,无非就是大家以后不碰柜员机多吐出的钱而已;但是soff案判了,程序员难道以后就不去深入到系统去编程?还是永远只用公开的api?如果不让广大的程序员深入专研技术,国家的网络信息安全靠谁去保卫?靠城管吗?
  • 如果许霖案不判,以后银行就要改进工作制度,软硬件的订购行为就会标准化。如果soff案不判,tx就会想办法用“不作恶”来吸引用户,加强软件测试并且提供更好的操作体验。

我想,相对于以上的两种分析,都不判对社会的促进最大,而判了,则是继续养了一群懒人,并且促使他们躺在判决书上继续懒下去。

posted @ 2008-03-26 22:39 老翅寒暑 阅读(3392) | 评论 (54)编辑

2008年1月3日

101个LINQ例子

     摘要: 101 LINQ Samples 说明:本文摘抄自Microsoft的MSDN网站,因为原文不方便快速阅读和学习,故此调整如下。本文所有权利归Microsoft公司所有。如果您觉得本文侵犯了您的著作权,请来信告知,我将即时改正!多谢! 所有的代码运行结果请点击代码上的链接,到MSDN网站去看。 Restriction Operators Where - Simple 1 public void... 阅读全文

posted @ 2008-01-03 16:19 老翅寒暑 阅读(790) | 评论 (3)编辑

2007年12月28日

和老同事的谈话:关于职业生涯以及MDA

应该是bigtall在2007年度的最后一篇了,虽然2007年欠了大家很多帐,但是继续写应该是在2008年的事情了,很多时候非常有想法的东西,真正到落笔的时候,还是会发现欠缺许多东西,所以很多时候犹豫再三,还是不敢落笔。请大家见谅!

今天有机会见到了以前共事的同事XJ,在KFC海阔天空谈了很多,主要都是集中在各自工作和技术方面,有点启发,所以写下来备忘。大家简单看一个意思就行了。

以前觉得年龄不是一个问题,现在想来其实还真是一个大问题,不知不觉都是30出头的人了,眼看着就要“奔四”,这个日子是慢慢的后半段不如前半段长了。今后的职业生涯怎么变化,也成了一个越来越需要思考的问题。什么是“有出路”的职业?我们经过讨论,觉得定义应该是“一个不需要改行可以一直做到退休的职业”就是一个“有出路”的职业。但是现在的职业真的是一个“有出路”的职业吗?

1.年龄问题

还记得20多岁的时候曾经想着30岁的程序员会不会有人要?结果现在发现30岁的程序员其实很厉害,并没有想象中的“没人要”的情况,相反是一个“香饽饽”(不过也挺贵的)。再看看我们国外那些出书写作的,都是四五十岁的。似乎看起来很不错,但是在中国的我们到40、50的时候还会不会有人要?

2.自身问题

再看,其实周围很多同事都已经转行了,总体来说,30岁以后的程序员数量是呈现一个逐步减少的趋势。30岁之后的程序员们的生活压力却是在不断的加大:娶妻生子,还有房子(加息听说过吗?);但是自身的学习能力、身体状况却是跟20多岁的时候不一样了,还记得bigtall大学时候40个小时可以只睡2小时,现在呢?没有一两天缓不过来!

3.竞争问题

软件这个行业的新技术新思想层出不穷,所以学习是很重要的,如果不持续学习,就可能在下一波的技术浪潮中被淘汰。可是随着年纪越大,学习能力就越弱,也许现在我们可以自豪地说自己是属于剩下的20%,但是我们50岁的时候还可以这样保证?跟20岁的人比还是跟50岁的人比?

4.软件技术发展的问题

曾经有一段时间国内有些人叫嚣什么“软件蓝领”。bigtall认为喊早了,软件开发领域的职责分工随着整体水平的提高逐渐变得越来越细(10年前听说过哪家公司招聘SQA吗?),到时候别说是“软件蓝领”,就是“软件背心”出现也是迟早的事情,但是这个前提应该是分工更加细致的时候,不会是现在,恐怕也不会是2020年(也就是我们50岁)之前。但是这个是一个持续渐进的过程。MDA概念的出现,其实就是标志着“软件蓝领”慢慢出现过程的开始。用bigtall自己的信条可以来解释这个发展趋势“如果人确实是不可靠的话,就尽可能让机器来做”。当然,编码是目前冯诺依曼体系的计算机软件开发中必不可少的部分,正是因为机器不可能替代一切,所以未来会出现“软件蓝领”,当然也会出现“软件金领”(记得C#的设计者吗?)。

面临这样的趋势,如果不因势而变,要想自己的“奶酪”不被人动都很难。

5.公司发展的问题

其实小公司壮大成为大公司的概率比我们想象的都要小。那些成型的大公司(或机构),比如NASA(美国宇航局)、IBM、Microsoft甚至华为,他们的软件开发模式已经基本成熟,可以开发出高质量的软件,而且收入不小,他们有能力从人群中选择最优秀的人为他们服务,而且更会培训新人、挖掘人才。慢慢地,他们的软件开发形成了一种正向循环:好公司-好人-好制度-好软件-好收入-更好的公司-更好的人-更好的制度...

相比小公司,因为能力所限,所以会做很多短期工作,导致产品运行、维护代价太大,有不容易找到合适的人,也少有资源去培训员工,慢慢地就走向了一种负面循环:小公司-软件质量问题-收入减少-员工流失-更小的公司...除了那些有决心摒除软件开发中短视行为的公司,并且有合适的机遇,才能壮大成为大公司。其他的基本都没有未来

那么,小公司能提供给我们“有出路”的职业吗?大公司又能提供几个?要进大公司,又有几个人能入他们的“法眼”呢?

总之一句话:有危机感,也有机会。

接下来谈一下MDA的事情。因为bigtall这两年一直在关注这个问题,但是目前的软件开发现状一直不能令bigtall满意。我们目前的软件开发已经进入了“组件化”的时代,但是显然现在的“组件化”带来的好处并不如想象的那么大。

bigtall一直很羡慕IC行业的做法,他们用一块块集成电路用几个线连一下就可以弄出这么多神奇的东西,从电视到电脑,从手表到手机,从火箭到卫星(注:我说的是硬件范畴)...虽然是差异性很大的领域,但是他们基本都会用到相同的IC(航天领域的对可靠性要求有不同,但是IC模块信号差异不大)。但是相比我们的“组件”,“集成电路(IC)”的通用性确实要大的多。为什么?

通过和XJ的探讨,我们认为IC和“组件”的接口规范是一致的:一定规则的输入,一定规则的输出。但是IC的输入输出是“信号”,而“组件”的输入输出却是“数据”。不同信号之间的差别,无非集中在电压、电流、频率三个方面,而且这三个方面都相对独立,可以单独拿出来处理;但是“数据”之间的区别却太大了。我们的“组件化”过多地强调了“接口规范”但是却忘记去屏蔽掉“数据”之间的差异,而更要命的是计算机程序不仅和“数据”,而且和“数据结构”之间的耦合实在是太紧密了。泛型(Generic)的出现主要是解决了“代码”和“数据结构”之间的耦合过紧的问题。继承(Inherit)和实现(Implement)的出现部分解决了数据的差异。但是“组件”是一个运行时的东西,所以泛型对“组件”和“数据结构”之间的解耦没有帮助。真正的“组件化”时代,bigtall认为还要有“设计时”“组件”的支持。

“设计时组件”的应用场景就是在MDA中。只有“设计时组件”出现,才能解决“组件”和“数据结构”之间耦合过紧的问题,才能提供真正类似于IC应用那样的水平的“组件”。两年前参加过一个“华罗庚软件”公司的推广会(他们老总自称是华老的弟子),这个公司致力于把“数据流图”可执行化,也就是通过设计器把各种设计好的模块像积木一样连起来,最终形成一个应用程序。这个思路很好,但是软件bug太多,听半路就走了。

可见软件开发的“积木化”是很多人的理想,一个“积木式”的软件模块无非就是若干个输入,若干个输出罢了。bigtall把这种“积木化”的模块用一个汉字“”来表示:横线表示区域界线,也可以表示“积木”本身,横线上面的三个笔画表示输入,横线下面的部分表示输出。现在的情况是做一个“米”比较容易,要把一堆“米”做成一个熟饭却是相当的不容易。我们目前的“组件化”看起来依然是在“一粒生米”的阶段,离煮成“一锅熟饭”的时候还早得很。

如果我们不仅有“运行时组件”也有“设计时组件”的话,加上每一个步骤的“可自定义”特性,我想MDA就会让编程进入一个新的时代。10年前流行的4GL并没有带来什么太大改变,但是以MDA以及类似的概念恐怕会带来深刻的变化。bigtall和XJ曾经谈到了MDA代码生成的反向工程问题,提出了一个疑问:软件行业普遍存在着不设计就进行编码的问题,但是电子行业似乎没有这个做法,他们都是仔细地计算每一个IC的输入输出参数,然后装板调试,从来没有说是把IC先接起来,然后再去设计或者“补设计”。道理很简单,IC随便连起来会烧片子的,会有实际现象(比如火光、闪电等)和真实的物品的损失,现实不容许他们这么做。但是软件太灵活了,而且损失的“时间”对很多人来说无所谓(不珍惜时间算不算民族劣根性?),所以我们会有“先实现后设计”的奇怪而荒谬的做法。其实仔细想想,真的我们实现代码的时候没有“设计”过程?不是,我们都是要想好了才能去动手的,只是不习惯去把设计用文字和图画形式表现出来,稳定下来之后再去编码而已。如果MDA成为了软件开发的主要方式,这种“先实现后设计”现象自然就销声匿迹了。

当然,要真正深入去探索MDA,路还很长!

祝大家元旦快乐!新的一年里,事业顺利!进步!

posted @ 2007-12-28 18:00 老翅寒暑 阅读(4847) | 评论 (20)编辑

2007年12月13日

Appfuse and Quartz: a very quick tutorial

这个文章被GFW了,拿下来做参考

Appfuse and Quartz: a very quick tutorial

Quartz is an enterprise-class job scheduler for use in stand-alone and full-blown J2EE applications. It's very easy to implement, scalable and light-weight. I have used Quartz in a number of projects and it works a wonder, no matter if deployed in a complex Weblogic application or a light pojo-based one.
Integrating Quartz with AppFuse (and Spring) is a simple task. For basic scheduling it does not even require you to write a single line of code, everything is done inside the Spring configuration file.
In this tutorial, Quartz is used to invoke a dummy method on the service layer. Things shouldn't be much different in a real life implementation, except that your business logic will be probably using a "dao" implementation to access the persistence store.
During this tutorial I will use %APPFUSE% to reference the "HOME" folder of your Appfuse based application.

Configuration

  • Download and uncompress the latest release of Quartz (1.5.2).
  • Create a folder named quartz-1.5.2 in your %APPFUSE%/lib directory and copy the quartz-all-1.5.2.jar into it.
  • Add the following entry to the lib.properties file, located in %APPFUSE%/lib:
    #
    # Quartz - http://www.opensymphony.com/quartz/
    #
    quartz.version=1.5.2
    quartz.dir=${lib.dir}/quartz-${quartz.version}
    quartz.jar=${quartz.dir}/quartz-all-${quartz.version}.jar
    
  • Add the Quartz library to the deployed webapp. Add the entry <lib file="${quartz.jar}"/> at the end of the "package-web" target in the %APPFUSE%/build.xml file.

Code

  • Add a dummy method to the service layer. The purpose of this method is to display how Quartz can call a method on your service layer.
  • Add the following method to UserManager.java
    public void checkLastLogin();
  • Add the following method to UserManagerImpl.java
    public void checkLastLogin() {
        log.info("Congratulations! Quartz called the dummy method!");
    }
    
  • Add the following xml beans to the applicationContext-service.xml file located in %APPFUSE%/src/service/appfuse/service.
    <bean id="dummyJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    	<property name="targetObject"><ref bean="userManager"/></property>
    	<property name="targetMethod"><value>checkLastLogin</value></property>
    </bean>
    
    <bean id="dummyTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
      <property name="jobDetail">
    	<ref bean="dummyJobDetail"/>
      </property>
      <property name="startDelay">
    	<value>30000</value>
      </property>
      <property name="repeatInterval">
    	<value>45000</value>
      </property>
    </bean>
    
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
      <property name="triggers">
    	<list>
    
    	  <ref local="dummyTrigger"/>
    
    	</list>
      </property>
    </bean>
    
    The SchedulerFactoryBean serves as a factory for a Quartz scheduler, allowing the registering of scheduled jobs via <list> of <refs> to Trigger beans (either SimpleTriggerBean or CronTriggerBean).

    A Spring SimpleTrigger bean is a wrapper for a Quartz SimpleTrigger that is "a concrete Trigger that is used to fire a JobDetail at a given moment in time, and optionally repeated at a specified interval" (from the Quartz documentation). A Spring SimpleTrigger holds a reference to a JobDetail bean (the actual business logic that we want to schedule) and has a few more options, such as the "repeat interval" and the "start delay". Both properties are expressed in milliseconds.

    The MethodInvokingJobDetailFactoryBean is the last bean in the xml snippet. As you can see in the Spring documentation, this bean extends a subclass of the Spring MethodInvoker helper class. The MethodInvoker bean dynamically invokes a method (even a static one) on an arbitrary class, in a declarative way. In a similar fashion, the MethodInvokingJobDetailFactoryBean allows us to specify which method of our service we want to schedule. In the example above, we set a <ref> to the UserManager bean and we set the "checkLastLogin" method as "targetMethod".
    And so, the "checkLastLogin" method will be invoked every 45 seconds.
  • In case of more sophisticated scheduling needs, Quartz has a Cron based trigger, wrapped by a Spring bean.
    The CronTriggerBean triggers a JobDetail using a "cron" expression.
    <bean id="dummyCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"/>
      <property name="jobDetail"/>
    	<ref bean="dummyJobDetail"//>
      </property/>
      <property name="cronExpression"/>
    	<!-- Fire every minute starting at 2pm and ending at 2:59pm, every day --/>
    	<value/>0 * 14 * * ?</value/>
      </property/>
    </bean/>
    
    The CronTriggerBean is very similar to a SimpleTriggerBean, except that a cron expression is required.

Quartz persistence

Quartz uses JobStores to persist information about triggers, jobs and calendars. The default Job Store is the "RAMJobStore". As indicated by the name, it keeps all the information in RAM, hence triggers and job information don't survive JVM restarts. Actually, this can be the desired behavior for an application. On some occasions, it may be necessary to persist the scheduling information on some less volatile storage. That's when the "JDBCJobStore" comes in handy. This JobStore persists all the scheduling information in a database. It comes in two flavours:
  • JobStoreTX: used to control transactions or when operating in a non-application server environment
  • JobStoreCMT: used when operating within a managed environment and you want the container to manage the transactions (eg. Spring)
The default JobStore used by Spring is the JobStoreCMT, through a LocalDataSourceJobStore that delegates to a Spring-managed DataSource instead of using a Quartz-managed connection pool. Due to the lack of serialization capability of the MethodInvokingJobDetailFactoryBean, it is not possible to activate the JDBCJobStore and invoke a method on the service layer using the previous example.
A QuartzJobBean must be used instead. A QuartzJobBean is a simple wrapper around a Quartz Job class.
public class DummyJob extends QuartzJobBean {

  private ApplicationContext ctx;
  <!-- Always expose this --/>
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.ctx = applicationContext;
  }

  protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    SomeManager sm = (SomeManager)ctx.getBean("someManager");
    sm.doScheduledOperation();
  }
}
As you can see from the code above, it's not possible to inject a bean reference into the QuartzJobBean without incurring serialization issues. Therefore the service bean has to be retrieved explicitly from the ApplicationContext. Few additional changes have to be implemented to activate a JDBCJobStore and use a QuartzJobBean:
  • Add this entry to the properties.xml file located in %APPFUSE%
    <pathelement location="${quartz.jar}"/>
    
    The entry has to be added in the "service.compile.classpath" section of the file.
  • After having added the new QuartzJobBean implementation class that invokes some service method on the service layer, change the previous configuration xml:
    <bean id="dummyJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
      <property name="jobClass">
    	<value>com.thetapenet.service.DummyJob</value>
      </property>
      <!-- This would be great, but doesnt work with JDBCJobStore -->
      <!--<property name="jobDataAsMap">
        <map>
    	  <entry key="someManager">
    	     <ref bean="someManager"/>
    	  </entry>
    	</map>
      </property>-->
    </bean>
    
    <bean id="dummyTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
      ...
    </bean>
    
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <property name="applicationContextSchedulerContextKey">
    	<value>applicationContext</value>
      </property>
      <property name="triggers">
    	<list>
    	  <ref local="dummyTrigger"/>
    	</list>
      </property>
    </bean>
    
    The SchedulerFactoryBean now has two more properties: the "datasource" and the "applicationContextSchedulerContextKey". "datasource" instructs the SchedulerFactoryBean to activate the JDBCJobStore and use the global datasource defined in the applicationContext-resources.xml configuration file. The second property is used to propagate the Spring ApplicationContext all the way to the JobDetailBean. Please note that a JobDetailBean implementation must always expose
    public void setApplicationContext(ApplicationContext applicationContext) {
    in order for the ApplicationContext to be injected into the Job.
  • The final step requires the creation of the database tables used by Quartz for its own persistence. The Quartz distribution comes with ready-made db scripts that should cover most of the available databases. They can be found in %QUARTZ%/docs/dbTables. I have used the MySQL version of the script and it worked perfectly.

posted @ 2007-12-13 18:26 老翅寒暑 阅读(258) | 评论 (0)编辑

2007年12月12日

应用程序框架设计之二:分层和层间数据传递(下)

上一篇:应用程序框架设计之二:分层和层间数据传递(上)

看了上篇之后大家的留言,好多人觉得DTO分这么多形态,给这么多名词,可能在实际中没有用处。其实相比.net而言,java在架构上的功力要深厚许多,要谈架构如果避开java不谈的话,就会肤浅许多。这一点上net可能还要许多年才能赶上(如果不加倍努力,恐怕永远就落后于java了)。至于说VO、BO、PO没有人分那么仔细,恐怕只是大家自己没有意识到自己在使用吧。正好下篇要对流行的架构进行分析,bigtall就斗胆show一下分析结果了。

针对在DTO的形态转换问题,bigtall选择了几个流行的架构进行了分析,主要就是想要看看他们是怎么做的,这几个架构分别是Petshop 4.0, Struts, Tapestry, Spring MVC。

首先我们看Petshop4,项目中包含22个子项目,我们按照三层架构的层次分类对这些子项目归类:

显示层:WEB  CacheDependencyFactory ICacheDependency  TableCacheDependency

业务层:Model BLL IBLLStrategy IMessaging  MessagingFactory MSMQMessaging OrderProcessor

存储层: DALFactory IDAL DBUtility  OracleDAL    SQLServerDAL

权限相关的独立部分:SQLProfileDAL  ProfileDALFactory OracleProfileDAL IProfileDAL Membership Profile

大家注意业务层的Model,里边定义了项目中使用到的所有数据对象,典型的BO。因为asp.net的组件化设计思想,导致没有明确的VO概念(被分散在诸如textBox1.Text中了)。但是我们看WEB项目中的AddressForm自定义控件代码:

    public partial class AddressForm : System.Web.UI.UserControl {
        public AddressInfo Address {
            get { ....
                string firstName = WebUtility.InputText(txtFirstName.Text, 50);
                ......
                return new AddressInfo(firstName, lastName, address1, address2, city, state, zip, country, phone, email);
            }
            set {
                if(value != null) {
                    ...
                    if(!string.IsNullOrEmpty(value.FirstName))
                        txtFirstName.Text = value.FirstName;
                    ...
                }
            }
        }

    }

分明就是一个典型的VO到BO之间相互映射的代码。同样我们看同一project下的CheckOut.aspx.cs也存在类似的转换代码:从WEB界面控件中提取数据,构建OrderInfo,最终传入SQLServerDAL或者OracleDAL的Order类中,大家可以看到如下的代码:

        public void Insert(OrderInfo order) {...
            orderParms[0].Value = order.UserId;
            ...
            orderParms[19].Value = order.AuthorizationNumber.Value;
            ...
}

这个同样是一个典型的BO到PO的转换过程,只不过我们用类似Hashtable的结构代替了自定义的PO对象而已。

参考文献:Microsoft .NET Pet Shop 4 架构与技术分析

接下来我们来看Struts。所有的WEB提交数据被放置到所谓的ActionForm对象中,很多人为了方便,直接自定义了一个类似Hashtable的结构来做通用的ActionForm了。这个ActionForm就是我们所说的VO。然后ActionForm传递给Action进行处理,一般Action都会把ActionForm内容作一次校验,然后构建BO,传递到Service层进行处理,Service层进行处理之后,调用DAO对象存储。因为java程序基本都使用了hibernate或者ibatis等模块,所以BO到PO的转换被封装掉了。

这里很多人使用struts或者其他java框架的时候,经常在Action中添加了过多的业务逻辑代码,把原本属于界面层后端的Action做成了业务层的东西,然后图方便对Service层代码只是做一个简单的转发调用,类似boolean XXService(XXBO bo) { return dao.save(bo); },实在是大错特错了。

说明:bigtall并不认同参考文献中认为的Action属于业务逻辑层。我认为业务逻辑层判断的一个标准是不加修改或者加一个简单的wrap,就可以暴露服务作为SOA。Action显然不满足这样的要求。退一步如果非要说Action属于业务逻辑层,那也只能是一个专门针对struts的Service封装接口,不合适包含大量的业务逻辑代码。

Struts返回数据到界面层的方法是通过把BO填入到一个Hashtable结构,由界面jsp直接使用其值,就跟asp用法一样。

参考文献:Struts,MVC 的一种开放源码实现

Tapestry框架是一个和asp.net采用了相似设计思想的组件化的web框架。一个web请求提交到服务器的时候,tapestry把请求中的内容填入到页面对应的BasePage派生类对象的属性中,这是一个自动的VO填充过程(类似asp.net中把用户输入的内容填充到对应的TextBox对象的Text属性中)。然后这个BasePage派生类对象把自己的属性最终填充成一个BO,传递到Service层,Service层调用DAO对象通过Hibernate或者ibatis存储到数据库中。

返回数据到界面层使用ognl表达式,基本原理类似把BO或者VO填入Hashtable结构,然后酌情用ognl表达式选取。比asp/jsp用法要利索一些,因为是组件化,所以很整齐。

参考文献:了解 Tapestry,第 1 部分了解 Tapestry,第 2 部分

Spring作为No.1的AOP框架,灵活性和可扩展性是它最大的优点。在Spring MVC框架中,web请求通过参数HttpServletRequest(类似一个Hashtable结构)存放所有的用户请求数据,传递给Controller处理。如果Controller是从SimpleFormController派生而来,则可以在jsp中使用bind机制自动把提交数据填充到一个指定的对象中(也就是VO了),否则就要手工从HttpServletRequest中获取。在Controller中可以把数据传递给Service层处理了。Service层的处理和其它java框架相同。

返回数据到界面层可以使用很多种方法,看使用不同的ViewResolver而定,可以用jsp,也可以用freemarker脚本或者velocity脚本,也可以自己定义一种新型的界面层描述。

参考文献:一步一步开发Spring Framework MVC 应用程序

从以上简单几个架构的分析,我们可以明显看出VO/BO/PO的相互转换过程。但是都有一个特点,就是对VO转BO有明确的处理和包装,但是对BO转VO忽略掉了,直接使用暴露BO对象,使用ognl或者其他技术直接取值。asp.net的WebForm相对复杂一点,但是也同样避开了VO的问题,但是赋值放到了类代码里边,灵活性相对少了一些。而BO转PO的问题,都倾向于用类似ORM的模块来处理。

DTO形态之间的转换讲了一大半,但是一个很实际的问题需要我们来面对,就是数据库ID的暴露问题。根据我们的理论,ID实际是属于PO的东西(以下简称POID),其实VO和BO中并不需要这个POID,另外就是暴露这个POID之后会存在很多的隐患,一旦程序检查不严格,很容易被人假造一个请求去修改不应该的数据。但是我们真的可以抛弃POID不用吗?bigtall同样用一个例子来说明。

bigtall依旧使用上篇的LoginInfo的例子,不过这次的场景是查询特定的LoginInfo并修改之。这个场景包含了如下的几个过程:

  1. 输入查询条件LoginInfoQuery到服务层,并返回LoginInfoBO[]对象数组。
  2. 展示LoginInfoBO的数据在界面层,并等待修改
  3. 保存界面层提交的修改之后的LoginInfoBO到数据库

这里就暴露了一个问题,如何让系统了解第3步和第2步的LoginInfoBO就是同一个对象?同样问题也存在BO和PO的转换中,如何把特定的BO转化为特定的PO?这个也就是我们现在为什么摆脱不掉这个POID的根本原因了。一句话,没有POID,我们无法解决对象映射的问题。

真的我们只能通过POID来实现对象映射吗?不是!我们有很多方法可以解决这个问题,只是不如直接使用POID来的方便。比如我们是不是可以用一个Hashtable来保存VP、BO和PO映射关系?当然可以,但是我想我们可以用更好的方法,因为这个问题归根到底就是对象唯一标识(以下简称OID)的问题。

要解决这个问题,我们需要两个条件:一是对象有一个唯一的标识序号OID,二是保存VO和BO、BO和PO对象之间的唯一标识映射关系。直接使用POID可以很容易满足这两个条件,但是带来了极大的程序风险,一旦界面层保存的POID被非法修改的话,程序对这方面的防范很困难,而且很多程序根本就是完全假设界面层POID是可靠的。但是如果程序应用在金融、财务等领域,操作人员就会极有可能有动机去修改这个界面层(尤其是浏览器中)的POID。而且从一般情况下他们会很容易推卸责任(程序bug嘛!要赔偿也是软件开发商赔偿)。所以,可靠的做法就是避免把POID当作通用的OID,而是给每一个对象分配一个OID,同时保证OID之间的简单映射关系。

bigtall给出的OID设计是这样的:所有的DTO对象都继承接口IIdentitable,接口IIdentitable有唯一的属性OID,对象构造的时候,由ClassFactory或者自身的构造函数自动给OID赋值,赋值的算法是这样的:使用session id的简单转换作为key,把POID加入一个校验位(记得身份证号码最后的X吗?)之后的新POID用DES算法加密,这个加密之后的结果就作为BO的OID,如果需要,同样的步骤可以用作BOID到VOID的转换中。用这个算法可以保证不同用户的不同次登录的session id是完全不一样的,所以无法通过简单复制获得OID。其次要配合检查程序,避免用户查询到不属于自己业务范畴的数据,并尽可能对操作对象进行权限检查。

至此,bigtall把DTO的形态变化讲完了,其实还有另外一个重要的概念,DTO的设计。这个设计重要吗?答案是很重要!请看bigtall的“应用程序框架设计之三:数据传递对象的类型和设计”。

posted @ 2007-12-12 10:22 老翅寒暑 阅读(3519) | 评论 (25)编辑

2007年12月10日

应用程序框架设计之二:分层和层间数据传递(上)

上一篇:应用程序框架设计之前言

还记得97年左右开始的胖客户机和瘦客户机之争吗?之后又是CS和BS之争,然后又是两层和多层之争...,十年之后的今天我们再回过头看这些争论,一切似乎看起来都那么理所应当:程序怎么能不分层啊?可是再想一下,原来我们用了整整十年的时间才达成了一个程序架构要多层的共识(效率多低啊)!

要分层,当然基本就是三层了,其实多层的基础也是三层:界面层、业务逻辑层、存储层。多层只不过在三层的基础上把每一层或多或少再拆分出一些来而已,总的来说没有什么大的变化。本系列文章中讨论都以三层为基本概念。

本文着重讨论的不是如何分层和层的定义,而是在分层情况下,讨论层与层之间的数据传递问题。现在的程序很少仔细地去分析层与层之间的数据传递问题,通常都是一个对象从界面生成开始一路穿过,直接保存到数据库(最显著的标志当然就是xxxID了)。这样的做法对程序伤害很大。

首先我们从一个简单的例子开始:应用程序的添加用户功能。界面很简单,如下:

添加用户

要为这个界面设计数据结构通常也很简单,class LoginInfo{ public String name; public String password; } 就好了,然后我们在form提交的时候new一个并且填充好LoginInfo结构,就save(loginInfo)到数据库里边了,最常的做法还会加入一个int loginInfoID字段。我们把这种类似LoginInfo可以直接存储到数据库中的数据结构命名为Persistence Object,简称PO。嗯,看起来从头到脚用一个数据结构并没有什么问题啊!

问题会来的,bigtall来改变一下需求,通常我们需要给用户密码输入两次,所以界面修改如下:

添加用户


这样,form提交到服务器的数据结构就应该是这样:class LoginInfo2{ public String name; public String password; public String password2; },然后服务器做的第一件事情就是比较password和password2是否相等,然后new一个LoginInfo结构,把name和password填充到里边,然后保存到数据库。我们同时把LoginInfo结构修改成这样class LoginInfo{ public int loginInfoID; public String name; public String password; } 。

大家可以看到,随着需求的变化,原来的“PO直通车”演化成了两个结构,我们把LoginInfo2类似的界面层和其它层沟通的数据结构叫做View Object,简称VO。是不是这样就够了?当然不是,我们再来修改一下需求,给系统加入权限功能,所以这个添加用户实际上应该修改成这样:

添加用户



我们需要继续做一些改进(或者叫做“重构”吧),首先修改VO,同时我们把命名也规范一下:

class LoginInfoVO{public String name; public String password; public String password2; public String[] roles;},

然后把以前的LoginInfo拆分成三个类:

class LoginInfoBO{public String name; public String password; public RoleInfo[] roles;}

class LoginInfoPO{public int loginInfoID; public String name; public String password;}

class RoleInfoPO{public int loginInfoID; public String role;}。

至此,我们顺利地引出了三个概念:View Object(VO)、Business Object(BO)、Persistence Object(PO)。他们分别是三层结构的显示层、业务逻辑层和存储层内部使用的数据结构,它们还有一个统称,叫做数据传输对象Data Transfer Object(DTO)。我们也可以把VO,BO和PO看成是DTO在不同阶段的不同表示形态。当一个DTO从显示层开始穿越整个系统的时候,它的形态和结构就开始变化,从VO转变到BO,最终到PO,但是这个过程不一定是可逆的,这个过程如果反向,从PO->BO->VO,很可能就对应不同的对象了。比如当输入错误的时候,回馈页面可能就需要增加一个错误信息提示。虽然实际使用的时候,我们经常会忽略这种细微的差异性,实际上这个错误信息,只对显示层有意义。

DTO的转换规律一般可以总结为如下的几个类型,实际变化则可以是各种类型的组合:

  • 属性内容的减少

属性内容的增减在DTO不同形态之间的转变时候经常会发生。比如上例中添加用户LoginInfo对象的VO转换到BO的时候,就需要丢弃“重复输入密码”的属性。有些VO对象甚至根本不需要转换成BO。在BO转换成PO的时候同样也会有属性内容减少的情况出现,比如“部门”这类树状层次结构对象,因为运行效率的因素,也许会需要BO中有“下级部门列表”,实际存储到数据库的时候,PO只需要一个“上级部门ID”就可以了。

  • 对象内容的填充或者增加

属性内容同样会有可能增加,但是在系统处理DTO转换的时候,属性增加可能就意味着需要进行额外的查询和填充,比如我们使用“用户名”和“密码”进行登录的时候,最终系统需要通过数据库查询得到并且存储“用户ID”,以此来保证用户的唯一性。又比如提交的数据存在校验错误,我们可能需要重新刷新该页面,并且增加新属性“ErrorMessage”,以便把它显示在界面上,提醒用户注意。

  • 对象的拆分和组合

我们可以看上面最后一个“添加用户”的例子,一个LoginInfo的BO转化为PO的时候被拆分成了2个对象,一个存放基本的用户信息,一个存放对应的Role信息。通常对象拆分的时候,常常需要填充或者补足新对象的内容;而对象合并的时候,常常出现内容减少的情况。

  • 对象或者属性类型的变化

出现对象属性类型的变化在VO到BO的转换中比较常见,比如把用户输入的生日转化为一个真正的DateTime类型。

  • 属性名称的变化

属性名称在转换过程中会有变化,一般这种情况应该尽可能不要出现,但是在项目重构的时候出现的概率较大。

除了DTO不同形态之间的转换规律之外,不同形态内部还有不同的工作要做:

  • 校验

“不要相信任何用户的输入”,这是设计程序跟用户进行交互操作时候永远需要遵守的一个原则。也就是所有的外部输入都需要进行正确性的校验。校验器是分为两个层次,一个是属性层次的校验,比如“年龄”只能0到150之间有效。另外一个是对象层次的校验,或者说跨属性层次的校验,比如“年份输入闰年的时候,2月可以有29日”等。

校验并不是一个单纯的问题,几乎所有的业务逻辑校验基本都需要一次完整的贯穿所有层次的调用。代价颇大。这个也是为什么我们在显示层做很多事先校验,而一旦进入业务逻辑层的时候,校验就经常会被“事后校验”代替了,人们会使用抛出异常的方法来代替“事前检查”。

突然想起来有一句闲话要讲。这个分析过程其实在一年前就完成了,那个时候正好沸沸扬扬的SOA满天飞,当把这个DTO形态分析完毕之后,回头看SOA发现它并不属于表现层,而是属于业务逻辑层,换句话说它使用的DTO必须是BO而不是VO。而所谓的SOA也不过就是分布的业务逻辑层而已。

因为以下的部分要花费较多的时间查找,bigtall怕文章搁久馊了,也怕各位看官等得太久,就分两部分发吧。下篇我们着重分析现net平台和java平台的几个架构在DTO形态上的对比,还要谈一个实用的问题,是不是需要对象ID的问题。

posted @ 2007-12-10 18:06 老翅寒暑 阅读(3791) | 评论 (33)编辑