每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks

2006年8月26日 #

     摘要: 你每天经常访问哪些站点?

你的收藏夹里有多少收藏的站点?  阅读全文
posted @ 2008-08-09 14:42 Alex 阅读(2349) | 评论 (2)编辑 收藏

     摘要: 上次参加open party朋友拿了一本《开源技术选型手册》,翻了一下,整理成脑图(图的效果还是比文字好哦)  阅读全文
posted @ 2008-08-06 10:30 Alex 阅读(2462) | 评论 (5)编辑 收藏

     摘要: 从svn版本上checkout出来的新的版本,dwr部分的xml配置文件是红色提示错误,一开始以为是jsp的普通验证错误,后发现不是那么回事。  阅读全文
posted @ 2008-08-04 15:00 Alex 阅读(1531) | 评论 (0)编辑 收藏

1.很多人喜欢用代码开发工具的debug功能来跟踪问题,虽然最终可以解决问题,但是从方向或者思想上不建议这样做,我依然记得最初我的技术经理对我说过 “代码不是调出来的”。

2.不是调出来的那是怎么出来的呢? “写出来的。” 呵呵,别扭,但是想想看,是否有点道理?

3.从另一个层面,我们需要加强代码的规范的写法,这就好比设计,先尽量将设计(在敏捷的年代似乎谈设计不太入流,那么你可以认为那是一种思考吧)做的到位一点

4.回到一个大的命题:战略如果错误,执行的越快死的越快;同理,思路或者方向如果有问题,做的越带劲,你越难受。

5.让我们朝这个目标挺进:代码一次编写就通过! 我见过一些这样的高人,只要你努力,是可以做到的。

posted @ 2008-08-03 20:56 Alex 阅读(2414) | 评论 (23)编辑 收藏

     摘要: 又一阵热风吹过来了,犹如当初的asp,犹如近段时间的SOA  阅读全文
posted @ 2008-08-03 01:02 Alex 阅读(1970) | 评论 (2)编辑 收藏

     摘要:   阅读全文
posted @ 2008-07-03 14:13 Alex 阅读(1568) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2008-07-03 14:12 Alex 阅读(2927) | 评论 (1)编辑 收藏

     摘要:   阅读全文
posted @ 2008-07-03 14:11 Alex 阅读(3482) | 评论 (5)编辑 收藏

//本站点内容来自于 颠覆软件

1.机器的ip经常改变,到公司一个ip,到客户那一个ip,到家又是一个ip,不知怎么搞的,今天到家oracle的dbconsole就启动不了了.

2.用命令行的方式启动 emctl.bat start dbconsole,结果还提示ORACLE_SID 还没有设置,晕,这个怎么没有了,set 一个吧 set ORACLE_SID=dggmcc (我的数据库实例)

3.还是有错,提示找不到192.168.0.52.这个地址,应该是ip和当初装数据库的时候IP不一样的缘故,将hosts设置一下 192.168.0.52   IDEA

4.OK

posted @ 2008-07-03 14:10 Alex 阅读(999) | 评论 (0)编辑 收藏

1.架构的分类:业务架构、数据库架构、软件设计架构,正在流行的所谓soa架构也算吧

2. 涉及到的主要内容:

  • 需求分析
  • 领域分析
  • UML建模
  • 文档设计管理
  • 设计模式
  • 基于javaEE5的设计(jsf+EJB3)[个人感觉jboss seam是最好的代表了]

3.今天一同事说在网上看到统计数据说基于soa的架构做的项目比传统的做法成功率确实高,可能性是存在的,不过也不排除这些统计数据是某些大厂赞助的结果 :)

4.今年打算有机会的话培训一下软件设计师的课程,内容摘要如下:(来自国信培训)

一、面向对象分析设计与UML

Ø 面向对象分析与设计OOAD

Ø UML建模与ICONIX进程

Ø 领域建模与用例建模

Ø 需求分析与UseCase

Ø 健壮性分析

Ø 时序图

Ø 对象图、状态图、活动图与协作图

Ø 类图、包图与组件图

Ø 部署图

Ø UMLRUP

Ø 设计进度和设计粒度的控制

二、设计模式

Ø GOF设计模式

Ø 创建型模式

Ø 结构型模式

Ø 行为型模式

Ø 模式扩展

Ø 开源项目中的设计模式

Ø 模式与交流

三、软件设计管理

Ø 软件设计文档

Ø 软件流程改进策略

Ø 软件设计风险管理

四、相关软件设计案例集

posted @ 2008-07-03 14:08 Alex 阅读(1544) | 评论 (0)编辑 收藏

//本站点内容来自于www.foxlog.org


1.中国还没有真正伟大的软件公司,就当自己做一下白日梦了

2.需要一个协同软件,比如oracle协作套件,或者IBM的Lotus,目的,提供邮件、文档、工作空间、门户、网络会议、移动办公等,让公司的所有资源在一个统一的平台上实施;当然,出于成本考虑,我也可以选择google的企业套件,最直接的方式了,而且效果不错,不过没有网络会议和门户功能,可以考虑使用webEx等 (近期,流行的一个观点是在协作平台里引入web2.0的相关技术)

3.需要一个学习平台,比如oracle ilearing.用于员工的入职须知,技能培训,为学习型组织提供一个最有力的帮助,最大的问题可能不是学习平台本身,而是学习内容的规划和设计,必要的时候需要购买 .  学习平台最终的目的就是一是培训员工,另一个就是知识共享。现在很多公司有这么一个毛病,公司运营了很多年,但是随着员工的逐步更换和流失,很多以前精华的东西都不见了,如果主意了知识的共享和积累(通过软件平台),这个问题就是另一种情况了.

4.需要一个 oa平台,主要基于公司的业务流程,目前大多数oa是基于工作流的机制来实现。事实上,oa的发展历程已经比较长了,也相对比较成熟,但是究竟能发挥多大作用或者该如何发挥最大的作用还依然是一个问题。

5.建立一个项目管理系统平台,所有项目都在上面监控,它应该是动态的,可以看到每一天的进度,最好能从上面能对项目的各个组员的工作作出评估,作出即时的沟通和修正。

6.软件开发过程系统:包含架构设计管理、开发动态控制管理、版本控制、测试管理 .这些内容的主要目的是保证开发的规范话、文档化

7.会议系统管理. 每个公司都需要开会,但是开会的层次差别太大了,存在的问题主要有:会议目标不明确,会议过程没有效率,会议结果不明确,会议结束后没有后期跟踪管理.关于这方面可以推荐一篇文章 九段秘书

8.以上好像偏重于技术的层面比较多,实际上一个公司的老总最看重的其实必然是市场,所以一个适合自己公司的ERP或者CRM系统也许是必要的,注意,我这里说的是也许。

9.最后,所有的软件仅仅提供了一个平台,它是“死”的,或者说其本身并不会发生什么作用,更关键的是看不见的“软件”——企业文化,一切的效率与创新都来自于公司的文化氛围,在这个基础上,软件才会有推波助澜的作用。

posted @ 2008-07-03 14:07 Alex 阅读(1040) | 评论 (3)编辑 收藏

     摘要: 很多东西是拿来被利用的,被 Fuck的,不是让你消磨时间的.  阅读全文
posted @ 2007-09-01 12:42 Alex 阅读(2510) | 评论 (5)编辑 收藏

     摘要: 项目,是一个范围很广的概念,三峡大坝是一个项目,IT企业的一次开发任务也是一个项目,个人某个时间段的活动安排仍然可以看作是一个项目.

  阅读全文
posted @ 2007-08-19 13:15 Alex 阅读(1780) | 评论 (7)编辑 收藏

     摘要: 经常用到update语句,不过很少用到其他表的数据来更新当前表,这次用到了,总结一下

  阅读全文
posted @ 2007-08-19 12:49 Alex 阅读(2221) | 评论 (1)编辑 收藏

key words: plsql,导出数据,大数据量

最近的账务的项目中需要导出Excel报表,数据量比较大,解决方案如下 :

1.通过plsql在服务器端查询出符合要求的纪录,然后直接写到文本文件中

2.在web端通过流的方式读文本文件,通过POI写到Excel,将生成的Excel流写到respoonse实现下载

这样做的好处是不必一次性将满足条件的纪录全部取出来,而是通过流的方式.

这个方式原来在处理Blob类型的字段时有直接的getStream来支持的,这次普通的数据只好通过文件的方式来实现.

贴一段sample的代码


sql 代码
 
  1. create or replace procedure alex_table_to_txt(filepath varchar2,filename varchar2) is  
  2. --var  
  3. v1 alex2.name%type;  
  4. v2 alex2.address%type;  
  5. v3 alex2.groupname%type;  
  6.   
  7. output varchar2(200);  
  8.   
  9. --file var  
  10. file_handle UTL_FILE.FILE_TYPE;  
  11.   
  12. cursor readtable is select t.name,t.address,t.groupname from alex2 t;  
  13. begin  
  14. file_handle:=utl_file.fopen(filepath,filename,'w',6000);  
  15. open readtable;  
  16.   
  17. loop  
  18. fetch readtable into v1,v2,v3;  
  19. exit when readtable%notfound;  
  20. output:=v1 || ',' || v2 || ',' || v3 ;  
  21. utl_file.put_line(file_handle,output);  
  22.   
  23. end loop;  
  24.   
  25. close readtable;  
  26. utl_file.fclose(file_handle);  
  27. end alex_table_to_txt;  
  28. /  

 

欢迎讨论你的方案

posted @ 2007-08-18 12:46 Alex 阅读(4230) | 评论 (1)编辑 收藏

     摘要: 一般来说,我们碰到问题都是无非google,看相关文档,请教专家,问圈子里的朋友,等等。我这里说的主要是个人自己的解决思路。

  阅读全文
posted @ 2007-08-18 12:17 Alex 阅读(1336) | 评论 (2)编辑 收藏

好久没有来这里了,blogjava的朋友们你们还好吗。

我的 blog搬家了,以后将主要在这里维护: www.foxlog.org 

需要和我交换连接的朋友请联系我 mail to me :  idea.wang@gmail.com,或者在我 blog留言
http://www.foxlog.org/%E7%BB%99%E6%88%91%E7%95%99%E8%A8%80
posted @ 2007-07-27 15:21 Alex 阅读(644) | 评论 (0)编辑 收藏

1.查看系统Swap空间使用

[root@jumper usr]# free
             total       used       free     shared    buffers     cached
Mem:        513980     493640      20340          0     143808     271780
-/+ buffers/cache:      78052     435928
Swap:      1052248      21256    1030992

2.在空间合适处创建swap文件

[root@jumper usr]# mkdir swap
[root@jumper usr]# cd swap
[root@jumper swap]# dd if=/dev/zero of=swapfile bs=1024 count=10000
10000+0 records in
10000+0 records out
[root@jumper swap]# ls -al
total 10024
drwxr-xr-x    2 root     root         4096  7月 28 14:58 .
drwxr-xr-x   19 root     root         4096  7月 28 14:57 ..
-rw-r--r--    1 root     root     10240000  7月 28 14:58 swapfile


[root@jumper swap]# mkswap swapfile
Setting up swapspace version 1, size = 9996 KiB


3.激活swap文件
[root@jumper swap]# swapon swapfile
[root@jumper swap]# ls -l
total 10016
-rw-r--r--    1 root     root     10240000  7月 28 14:58 swapfile
[root@jumper swap]# free
             total       used       free     shared    buffers     cached
Mem:        513980     505052       8928          0     143900     282288
-/+ buffers/cache:      78864     435116
Swap:      1062240      21256    1040984
[root@jumper swap]#  

 

 

Swap,即交换区,除了安装Linux的时候,有多少人关心过它呢?其实,Swap的调整对Linux服务器,特别是Web服务器的性能至关重要。通过调整Swap,有时可以越过系统性能瓶颈,节省系统升级费用。 

本文内容包括: 


Swap基本原理 

突破128M Swap限制 

Swap配置对性能的影响 

Swap性能监视 

有关Swap操作的系统命令 
Swap基本原理 

Swap的原理是一个较复杂的问题,需要大量的篇幅来说明。在这里只作简单的介绍,在以后的文章中将和大家详细讨论Swap实现的细节。 

众所周知,现代操作系统都实现了"虚拟内存"这一技术,不但在功能上突破了物理内存的限制,使程序可以操纵大于实际物理内存的空间,更重要的是,"虚拟内存"是隔离每个进程的安全保护网,使每个进程都不受其它程序的干扰。 

Swap 空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换。 

计算机用户会经常遇这种现象。例如,在使用Windows系统时,可以同时运行多个程序,当你切换到一个很长时间没有理会的程序时,会听到硬盘"哗哗"直响。这是因为这个程序的内存被那些频繁运行的程序给"偷走"了,放到了Swap区中。因此,一旦此程序被放置到前端,它就会从Swap区取回自己的数据,将其放进内存,然后接着运行。 

需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这样的话,Swap就会不堪重负),有相当一部分数据被直接交换到文件系统。例如,有的程序会打开一些文件,对文件进行读写(其实每个程序都至少要打开一个文件,那就是运行程序本身),当需要将这些程序的内存空间交换出去时,就没有必要将文件部分的数据放到 Swap空间中了,而可以直接将其放到文件里去。如果是读文件操作,那么内存数据被直接释放,不需要交换出来,因为下次需要时,可直接从文件系统恢复;如果是写文件,只需要将变化的数据保存到文件中,以便恢复。但是那些用malloc和new函数生成的对象的数据则不同,它们需要Swap空间,因为它们在文件系统中没有相应的"储备"文件,因此被称作"匿名"(Anonymous)内存数据。这类数据还包括堆栈中的一些状态和变量数据等。所以说,Swap 空间是"匿名"数据的交换空间。 

突破128M Swap限制 

经常看到有些Linux(国内汉化版)安装手册上有这样的说明:Swap空间不能超过128M。为什么会有这种说法?在说明"128M"这个数字的来历之前,先给问题一个回答:现在根本不存在128M的限制!现在的限制是2G! 

Swap 空间是分页的,每一页的大小和内存页的大小一样,方便Swap空间和内存之间的数据交换。旧版本的Linux实现Swap空间时,用Swap空间的第一页作为所有Swap空间页的一个"位映射"(Bit map)。这就是说第一页的每一位,都对应着一页Swap空间。如果这一位是1,表示此页Swap可用;如果是0,表示此页是坏块,不能使用。这么说来,第一个Swap映射位应该是0,因为,第一页Swap是映射页。另外,最后10个映射位也被占用,用来表示Swap的版本(原来的版本是Swap_space ,现在的版本是swapspace2)。那么,如果说一页的大小为s,这种Swap的实现方法共能管理"8 * ( s - 10 ) - 1"个Swap页。对于i386系统来说s=4096,则空间大小共为133890048,如果认为 1 MB=2^20 Byte的话,大小正好为128M。 

之所以这样来实现Swap空间的管理,是要防止Swap空间中有坏块。如果系统检查到Swap中有坏块,则在相应的位映射上标记上0,表示此页不可用。这样在使用Swap时,不至于用到坏块,而使系统产生错误。 

现在的系统设计者认为: 

1.现在硬盘质量很好,坏块很少。 

2.就算有,也不多,只需要将坏块罗列出来,而不需要为每一页建立映射。 

3.如果有很多坏块,就不应该将此硬盘作为Swap空间使用。 

于是,现在的Linux取消了位映射的方法,也就取消了128M的限制。直接用地址访问,限制为2G。 

Swap配置对性能的影响 

分配太多的Swap空间会浪费磁盘空间,而Swap空间太少,则系统会发生错误。 

如果系统的物理内存用光了,系统就会跑得很慢,但仍能运行;如果Swap空间用光了,那么系统就会发生错误。例如,Web服务器能根据不同的请求数量衍生出多个服务进程(或线程),如果Swap空间用完,则服务进程无法启动,通常会出现"application is out of memory"的错误,严重时会造成服务进程的死锁。因此Swap空间的分配是很重要的。 

通常情况下,Swap空间应大于或等于物理内存的大小,最小不应小于64M,通常Swap空间的大小应是物理内存的2-2.5倍。但根据不同的应用,应有不同的配置:如果是小的桌面系统,则只需要较小的Swap空间,而大的服务器系统则视情况不同需要不同大小的Swap空间。特别是数据库服务器和Web服务器,随着访问量的增加,对Swap空间的要求也会增加,具体配置参见各服务器产品的说明。 

另外,Swap分区的数量对性能也有很大的影响。因为Swap交换的操作是磁盘IO的操作,如果有多个 Swap交换区,Swap空间的分配会以轮流的方式操作于所有的Swap,这样会大大均衡IO的负载,加快Swap交换的速度。如果只有一个交换区,所有的交换操作会使交换区变得很忙,使系统大多数时间处于等待状态,效率很低。用性能监视工具就会发现,此时的CPU并不很忙,而系统却慢。这说明,瓶颈在 IO上,依靠提高CPU的速度是解决不了问题的。 
系统性能监视 

Swap空间的分配固然很重要,而系统运行时的性能监控却更加有价值。通过性能监视工具,可以检查系统的各项性能指标,找到系统性能的瓶颈。本文只介绍一下在Solaris下和Swap相关的一些命令和用途。 

最常用的是Vmstat命令(在大多数Unix平台下都有这样一些命令),此命令可以查看大多数性能指标。 

例如: 
# vmstat 3 
procs memory swap io system cpu 
r b w swpd free buff cache si so bi bo in cs us sy id 
0 0 0 0 93880 3304 19372 0 0 10 2 131 10 0 0 99 
0 0 0 0 93880 3304 19372 0 0 0 0 109 8 0 0 100 
0 0 0 0 93880 3304 19372 0 0 0 0 112 6 0 0 100 
............ 

命令说明: 
vmstat 后面的参数指定了性能指标捕获的时间间隔。3表示每三秒钟捕获一次。第一行数据不用看,没有价值,它仅反映开机以来的平均性能。从第二行开始,反映每三秒钟之内的系统性能指标。这些性能指标中和Swap有关的包括以下几项: 


procs下的w 
它表示当前(三秒钟之内)需要释放内存、交换出去的进程数量。 

memory下的swpd 
它表示使用的Swap空间的大小。 

Swap下的si,so 
si表示当前(三秒钟之内)每秒交换回内存(Swap in)的总量,单位为kbytes;so表示当前(三秒钟之内)每秒交换出内存(Swap out)的总量,单位为kbytes。 
以上的指标数量越大,表示系统越忙。这些指标所表现的系统繁忙程度,与系统具体的配置有关。系统管理员应该在平时系统正常运行时,记下这些指标的数值,在系统发生问题的时候,再进行比较,就会很快发现问题,并制定本系统正常运行的标准指标值,以供性能监控使用。 

另外,使用Swapon-s也能简单地查看当前Swap资源的使用情况。例如: 
# swapon -s 
Filename Type Size Used Priority 
/dev/hda9 partition 361420 0 3 

能够方便地看出Swap空间的已用和未用资源的大小。 

应该使Swap负载保持在30%以下,这样才能保证系统的良好性能。 

有关Swap操作的系统命令 


增加Swap空间,分以下几步: 
1)成为超级用户 
$su - root 

2)创建Swap文件 
# dd if=/dev/zero of=swapfile bs=1024 count=65536 

创建一个有连续空间的交换文件。 

3)激活Swap文件 
#/usr/sbin/swapon swapfile 

swapfile指的是上一步创建的交换文件。 4)现在新加的Swap文件已经起作用了,但系统重新启动以后,并不会记住前几步的操作。因此要在/etc/fstab文件中记录文件的名字,和Swap类型,如: 
/path/swapfile none Swap sw,pri=3 0 0 

5)检验Swap文件是否加上 
/usr/sbin/swapon -s 


删除多余的Swap空间。 
1)成为超级用户 

2)使用Swapoff命令收回Swap空间。 
#/usr/sbin/swapoff swapfile 

3)编辑/etc/fstab文件,去掉此Swap文件的实体。 

4)从文件系统中回收此文件。 
#rm swapfile 

5)当然,如果此Swap空间不是一个文件,而是一个分区,则需创建一个新的文件系统,再挂接到原来的文件系统上。

posted @ 2007-06-18 14:27 Alex 阅读(995) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-06-13 16:43 Alex 阅读(3727) | 评论 (4)编辑 收藏


come from here

如果将需求分析阶段的工作归结为编写需求规格说明书,这种简化的做法往往是导致项目后期层出不穷问题的罪魁祸首。建议采用以下步骤形成软件需求:获取用户需求→分析用户需求→编写需求文档→评审需求文档→管理需求。下面我们先来讨论前两个步骤(获取用户需求、分析用户需求)的做法。

获取用户需求

  这是该阶段的一个最重要的任务。以下为获取用户需求需要执行的活动(如图1所示)。

  ● 了解客户方的所有用户类型以及潜在的类型。然后,根据他们的要求来确定系统的整体目标和系统的工作范围。

  ● 对用户进行访谈和调研。交流的方式可以是会议、电话、电子邮件、小组讨论、模拟演示等不同形式。需要注意的是,每一次交流一定要有记录,对于交流的结果还可以进行分类,便于后续的分析活动。例如,可以将需求细分为功能需求、非功能需求(如响应时间、平均无故障工作时间、自动恢复时间等)、环境限制、设计约束等类型。

  ● 需求分析人员对收集到的用户需求做进一步的分析和整理。下面是几条常见的准则:

  ⑴对于用户提出的每个需求都要知道“为什么”,并判断用户提出的需求是否有充足的理由;

  

  图1 获取用户需求的活动

  ⑵将那种以“如何实现”的表述方式转换为“实现什么”的方式,因为需求分析阶段关注的目标是“做什么”,而不是“怎么做”;

  ⑶分析由用户需求衍生出的隐含需求,并识别用户没有明确提出来的隐含需求(有可能是实现用户需求的前提条件),这一点往往容易忽略掉,经常因为对隐含需求考虑得不够充分而引起需求变更。

  ● 需求分析人员将调研的用户需求以适当的方式呈交给用户方和开发方的相关人员。大家共同确认需求分析人员所提交的结果是否真实地反映了用户的意图。需求分析人员在这个任务中需要执行下述活动:

  ⑴明确标识出那些未确定的需求项(在需求分析初期往往有很多这样的待定项);

  ⑵使需求符合系统的整体目标;

  ⑶保证需求项之间的一致性,解决需求项之间可能存在的冲突。

分析用户需求

  在很多情形下,分析用户需求是与获取用户需求并行的,主要通过建立模型的方式来描述用户的需求,为客户、用户、开发方等不同参与方提供一个交流的渠道。这些模型是对需求的抽象,以可视化的方式提供一个易于沟通的桥梁。用户需求的分析与获取用户需求有着相似的步骤,区别在于分析用户需求时使用模型来描述,以获取用户更明确的需求。分析用户需求需要执行下列活动:

  ● 以图形表示的方式描述系统的整体结构,包括系统的边界与接口;

  ● 通过原型、页面流或其它方式向用户提供可视化的界面,用户可以对需求做出自己的评价;

  ● 系统可行性分析,需求实现的技术可行性、环境分析、费用分析、时间分析等;

  ● 以模型描述系统的功能项、数据实体、外部实体、实体之间的关系、实体之间的状态转换等方面的内容。

  

  图2 DFD示意图

  用于需求建模的方法有很多种,最常用的包括数据流图(DFD)、实体关系图(ERD)和用例图(Use Case)三种方式。DFD作为结构化系统分析与设计的主要方法,已经得到了广泛的应用,DFD尤其适用于MIS系统的表述。DFD使用四种基本元素来描述系统的行为,过程、实体、数据流和数据存储。DFD方法直观易懂,使用者可以方便地得到系统的逻辑模型和物理模型,但是从DFD图中无法判断活动的时序关系。图2描述的是某个项目的DFD示意图。

  ERD方法用于描述系统实体间的对应关系,需求分析阶段使用ERD描述系统中实体的逻辑关系,在设计阶段则使用ERD描述物理表之间的关系。需求分析阶段使用ERD来描述现实世界中的对象。ERD只关注系统中数据间的关系,而缺乏对系统功能的描述。如果将ERD与DFD两种方法相结合,则可以更准确地描述系统的需求。

  在面向对象分析的方法中通常使用Use Case来获取软件的需求。Use Case通过描述“系统”和“活动者”之间的交互来描述系统的行为。通过分解系统目标,Use Case描述活动者为了实现这些目标而执行的所有步骤。Use Case方法最主要的优点,在于它是用户导向的,用户可以根据自己所对应的Use Case来不断细化自己的需求。此外,使用Use Case还可以方便地得到系统功能的测试用例。

编写需求文档

  需求文档可以使用自然语言或形式化语言来描述,还可以添加图形的表述方式和模型表征的方式。需求文档应该包括用户的所有需求(功能性需求和非功能性需求)。

评审需求文档

  需求文档完成后,需要经过正式评审,以便作为下一阶段工作的基础。一般的评审分为用户评审和同行评审两类。用户和开发方对于软件项目内容的描述,是以需求规格说明书作为基础的;用户验收的标准则是依据需求规格说明书中的内容来制订,所以评审需求文档时用户的意见是第一位的。而同行评审的目的,是在软件项目初期发现那些潜在的缺陷或错误,避免这些错误和缺陷遗漏到项目的后续阶段。

管理需求

  

  图1 需求变更流程

  需求的变更是不可避免的,如何以可控的方式管理软件的需求,对于项目的顺利进行有着重要的意义。如果匆匆忙忙地完成用户调研与分析,则往往意味着不稳定的需求。所以需求管理要保证需求分析各个活动都得到了充分的执行。对于需求变更的管理,则主要使用需求变更流程和需求跟踪矩阵的管理方式。需求变更流程和需求跟踪矩阵分别如图1和图2所示。

  

  图2 需求跟踪矩阵

  常见问题及建议

  Q、客户与最终用户的区别是什么?

  A、可以借助图3来说明它们之间的区别。

  

  图3 需求获取渠道示意图

  软件需求来自系统工程与客户两个方面,其中客户是主要的需求提供者(系统工程需求也来自于客户)。客户需要搜集其最终用户的需求并考虑自身的需求,然后再提供给开发方。假如客户并未去认真搜集最终用户的需求,开发方便需要做到这一点,因为系统最终要满足最终用户的需求。

  Q、如何进行用户访谈?

  A、首先,一定要事先确定访谈的目的和提纲。其次,因为用户往往并不知道应该提供哪些方面的需求,所以需要开发人员引导。

  Q、用户访谈内容是什么?

  A、首先,请用户描述他们如何完成自己当前的工作,并与用户一起抽象出一个工作流程或工作模型。然后,在得到用户的认可后,向用户解释自己是怎样来实现这些功能的,并说明哪些环节可以用自动化方式实现等。

  Q、采用哪一种方式做需求分析最好?

  A、不同的需求分析有不同的特点。还没有哪一种方法可以完全替代别的方法,否则,现在就不会存在不同的需求建模方式了。一般来说,可以使用DFD+ERD来描述那些功能层次比较清晰的需求;而USE CASE则适于描述功能结构复杂的需求。做需求分析的目的是为了建立需求的模型,不同的子系统有可能使用不同的建模方法。

  Q、怎样做原型,原型的目的是什么?

  A、通常使用原型分析方法来帮助开发方进一步获取用户需求或让用户确认需求。开发方往往先向用户提供一个可视界面作为原型,并在界面上布置必要的元素以演示用户所需要的功能。可以使用第四代语言(例如Visual Basic、Delphi等)来快速生成用户界面,也可以使用FrontPage等网页制作工具来生成用户可视的页面流。

  原型的目的往往是获取需求。但有时也使用原型的方式来验证关键技术或技术难点。对于技术原型,界面则往往被忽略掉。

posted @ 2007-06-12 17:47 Alex 阅读(967) | 评论 (0)编辑 收藏

echo "begin to start oracle"
lsnrctl start
sqlplus /nolog 
<<EOF
connect /as sysdba
startup
exit
exit
echo "oracle have started"
posted @ 2007-06-12 09:13 Alex 阅读(1130) | 评论 (0)编辑 收藏

已经打印了,晚上看一下,感觉不错。

download

posted @ 2007-06-11 18:24 Alex 阅读(650) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-06-04 00:18 Alex 阅读(2060) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-06-02 11:50 Alex 阅读(947) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-05-29 11:32 Alex 阅读(1475) | 评论 (5)编辑 收藏

     摘要:   阅读全文
posted @ 2007-05-24 09:52 Alex 阅读(1189) | 评论 (2)编辑 收藏

     摘要:   阅读全文
posted @ 2007-05-14 09:05 Alex 阅读(13332) | 评论 (8)编辑 收藏

     摘要:   阅读全文
posted @ 2007-05-13 17:15 Alex 阅读(1910) | 评论 (3)编辑 收藏

     摘要:   阅读全文
posted @ 2007-05-13 16:41 Alex 阅读(5618) | 评论 (13)编辑 收藏

key words: jstl,struts,log4j

1.jstl
jstl的配置参考这篇文章:
http://foolmouse.cnblogs.com/archive/2006/04/20/380695.html

在iAS904服务器上的jstl的版本只能用1.0 的
"standard.jar和jstl.jar文件拷贝到\WEB-INF\lib\
"

2.struts
struts的配置主要是把 相关jar文件(struts.jar,struts-legacy.jar)拷贝到\WEB-INF\lib
,另外,struts需要用到一些apache的commons的包(commons-beanutils.jar,commons-collections-2.1.1.jar,commons-digester.jar)

3.log4j
log4j经常有莫名其妙的问题,有时候能出来log有时候又不能出来log,最后把log4j.xml统一改为log4j.properties,暂时看好像有效果。

4。web.xml配置

<taglib>
        
<taglib-uri>http://java.sun.com/jsp/jstl/core</taglib-uri>
        
<taglib-location>/WEB-INF/c-1_0.tld</taglib-location>
    
</taglib>
    
<taglib>
        
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
        
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
    
</taglib>
    
<taglib>
        
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
        
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
    
</taglib>
    
<taglib>
        
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
        
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
    
</taglib>


今天终于先在iAS里部署完了struts,下一步把hibernate放进去,上次部署过一次,没有成功,据说是和toplink有点冲突。 知道的兄弟分享下oc4j中部署hibernate
posted @ 2007-05-08 19:11 Alex 阅读(1331) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-04-11 16:09 Alex 阅读(7768) | 评论 (8)编辑 收藏

     摘要:   阅读全文
posted @ 2007-03-30 08:34 Alex 阅读(798) | 评论 (0)编辑 收藏

key words: contentType,meta

come from here


 

在经典同时看到两个关于ContentType的问题

http://bbs.blueidea.com/thread-2729935-1-1.html

http://bbs.blueidea.com/thread-2729945-1-1.html

所以查了下资料,copy了一份详细的ContentType的列表

 

不同的ContentType 会影响客户端所看到的效果.

默认的ContentType为 text/html  也就是网页格式.

代码如:

<% response.ContentType ="text/html" %> 
<!--#i nclude virtual="/ContentType.html" -->

显示的为网页,而

<% response.ContentType ="text/plain" %> 
<!--#i nclude virtual="/sscript/ContentType.html" -->

则会显示html原代码.

以下为一些常用的 ContentType

GIF images
<% response.ContentType ="image/gif" %> 
<!--#i nclude virtual="/myimage.gif" -->
JPEG images
<% response.ContentType ="image/jpeg" %> 
<!--#i nclude virtual="/myimage.jpeg" -->
TIFF images
<% response.ContentType ="image/tiff" %> 
<!--#i nclude virtual="/myimage.tiff" -->
MICROSOFT WORD document
<% response.ContentType ="application/msword" %> 
<!--#i nclude virtual="/myfile.doc" -->
RTF document
<% response.ContentType ="application/rtf" %> 
<!--#i nclude virtual="/myfile.rtf" -->
MICROSOFT EXCEL document
<% response.ContentType ="application/x-excel" %> 
<!--#i nclude virtual="/myfile.xls" -->
MICROSOFT POWERPOINT document
<% response.ContentType ="application/ms-powerpoint" %> 
<!--#i nclude virtual="/myfile.pff" -->
PDF document
<% response.ContentType ="application/pdf" %> 
<!--#i nclude virtual="/myfile.pdf" -->
ZIP document
<% response.ContentType ="application/zip" %> 
<!--#i nclude virtual="/myfile.zip" -->

 

下面是更详细的ContentType

application/andrew-insetez
application/mac-binhex40hqx
application/mac-compactprocpt
application/mathml+xmlmathml
application/msworddoc
application/octet-streambin dms lha lzh exe class so dll
application/odaoda
application/oggogg
application/pdfpdf
application/postscriptai eps ps
application/rdf+xmlrdf
application/smilsmi smil
application/srgsgram
application/srgs+xmlgrxml
application/vnd.mifmif
application/vnd.mozilla.xul+xmlxul
application/vnd.ms-excelxls
application/vnd.ms-powerpointppt
application/vnd.wap.wbxmlwbxml
application/vnd.wap.wmlc.wmlc wmlc
application/vnd.wap.wmlscriptc.wmlsc wmlsc
application/voicexml+xmlvxml
application/x-bcpiobcpio
application/x-cdlinkvcd
application/x-chess-pgnpgn
application/x-cpiocpio
application/x-cshcsh
application/x-directordcr dir dxr
application/x-dvidvi
application/x-futuresplashspl
application/x-gtargtar
application/x-hdfhdf
application/x-httpd-php.php .php4 .php3 .phtml
application/x-httpd-php-source.phps
application/x-javascriptjs
application/x-koanskp skd skt skm
application/x-latexlatex
application/x-netcdfnc cdf
application/x-pkcs7-crl.crl
application/x-shsh
application/x-sharshar
application/x-shockwave-flashswf
application/x-stuffitsit
application/x-sv4cpiosv4cpio
application/x-sv4crcsv4crc
application/x-tar.tgz tar
application/x-tcltcl
application/x-textex
application/x-texinfotexinfo texi
application/x-trofft tr roff
application/x-troff-manman
application/x-troff-meme
application/x-troff-msms
application/x-ustarustar
application/x-wais-sourcesrc
application/x-x509-ca-cert.crt
application/xhtml+xmlxhtml xht
application/xmlxml xsl
application/xml-dtddtd
application/xslt+xmlxslt
application/zipzip
audio/basicau snd
audio/midimid midi kar
audio/mpegmpga mp2 mp3
audio/x-aiffaif aiff aifc
audio/x-mpegurlm3u
audio/x-pn-realaudioram rm
audio/x-pn-realaudio-pluginrpm
audio/x-realaudiora
audio/x-wavwav
chemical/x-pdbpdb
chemical/x-xyzxyz
image/bmpbmp
image/cgmcgm
image/gifgif
image/iefief
image/jpegjpeg jpg jpe
image/pngpng
image/svg+xmlsvg
image/tifftiff tif
image/vnd.djvudjvu djv
image/vnd.wap.wbmp.wbmp wbmp
image/x-cmu-rasterras
image/x-iconico
image/x-portable-anymappnm
image/x-portable-bitmappbm
image/x-portable-graymappgm
image/x-portable-pixmapppm
image/x-rgbrgb
image/x-xbitmapxbm
image/x-xpixmapxpm
image/x-xwindowdumpxwd
model/igesigs iges
model/meshmsh mesh silo
model/vrmlwrl vrml
text/calendarics ifb
text/csscss
text/html.shtml html htm
text/plainasc txt
text/richtextrtx
text/rtfrtf
text/sgmlsgml sgm
text/tab-separated-valuestsv
text/vnd.wap.wml.wml wml
text/vnd.wap.wmlscript.wmls wmls
text/x-setextetx
video/mpegmpeg mpg mpe
video/quicktimeqt mov
video/vnd.mpegurlmxu
video/x-msvideoavi
video/x-sgi-moviemovie
x-conference/x-cooltalkice
posted @ 2007-03-29 09:25 Alex 阅读(1937) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-03-28 16:55 Alex 阅读(4549) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-03-26 22:18 Alex 阅读(36075) | 评论 (7)编辑 收藏

     摘要:   阅读全文
posted @ 2007-03-20 16:23 Alex 阅读(3362) | 评论 (1)编辑 收藏

key words : jsp防盗链 header

最近碰到盗链的问题,即复制一个url地址,在另一个地方也能访问。

index.jsp页面
<html>
  
<head><title>Simple jsp page</title></head>
  
<body>Place your content here

  here is index jsp
    get header info
  
<href="a.jsp">a.jsp</a>
  
</body>
</html>

a.jsp页面
<html>
  
<head><title>Simple jsp page</title></head>
  
<body>Place your content here

  here is a. jsp
    get header info
  
<%=request.getHeader("Referer")%>
  
<%if(null == request.getHeader("Referer") || request.getHeader("Referer").indexOf("yourdomain.com"< 0){%>
     做人要厚道
  
<%}else{%>
  合法访问
  
<%}%>
  
</body>
</html>


即从内部访问可以,直接粘贴地址在另一个浏览器里访问禁止
posted @ 2007-03-13 10:01 Alex 阅读(5912) | 评论 (8)编辑 收藏

     摘要:   阅读全文
posted @ 2007-03-12 00:40 Alex 阅读(12373) | 评论 (18)编辑 收藏

     摘要:   阅读全文
posted @ 2007-02-25 00:20 Alex 阅读(397) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2007-02-24 23:37 Alex 阅读(1881) | 评论 (2)编辑 收藏

key words: pd, powerdesigner,外建

在PD中建立外键碰到一个问题,如: forum(id,name)表,forum_thread(id,forum_id)表,需要将forum的id设为forum_thread的外建,指向forum_id,默认的在 PD中是将主键设为外建.

需要如下设置才可以:

foreign_key.png
posted @ 2007-01-31 10:42 Alex 阅读(1296) | 评论 (0)编辑 收藏

key words: 上传文件

目里面有上传文件的需求,我想了一下不外乎下面两种处理方法:

1. 在数据库表中建立一个blob字段存放用户上传文件.
2. 在服务器上建立一个文件夹保存用户上传文件,数据库表中只存放该文件的url地址.

我本人现在比较倾向于第2种方案, 主要原因是担心方案1的效率(我用的是mysql数据库)。 但是处理过程中除了维护数据库中表的字段还要维护上传的文件,稍微麻烦一点。

大家在项目里面又是怎么做呢? 给我点建议!谢谢


讨论内容见: javaeye

robin更建议第二种方案,放在数据库中主要的问题是 AppServer吃不消,开销比较大.

上面是摘录,不过我们这里用的是Oracle9i AS,在Oracle的协作套间里一般文档或者上传的文件都是保存在数据库里,还把这个特性作为Oracle 与别的协作套件之间不同的卖点。

大家以为如何? 我直觉是Oracle的DB和oc4j的AppServer对付这个似乎没有开销上的担心,但是没有实际检测过。

换句话说,文件的管理是放在文件夹里方便还是数据库里方便? 有点为难

update (2007-5-13):
还有一种方案结合了数据库和IO,我认为比较可行,就是文件存在数据库,但是下载的时候第一次从数据库下载,然后第一次这个文件保存在一个临时文件夹下面,以后每次下载的时候总是先检查此临时文件夹,如果已经存在则直接下载,如果没有则从数据库重复这个动作。当然,保存在临时文件夹下的文件的命名需要唯一,这个应该没有问题。
posted @ 2007-01-31 09:06 Alex 阅读(1942) | 评论 (4)编辑 收藏


这个年代的偶像很多,值得学习的也很多,,如果让我说,我觉得在现在这个急功近利的所谓2.0时代,阿甘精神更值得我们学习。

什么是阿甘精神?
1。奔跑。 不停的奔跑,不要停下来
2。不断地重复,如果你做的不够好,那就说明你还重复的不够多。
3。执着。 不要相信权威,按自己的意志去做。

现在,聪明的人很多,但是小聪明似乎更多,包括我。

你认可阿甘精神么? 如果是,顶一下。

刚看到关于阿甘精神的另一个有意思的摘录:



"
阿甘就是看到一个目标就走过去了,别的人是,看见一个目标,先订一个作战计划,然后匍匐前进,往左闪,往右躲,再弄个掩体…一辈子就看他闪转腾挪活得那叫一个花哨,最后哪儿也没到达。
"

很形象哦。

祝各位在2007年工作进步。

posted @ 2007-01-30 08:59 Alex 阅读(1208) | 评论 (2)编辑 收藏

key words: js,javascript,检查上传文件大小

有时需要在客户端获得待上传得文件大小,google了一下,发现下面这个用法.

<html>  
<input type="file" name="file1" onchange="ShowSize(this.value)">  
<script language="JavaScript">  
<!--  
function ShowSize(files)  
{  
  
var fso,f;  
  fso
=new ActiveXObject("Scripting.FileSystemObject");  
  f
=fso.GetFile(files);
  
var mySize = f.size/1024;
  alert(mySize
+" K ");  
}
  
//-->  
</script>  
</html>


BTW: 奇怪,COS上传组件里怎么没有获得上传文件大小的方法?  MultipartRequest里是没有,有知道的兄弟通知下哦
posted @ 2007-01-25 17:16 Alex 阅读(10394) | 评论 (4)编辑 收藏

     摘要: key words : hibernate spring daocome from : http://lpacec.javaeye.com/blog/46220   1.  package infoweb.dao;         2.         3. import java.util.List;         4. import java.util.Iterator;         5...  阅读全文
posted @ 2007-01-25 09:24 Alex 阅读(896) | 评论 (0)编辑 收藏

key words: REST webservice

转自廖宇雷的Weblog

REST 是由 Roy Fielding 在他的论文《Architectural Styles and the Design of Network-based Software Architectures》中提出的一个术语。

REST 是英文 Representational State Transfer 的缩写,有中文翻译为“具象状态传输”(参考:《SIP/IMS网络中的Representational State Transfer (REST)和数据分布》)。

—————————————

前面的内容比较枯燥,我说说我自己的理解。

但是 REST 到底是什么呢?论文我看不懂,不过找到一篇更简单易懂的东西:《Building Web Services the REST Way》

根据这篇文章,我整理了一下我自己对 REST 的理解:

REST 首先只是一种架构样式,不是一种标准。这点和 Ajax 类似,两者都是利用现有的成熟技术。

在 REST 的定义中,一个 Web 应用总是使用固定的 URI 向外部世界呈现(或者说暴露)一个资源

URI 是英文 Uniform Resource Identifier 的缩写,中文翻译“通用资源标志符”。

“通用资源标志符”是指唯一标识一个资源(xhtml 文件、图片、css 样式表)的字符串。当然了,RFC 中定义的 URI 复杂得多,不过我们此处将 URI 想象成一个人的身份证号码就行了(你不能有两个同时有效的身份证号码,一个号码也不可能同时对应两个人)。而我们天天挂在嘴边的 URL 地址就是 URI 的一种表现形式(个人理解,有错请纠正)。

知道什么是 URI 后,我们来看一个实际例子:

http://www.example.com/photo/logo 指向 example.com 网站(可以视为一个 Web 应用)中类型为 photo,名字为 logo 的资源。我们用浏览器访问这个 URI,看到的将可能是一个 xhtml 文档,其中用 <img src=”……” /> 来显示实际的照片。

http://www.example.com/photo/logo 很容易让你想到 URL 重写。事实上,这个地址很可能会在服务器内部处理为 http://www.example.com/photo.php?name=logo 这样的地址。photo.php 是服务器端的一个动态脚本文件,根据 name 参数生成 xhtml 文档返回给浏览器。

现在假设我们要获取这张照片的 XML 文档。XML 文档中包含照片的文件名、文件大小、拍摄日期等等信息。也就是说我们要获取“同一个资源的不同表现形式的数据”。对于这个要求,我们可以很容易的用另一个 URL 地址达到:http://www.example.com/xml/logo。

但是,这就违背了“URI 唯一标识一个资源”的定义。如果我们要获取同一个资源的多种表现形式,那么就要使用更多的 URL,从而给一个资源指定了多个不同的 URI。

而在 REST 中,不管是获取照片的 xhtml 文档还是 XML 文档,或者照片文件本身,都是用同一个 URI,就是 http://www.example.com/photo/logo。

那这是怎么办到的呢?Ruby On Rails 中是通过分辨 HTTP Request Header 信息来分辨客户端是想要取得资源的哪一种表现形式的数据。

当我们用浏览器访问一个网址时,浏览器会构造一个 HTTP 请求。这个请求有一个头信息,其中包括了本次请求接受何种类型的数据。通常浏览器发送的 HTTP 请求头中,Accept 的值都是 */*,也就说接受服务器返回的任何类型的数据。

看到这里,聪明的家伙应该知道了。只要我们指定一个特定的 Accept 参数,那么服务器就可以通过判断该参数来决定返回什么类型的数据。所以在一个采用 REST 架构的应用中,要获取同一个资源的不同表现形式的数据,只需要使用不同的 HTTP 请求头信息就行了。

如果考虑为 Web 应用增加 Web Services,这种技术的价值就体现出来了。比如我写了一个 Delphi 程序,现在只需要构造一个包含 Accept: text/xml 的 HTTP 请求头,然后将请求发送到 http://www.example.com/photo/logo 就可以了。返回的结果就是一个 XML 文档,而不是 xhtml 文档。

因为我们的 HTTP 请求头信息有不同的状态,从而可以获得不同的数据,所以叫做“具象状态传输” :)

—————————————

除了上面的用法,REST 还有进一步的扩展。

我们在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有 HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的 CRUD(创建、读取、更新和删除)操作:

  • POST: 创建
  • GET: 读取
  • PUT: 更新
  • DELETE: 删除

经过这样的一番扩展,我们对一个资源的 CRUD 操作就可以通过同一个 URI 完成了:

http://www.example.com/photo/logo(读取)
仍然保持为 [GET] http://www.example.com/photo/logo

http://www.example.com/photo/logo/create(创建)
改为 [POST] http://www.example.com/photo/logo

http://www.example.com/photo/logo/update(更新)
改为 [PUT] http://www.example.com/photo/logo

http://www.example.com/photo/logo/delete(删除)
改为 [DELETE] http://www.example.com/photo/logo

从而进一步规范了资源标识的使用。

通过 REST 架构,Web 应用程序可以用一致的接口(URI)暴露资源给外部世界,并提供对资源的操作服务。这对于以资源为中心的 Web 应用来说非常重要。例如照片共享网站、用户社区等。

—————————————

Ruby On Rails 1.2 版对 REST 有很好的支持,但要在 PHP 中应用 REST 还需要解决不少问题:

  • 如何在服务端判断 PUT、DELETE 请求方法;
  • 如何获取用 PUT、DELETE 请求方法中传递的数据;
  • 如何获取 HTTP 请求头信息中的 Accept 参数值;
  • 如何在浏览器端发起 PUT 和 DELETE 请求。

不过我仔细看了 PHP 文档,我觉得上面几个问题都是可以解决的。

服务端综合使用 $_SERVER[’HTTP_ACCEPT’]、$_SERVER[’REQUEST_URI’]、$_SERVER[’REQUEST_METHOD’]、$_SERVER[’QUERY_STRING’] 这些变量应该可以搞定前面三个问题。而第四个问题则可以用 JavaScript 的 XMLHttpRequest 对象来实现。

不过我想 REST 的真正价值在于 Web Services,而不是通过浏览器操作的应用程序。

—————————————

参考:

posted @ 2007-01-23 08:30 Alex 阅读(431) | 评论 (0)编辑 收藏

key words : web快速开发

web快速开发是一个值得期待的东西,最早我接触过codecharge,是一个商业软件,功能据说很强大,我试用过,支持php,asp,jsp等多语言,但是感觉对java的支持不是很顺手,接着就是asp,这个好也不好,原因就不说了,因为我对java关注。

2. 接着就是dorado ,刚看到的,感觉也不错,有asp的风格,不过更适合j2ee的环境 ,这里是他们的在线演示demo,有兴趣的可以看一下

3. 最后,刚google了一下,Oracle也有一个基于Oracle数据库的快速开发产品  : APEX,基于浏览器做DB 开发,原来的名字叫做HTML DB,名字很直观,据说Oracle的metalink就是用这个实现的,应该功能不赖。

4.对了,再补充一个现在很热的,Ror,听说javaeye的网站要重新用ruby1.2开发,试用过一点,感觉确实令人震惊,值得关注。

java的技术其实是看着很热闹,比较大的东西也不少,但是真正关注web这一块的其实不多,无论是struts还是webwork,离我们所说的真正的“快速开发”的距离很远,而以上一些工具的出现也许会逐步改善j2ee在这一领域的现状。

我之所以对这个目前有点兴趣,确实有一种强烈的反差在折磨着我,因为我相信在j2ee的web开发里确实存在本来要开发1周多的工作量很可能用一个好的工具几个小时就搞定了,这是一件多么荒唐搞笑的事情?如果dorado在它的产品中真的能做到这样那我们的java开发那么坑吃坑吃的折腾个什么劲啊? 也许真的有奇迹,我宁愿相信有这样的奇迹。

大家有什么好的想法可以讨论一下。
posted @ 2007-01-20 22:56 Alex 阅读(3761) | 评论 (7)编辑 收藏

key words: powerdesign,power , design

引言:
发现powerdesign的文档真的是不多,不过powerdesign还是很有用的,见到此类文档就收藏下吧.

转自edeed

 附上一些使用pd11的心得:

1、安装PD v11.0版

2、由pdm生成建表脚本时,字段超过15字符就发生错误(oracle)
原因未知,解决办法是打开PDM后,会出现Database的菜单栏,进入Database - Edit Current DBMS -script-objects-column-maxlen,把value值调大(原为30),比如改成60。出现表或者其它对象的长度也有这种错误的话都可以选择对应的objects照此种方法更改!
或者使用下面的这种方法:
生成建表脚本时会弹出Database generation提示框:把options - check model的小勾给去掉,就是不进行检查(不推荐)!
或者可以修改C:\Program Files\Sybase\PowerDesigner Trial 11\Resource Files\DBMS\oracl9i2.xdb文件
修改好后,再cdm转为pdm时,选择“Copy the DBMS definition in model”把把这个资源文件拷贝到模型中。

3、生成的建表脚本中如何把对象的双引号去掉?
打开cdm的情况下,进入Tools-Model Options-Naming Convention,把Name和Code的标签的Charcter case选项设置成Uppercase或者Lowercase,只要不是Mixed Case就行!
或者选择Database->Edit current database->Script->Sql->Format,有一项CaseSensitivityUsingQuote,它的 comment为“Determines if the case sensitivity for identifiers is managed using double quotes”,表示是否适用双引号来规定标识符的大小写,可以看到右边的values默认值为“YES”,改为“No”即可!
或者在打开pdm的情况下,进入Tools-Model Options-Naming Convention,把Name和Code的标签的Charcter case选项设置成Uppercase就可以!

4、建立一个表后,为何检测出现Existence of index的警告
A table should contain at least one column, one index, one key, and one reference.
可以不检查 Existence of index 这项,也就没有这个警告错误了!
意思是说没有给表建立索引,而一个表一般至少要有一个索引,这是一个警告,不用管也没有关系!

5、创建一个表在修改字段的时候,一修改name的内容,code也跟着变化,如何让code不随着name变化
Name和Code 的右侧都有一个按钮“=”,如果需要不同步的话,把这个按钮弹起来就可以了。
Tools->General Options->Dialog->Name to Code Mirroring (去掉)

6、由CDM生成PDM时,自动生成的外键的重命名
PDM Generation Options->Detail->FK index names默认是%REFR%_FK,改为FK_%REFRCODE%,其中%REFRCODE%指的就是CDM中Relationship的code!另外自动生成的父字段的规则是PDM Generation Options->Detail->FK column name template中设置的,默认是%.3:PARENT%_%COLUMN%,可以改为Par%COLUMN%表示是父字段!

7、如何防止一对一的关系生成两个引用(外键)
要定义关系的支配方向,占支配地位的实体(有D标志)变为父表。
在cdm中双击一对一关系->Detail->Dominant role选择支配关系

8、修改报表模板中一些术语的定义
即文件:C:\Program Files\Sybase\PowerDesigner Trial 11\Resource Files\Report Languages\Chinese.xrl
Tools-Resources-Report Languages-选择Chinese-单击Properties或双击目标
修改某些对象的名称:Object Attributes\Physical Data Model\Column\
        ForeignKey:外键
        Mandatory:为空
        Primary:主键
        Table:表
用查找替换,把“表格”替换成“表”
修改显示的内容为别的:Values Mapping\Lists\Standard,添加TRUE的转化列为是,FALSE的转化列为空
另外Report-Title Page里可以设置标题信息
posted @ 2007-01-17 09:55 Alex 阅读(3145) | 评论 (3)编辑 收藏

key words : beanShell ,动态脚本

原来第一次是在osworkflow中听说过beanShell,最近又碰到,感觉还是挺有用的,比如对于我来说我就想有一些class文件拿过来直接调用看看,但有不想搭建一个麻烦的环境,而beanshell就可以满足我这个需求。

假设我欲调用的java为MyShit
package com.app;

public class MyShit
{
    
static{
        System.out.println(
"this is in static blog");
    }

    
public static void main(String[] args){
        System.out.println(
"this is in main method : hello shit");
    }

    
public void shit(){
        System.out.println(
"hello shit!!!");
    }


    
public static void haha(){
        System.out.println(
"this is static method haha");
    }

}


javac ...
java ..

在dos中设置classpath,指向该class文件

set classpath=%classpath%;c:\beanshell

编写一个 bsh的bat文件,便于dos直接调用beanshell脚本
 
java bsh.Interpreter %1


编写一个test.bsh脚本
//mytest
import com.app.*;

print(
"hello,it's a beanShell test");

List list  
= new ArrayList();
list.add(
"111");
list.add(
"222");
list.add(
"333");

print(
"the list = " + list);

Date date 
= new Date();
print(
"the date = " + date);

//method test

add( a, b ) 
{
    
return a + b;
}


foo 
= add(12);            // 3
print("foo = " + foo);
foo 
= add("Oh"" baby");   // "Oh baby"
print("foo = " + foo);

//about object
foo() {
    print(
"foo");
    x
=5;

    bar() 
{
        print(
"foo's method bar()");
    }


    
return this;
}


myfoo 
= foo();    // prints "foo"
print( myfoo.x ); // prints "5"
myfoo.bar();      // prints "bar"

MyShit shit 
= new MyShit();
shit.shit();
shit.main(
null);
print(
"do you shit");

//MyShit.haha();


在dos下运行 bsh test.bsh就OK了

另外,beanshell作为动态脚本语言可以结合 spring2.0的新特性,你新写的业务类可以直接修改而不用重新发布,怎么样,是不是比较方便,详细操作请参考Springframework 2.0 与 ZK 混合开发实例

试一试!

详细使用请参考官方文档
posted @ 2007-01-09 20:01 Alex 阅读(6500) | 评论 (7)编辑 收藏

key words: 连接池 数据库

发现一个有意思的现象,好多人在开发过程中不知道是出于训练自己编码还是为了重新发明轮子,总之明明已经存在的功能他非要自己实现一遍。

今天又碰到一个。

jdbc的连接,好多人也喜欢自己实现一个连接池,但是对于app server来说本身一般都有支持连接池的,为什么不用呢?
写了一通又长又臭的代码,自己以为牛B的很,其实算个球啊。

不要浪费,不要自己发明轮子,你不是最牛的,一定还有比你更牛的!

对于weblogic或者oracle以及ibm的产品来说,大家还是比较习惯于用容器的jndi,但是即使对于tomcat,我个人也建议用tomcat自带的。

附: jndi连接数据库

public final static synchronized Connection getConnection(String inputJNDI) throws
      DBMException 
{
    Connection conn 
= null;
    String strConnJNDI 
= null;
    
try {
      Context ctx 
= new InitialContext();
      _log.debug(ctx);

      
if (null != inputJNDI) {
        
if(inputJNDI.length() > 0)
          strConnJNDI 
= inputJNDI;
        
else
          strConnJNDI 
= dbJndiName;
      }

      
else {
        strConnJNDI 
= dbJndiName;
      }

      DataSource ds 
= (DataSource) ctx.lookup(strConnJNDI);
      _log.debug(ds);
      conn 
= ds.getConnection();
    }

    
catch (Exception ex) {
      _log.error(
"It's error to get connection", ex);
      
throw new DBMException("Error to get connection");
    }

    _log.debug(conn);
    
return conn;
  }


以此类推,类似于xml解析等的工作也没有必要自己一步一步地用dom或者什么乱七八糟的sax自己去搞一遍,搞了半天就使为了得到其中的一个value,何苦来着?    如果你不是为了做研究,那么,效率第一,安全第一.

随便说说,也许有人不同意我的观点,没关系,尽管谈谈,哈哈  :)
posted @ 2007-01-04 20:58 Alex 阅读(1927) | 评论 (8)编辑 收藏

key words: 2007 plans

新的一年开始了,制定一个计划吧

说说你有什么计划

如图:

2007-plans.png
posted @ 2007-01-03 23:31 Alex 阅读(1482) | 评论 (12)编辑 收藏

key words: tomcat,数据源配置,datasource

把一个应用从4.1移到5.5数据源死活不出来,用probe察看了一下居然datasource配置错误.

4.x的配置如下:

<Context path="/cpms" docBase="cpms"   debug="99" privileged="true">
            
<Resource name="jdbc/report" auth="Container" type="javax.sql.DataSource"/>
            
<ResourceParams name="jdbc/report"> 
                
<parameter>
                    
<name>username</name>
                    
<value>root</value>
                
</parameter>
                
<parameter>
                    
<name>password</name>
                    
<value>XXX</value>
                
</parameter>
                
<parameter>
                    
<name>driverClassName</name> 
                    
<value>org.gjt.mm.mysql.Driver</value>
                
</parameter>
                
<parameter>
                    
<name>url</name> 
                    
<value>jdbc:mysql://localhost/app</value>
                
</parameter> 
                
<parameter>
          
<name>RemoveAbandoned</name>
          
<value>true</value>
        
</parameter>
        
<parameter>
          
<name>LogAbandoned</name>
          
<value>true</value>
        
</parameter>
        
<parameter>
          
<name>RemoveAbandonedTimeout</name>
          
<value>60</value>
        
</parameter>
              
</ResourceParams> 
    
</Context>  


5.x的配置如下:

<Context path="/app" docBase="cpms"
        debug
="5" reloadable="true" crossContext="true">



  
<Resource name="jdbc/report" auth="Container" type="javax.sql.DataSource"
               maxActive
="100" maxIdle="30" maxWait="10000"
               username
="develop" password="XX" driverClassName="com.mysql.jdbc.Driver"
               url
="jdbc:mysql://localhost:3306/app?autoReconnect=true"/>
</Context>


没有具体看原因.知道的兄弟可以发表高见

tomcat的管理和监控推荐用probe,自带的那个admin管理太滥了。

posted @ 2006-12-22 12:04 Alex 阅读(1702) | 评论 (1)编辑 收藏

key words: sql server,单用户,master恢复

 作者:jankie
日期:2006-12-1

一、MASTER数据库备份与恢复
   1、数据库master正常情况下对master进行(完全)备份;
若要恢复:
   2、停止SQL Server服务;
   3、以单用户实例的方式启动SQL Server,启动时不要把窗口关闭;(具体看二)
   4、然后再对MASTE数据库进行恢复即可;

二、MSSQL单用户实例的启动方法:
  如何在单用户模式下启动SQL Server的命名实例(命令提示符)   
  在单用户模式下从命令提示符启动SQL Server 的命名实例     
  从命令提示符输入:     
  sqlservr.exe -c  -m   -s   {instancename}   

例1:
  1、sqlserver.exe -c -m 回车(默认实例)
  2、sqlserver.exe -c -m -s benet (实例名为benet)
   
  说明在启动 sqlservr.exe之前,必须在命令窗口中切换到适当的目录
如:c:\program files\microsoft sql server\mssql\bin目录下
posted @ 2006-12-21 15:08 Alex 阅读(479) | 评论 (0)编辑 收藏

key words: dll,ocx,注册控件

最近项目中用到了收银,需要在顾显上显示金额.


1.将.dll、.ocx等考到system32下。
2.用命令注册regsvr32.exe  .dll。(eg:regsvr32.exe  OWC11.dll)
3.在注册表中查看:利用名称或与其唯一对应的clsid("clsid:0002E55D-0000-0000-C000-   000000000046")可查找到相关信息
posted @ 2006-12-20 15:13 Alex 阅读(675) | 评论 (0)编辑 收藏

     摘要: key words: bea world2006 ,workshop studio, 快速开发

bea world 2006 北京已经过去好几天了,早就想记录一下一直忙 :)
  阅读全文
posted @ 2006-12-20 11:39 Alex 阅读(2634) | 评论 (10)编辑 收藏

key words: commons log,log4j,apache log

前言: 对于log4j虽然在用,但是也存在一个疑问,怎么有的用apache的commons logging有的直接用log4j,下面的这篇文章解释了我的疑问.

转自 here

Apache组织开发了一套用于支持Logging的Log4J,Java 1.4版本也引入了一套内置的 Logging框架,如果开发者想在这两套Logging系统之间自由的切换,该怎么办呢?答案就是,使用Commons Logging。 Commons Logging定义了一套抽象的Logging接口,用户可以通过配置,使这些接口指向任何一个已存在的Logging系统。

•使用抽象Logging接口
问题:
你在编写一个可以重复使用的库,需要写入Log信息,但你不想使你的Logging功能绑定在Apache Log4J或者JDK 1.4 Logging框架上。
解决方案:

public static void main(String[] args) {//自己替换[]

  System.setProperty("org.apache.commons.logging.Log",
      "org.apache.commons.logging.impl.Jdk14Logger");
  Log log = LogFactory.getLog("com.discursive.jccook.SomeApp");

  if (log.isTraceEnabled()) {
    log.trace("This is a trace message");
  }

  if (log.isDebugEnabled()) {
    log.debug("This is a debug message");
  }

  log.info("This is an informational message");
  log.warn("This is a warning");
  log.error("This is an error");
  log.fatal("This is fatal");

}


LogFactory.getLog方法会根据底层环境返回一个适当的Log实现。如果用户想指定一个具体的Logging系统实现,可以设置org.apache.commons.logging.Log系统属性。例如:
System.setProperty("org.apache.commons.logging.Log",
"org.apache.commons.logging.impl.Log4JLogger");
这样就会使用Log4J作为Logging系统。
org.apache.commons.logging.Log可以设定为:
•org.apache.commons.logging.impl.Log4JLogger  使用Log4J
•org.apache.commons.logging.impl.Jdk14Logger  使用JDK 1.4 Logging框架
•org.apache.commons.logging.impl.SimpleLog  使用Commons Logging内置的简单Log实现
其他:
总结一下,Commons Logging会按照下列顺序来指定具体的Log实现。
•如果定义了org.apache.commons.logging.Log系统参数,实用指定的Logging实现。
•如果在CLASSPATH里发现了Log4J,使用Log4J。
•如果使用的是JDK1.4,使用JDK1.4内置的Logging框架。
•如果都没有找到,则使用Commons Logging内置的简单Log实现。
posted @ 2006-12-18 15:38 Alex 阅读(692) | 评论 (0)编辑 收藏

key words : 职业规划  高薪

原文见robbin的pdf,我整理了一下脑图

职业规划与高薪之路.png
posted @ 2006-12-17 13:25 Alex 阅读(3432) | 评论 (6)编辑 收藏

注意:这篇文章是由无人工介入的自动的机器翻译系统翻译完成。这些文章是微软为不懂英语的用户提供的, 以使他们能够理解这些文章的内容。微软不保证机器翻译的正确度,也不对由于内容的误译或者客户对它的使用所引起的任何直接的, 或间接的可能的问题负责。

概要

Microsoft SQL Server 2000 支持同一计算机上同时运行的多个 SQLServer 数据库引擎实例。 以下是两种类型的 SQLServer 数据库引擎实例: 默认和命名。 只能有, 任何计算机上运行一个默认实例并且由默认实例运行计算机上的名称标识它。 通常指定以下列格式: 计算机名和实例名称
computer_name\instance_name
要 MicrosoftSQLServer2000JDBC, 连接命名实例通过 SQL 服务器 2000 MicrosoftTextDriver 必须指定端口号与命名实例, 代替的命名实例名称相关联如前面。

更多信息

要查找 SQLServer 实例端口号, 请按照下列步骤:
1.在 Microsoft SQL Server 2000 服务器, 启动 SQL Server 网络实用工具。
2.要从 实例 下拉菜单实例依次, 常规 选项卡。
3.TCP/IP, 依次 属性 。 注意, 在 属性 对话框中出现端口号为此实例。

当您连接到 SQLServer 通过 JDBC 连接 URL 中使用只要有该值,。 下面是典型连接 URL 的示例:

jdbc:microsoft:sqlserver: / 1433; yourServerName / 用户 = yourUser ; 密码 = yourPwd :
在本示例, 使用默认端口是 1433。 将此默认替换端口号为您命名实例。
posted @ 2006-12-17 00:35 Alex 阅读(1216) | 评论 (0)编辑 收藏

key words: jsp,jstl,1.0,1.1,fn函数

原来一直用struts,最近项目里有人用jstl,我也就顺便拿来用,感觉还是不错。

过程中碰到一些小的问题总结如下:

一。版本问题
jstl存在1.0和1.1的差异问题,用EL建议需要在1.1的版本下,1.1的URI的标志为:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>

注意,1.0版本没有/jsp/.

如果用的1.0会出现如下异常
org.apache.jasper.JasperException: /public/left_tree.jsp(100,24) According to TLD or attribute directive in tag file, attribute items does not accept any expressions
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:
510)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:
375)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:
314)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:
264)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:
802)
    org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:
75)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:
77)
    com.wellsoon.zfzw.webapp.common.VabAccessFilter.doFilter(VabAccessFilter.java:
43)


root cause 



二。打开EL

<%@ page contentType="text/html;charset=gb2312" language="java" isELIgnored="false" %>

前提是容器支持2.0,即使支持默认也未必打开,最安全的方式就是显示打开 isELIgnored="false"

三.fn的用法
对于Vo里的一个List作length的计算或判断很方便
<table width="160" border="0" cellspacing="0" cellpadding="4">
                        
<c:forEach items="${menuInfos}" var="m">
                            
<c:if test="${fn:length(m.subMenus)>0}">
                          
<tr>
                            
<td>
                                
<script language="JavaScript">
                                      
var tObj = new treeClass("A1")
                                      tObj.start();
                                     tObj.m_start(
"<c:out value="${m.menuModule}"/>",0);
                                    
<c:forEach items="${m.subMenus}" var="sub">
                                      tObj.add_Sub(
"<c:out value="${sub.menuName}"/>","<%=path%>
<c:out value=
"${sub.url}"/>","mymain");
                                    
</c:forEach>
                                     tObj.m_end();
                                      tObj.end();
                                      tObj.print();
                                 
</script>
                            
</td>
                        
</tr>
                            
</c:if>
                        
</c:forEach>

                    
</table>
 
四.tld的声明可以在web.xml作显式声明也可以用http作直接URL声明
推荐用本地的方式.

posted @ 2006-12-11 19:21 Alex 阅读(2779) | 评论 (2)编辑 收藏

key words: MiddleGen,hibernate,many to many,多对多

如果你有如下表结构

user(user_id,user_name)
role(role_id,role_name)

user_role(id,user_id,role_id)

那么默认MiddleGen生成的是两个一对多,但我们更多的情况是用many to many

需要修改middlegen的build.xml文件

<many2many>
            
<tablea generate="true" name="user"/>
            
<jointable name="user_role" generate="false"/>
            
<tableb generate="true" name="role"/>
         
</many2many>

posted @ 2006-12-07 20:26 Alex 阅读(892) | 评论 (0)编辑 收藏

越来越发现其实掌握 hibernate并不容易,Spring用起来其实简单多了,但是在用hibernate的时候真的是需要一定的时间积累,对一个项目组来说如果采用hibernate最好有一个对hibernate比较清楚的人否则碰到问题就会成为项目的风险。
我想告诉各位的是,掌握hibernate可能比你预期的难多了,当你轻松的告诉我,hibernate很简单的时候该是你自己多反省了. (只有一种情况例外,你是一个牛人)

好了,一个引子废话那么多,其实今天只是想先说一说hibernate里的Fetch的作用.

大家都知道,在hibernate里为了性能考虑,引进了lazy的概念,这里我们以Parent和Child为模型来说明,

public class Parent implements Serializable {

    
/** identifier field */
    
private Long id;

    
/** persistent field */
    
private List childs;

    
//skip all getter/setter method

  
}  



public class Child implements Serializable {

    
/** identifier field */
    
private Long id;

    
/** persistent field */
    
private net.foxlog.model.Parent parent;

    //skip all getter/setter method

}

在我们查询Parent对象的时候,默认只有Parent的内容,并不包含childs的信息,如果在Parent.hbm.xml里设置lazy="false"的话才同时取出关联的所有childs内容.

问题是我既想要hibernate默认的性能又想要临时的灵活性该怎么办?  这就是fetch的功能。我们可以把fetch与lazy="true"的关系类比为事务当中的编程式事务与声明式事务,不太准确,但是大概是这个意思。

总值,fetch就是在代码这一层给你一个主动抓取得机会.

Parent parent = (Parent)hibernateTemplate.execute(new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Query q 
= session.createQuery(
                        
"from Parent as parent "+
                                
" left outer join fetch parent.childs " +
                                
" where parent.id = :id"
                );
                q.setParameter(
"id",new Long(15));
                
return (Parent)q.uniqueResult();
            }

        });

        Assert.assertTrue(parent.getChilds().size() 
> 0);


你可以在lazy="true"的情况下把fetch去掉,就会报异常. 当然,如果lazy="false"就不需要fetch了


有一个问题,使用Fetch会有重复记录的现象发生,我们可以理解为Fetch实际上不是为Parent服务的,而是为Child服务的.所以直接取Parent会有不匹配的问题.



参考一下下面的这篇文章
Hibernate集合初始化

======================================================================

update:以上有些结论错误,实际上在hibernate3.2.1版本下测试,可以不出现重复记录,

public void testNPlusOne() throws Exception{
        List list 
= (List)hibernateTemplate.execute(new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Query q 
= session.createQuery(
                        
"select distinct p from net.foxlog.model.Parent p inner join fetch p.childs"
                );
                
return q.list();
            }

        });

        
//((Parent)(list.get(0))).getChilds();
        System.out.println("list size = " + list.size());
        
for(int i=0;i<list.size();i++){
            Parent p 
= (Parent)list.get(i);
            System.out.println(
"===parent = " + p);
            System.out.println(
"===parent's child's length = " + p.getChilds().size());
        }

    }


打印结果如下:
Hibernate: select distinct parent0_.id as id2_0_, childs1_.id as id0_1_, childs1_.parent_id as parent2_0_1_, childs1_.parent_id as parent2_0__, childs1_.id as id0__ from parent parent0_ inner join child childs1_ on parent0_.id=childs1_.parent_id
list size 
= 3
===parent = net.foxlog.model.Parent@1401d28[id=14]
===parent's child's length = 1
===parent = net.foxlog.model.Parent@14e0e90[id=15]
===parent's child's length = 2
===parent = net.foxlog.model.Parent@62610b[id=17]
===parent's child's length = 3

另外,如果用open session in view模式的话一般不用fetch,但首先推荐fetch,如果非用的话因为有N+1的现象,所以可以结合batch模式来改善下性能.


posted @ 2006-12-01 13:01 Alex 阅读(19336) | 评论 (7)编辑 收藏

LDAPChina序言:这篇文章是互联网上最流行的一篇介绍LDAP基础知识的文章,本文作者Michael DonnellyLDAPMAN.org的站长。由于本文译文较长,我们将其分为两部分,第一部分介绍LDAP概论,第二部分介绍LDAP基础知识。感谢网友Brimmer翻译此文。

如 果你在计算机行业工作,那么对LDAP可能早有耳闻了。想深入地了解LDAP吗?那么可以好好地读一下这篇文章。这篇介绍性的文章是一系列介绍如何在企业 中设计、实现和集成LDAP环境的文章的头一篇。主要是先让你熟悉一下LDAP的基本概念,那些比较困难的细节问题将放到以后讨论。在这篇文章中我们将要 介绍:

什么是LDAP?
什么时候该用LDAP存储数据?

现 在LDAP技术不仅发展得很快而且也是激动人心的。在企业范围内实现LDAP可以让运行在几乎所有计算机平台上的所有的应用程序从LDAP目录中获取信 息。LDAP目录中可以存储各种类型的数据:电子邮件地址、邮件路由信息、人力资源数据、公用密匙、联系人列表,等等。通过把LDAP目录作为系统集成中 的一个重要环节,可以简化员工在企业内部查询信息的步骤,甚至连主要的数据源都可以放在任何地方。如果Oracle、Sybase、Informix或 Microsoft SQL数据库中已经存储了类似的数据,那么LDAP和这些数据库到底有什么不同呢?是什么让它更具优势?请继续读下去吧!


什么是LDAP?

LDAP 的英文全称是Lightweight Directory Access Protocol,一般都简称为LDAP。它是基于X.500标准的,但是简单多了并且可以根据需要定制。与X.500不同,LDAP支持TCP/IP, 这对访问Internet是必须的。LDAP的核心规范在RFC中都有定义,所有与LDAP相关的RFC都可以在LDAPChina.com RFC专栏中找到。

怎么使用LDAP这个术语呢?

在 日常交谈中,你可能会听到有些人这么说:“我们要把那些东西存在LDAP中吗?”,或者“从LDAP数据库中取出那些数据!”,又或者“我们怎么把 LDAP和关系型数据库集成在一起?”。严格地说,LDAP根本不是数据库而是用来访问存储在信息目录(也就是LDAP目录)中的信息的协议。更为确切和正式的说法应该是象这样的:“通过使用LDAP,可以在信息目录的正确位置读取(或存储)数据”。但是,也没有必要吹毛求疵,尽管表达得不够准确,我们也都知道对方在说什么。

LDAP目录是数据库吗?

就 象Sybase、Oracle、Informix或Microsoft的数据库管理系统(DBMS)是用于处理查询和更新关系型数据库那样,LDAP服务 器也是用来处理查询和更新LDAP目录的。换句话来说LDAP目录也是一种类型的数据库,但是不是关系型数据库。不象被设计成每分钟需要处理成百上千条数 据变化的数据库,例如:在电子商务中经常用到的在线交易处理(OLTP)系统,LDAP主要是优化数据读取的性能。

LDAP目录的优势

现在该说说LDAP目录到底有些什么优势了。现在LDAP的流行是很多因素共同作用的结果。我在这里说的不过是一些基本的原因,请你注意一下这不过是一小部分原因。

可能LDAP最大的优势是:可以在任何计算机平台上,用很容易获得的而且数目不断增加的LDAP的客户端程序访问LDAP目录。而且也很容易定制应用程序为它加上LDAP的支持。

LDAP 协议是跨平台的和标准的协议,因此应用程序就不用为LDAP目录放在什么样的服务器上操心了。实际上,LDAP得到了业界的广泛认可,因为它是 Internet的标准。产商都很愿意在产品中加入对LDAP的支持,因为他们根本不用考虑另一端(客户端或服务端)是怎么样的。LDAP服务器可以是任 何一个开发源代码或商用的LDAP目录服务器(或者还可能是具有LDAP界面的关系型数据库),因为可以用同样的协议、客户端连接软件包和查询命令与 LDAP服务器进行交互。与LDAP不同的是,如果软件产商想在软件产品中集成对DBMS的支持,那么通常都要对每一个数据库服务器单独定制。

不象很多商用的关系型数据库,你不必为LDAP的每一个客户端连接或许可协议付费。

大多数的LDAP服务器安装起来很简单,也容易维护和优化。

LDAP服务器可以用“推”或“拉”的方法复制部分或全部数据,例如:可以把数据“推”到远程的办公室,以增加数据的安全性。复制技术是内置在LDAP服务器中的而且很容易配置。如果要在DBMS中使用相同的复制功能,数据库产商就会要你支付额外的费用,而且也很难管理。

LDAP 允许你根据需要使用ACI(一般都称为ACL或者访问控制列表)控制对数据读和写的权限。例如,设备管理员可以有权改变员工的工作地点和办公室号码,但是 不允许改变记录中其它的域。ACI可以根据谁访问数据、访问什么数据、数据存在什么地方以及其它对数据进行访问控制。因为这些都是由LDAP目录服务器完 成的,所以不用担心在客户端的应用程序上是否要进行安全检查。

LDAP对于存储下面这样的信息最为有用,也就是数据需要从不同的地点读取,但是不需要经常更新。例如,这些信息存储在LDAP目录中是十分有效的:

  • 公司员工的电话号码簿和组织结构图
  • 客户的联系信息
  • 计算机管理需要的信息,包括NIS映射、email假名,等等
  • 软件包的配置信息
  • 公用证书和安全密匙

什么时候该用LDAP存储数据?

大 多数的LDAP服务器都为读密集型的操作进行专门的优化。因此,当从LDAP服务器中读取数据的时候会比从专门为OLTP优化的关系型数据库中读取数据快 一个数量级。也是因为专门为读的性能进行优化,大多数的LDAP目录服务器并不适合存储需要经常改变的数据。例如,用LDAP服务器来存储电话号码是一个 很好的选择,但是它不能作为电子商务站点的数据库服务器。

如果下面每一个问题的答案都是“是”,那么把数据存在LDAP中就是一个好主意。

  • 需要在任何平台上都能读取数据吗?
  • 每一个单独的记录项是不是每一天都只有很少的改变?
  • 可以把数据保存在平面数据库(flat database)而不是关系型数据库中吗?换句话来说,也就是不管什么范式不范式的,把所有东西都存在一个记录中(差不多只要满足第一范式)。

最 后一个问题可能会唬住一些人,其实用平面数据库去存储一些关系型的数据也是很一般的。例如,一条公司员工的记录就可以包含经理的登录名。用LDAP来存储 这类信息是很方便的。一个简单的判断方法:如果可以把数据保存在一张张的卡片里,就可以很容易地把它存在LDAP目录里。


接上文

LDAP目录树的结构
单条LDAP记录
定制目录的对象类
一个LDAP单个条目的例子
LDAP复制
安全和访问控制

LDAP目录树的结构

LDAP 目录以树状的层次结构来存储数据。如果你对自顶向下的DNS树或UNIX文件的目录树比较熟悉,也就很容易掌握LDAP目录树这个概念了。就象DNS的主 机名那样,LDAP目录记录的分辨名(Distinguished Name,简称DN)是用来读取单条记录,以及回溯到树的顶部。后面会做详细地介绍。

为什么要用层次结构来组织数据呢?原因是多方面的。下面是可能遇到的一些情况:

  • 如果你想把所有的美国客户的联系信息都“推”到位于到西雅图办公室(负责营销)的LDAP服务器上, 但是你不想把公司的资产管理信息“推”到那里。
  • 你可能想根据目录树的结构给予不同的员工组不同的权限。在下面的例子里,资产管理组对“asset-mgmt”部分有完全的访问权限,但是不能访问其它地方。
  • 把LDAP存储和复制功能结合起来,可以定制目录树的结构以降低对WAN带宽的要求。位于西雅图的营销办公室需要每分钟更新的美国销售状况的信息,但是欧洲的销售情况就只要每小时更新一次就行了。

刨根问底:基准DN

LDAP目录树的最顶部就是根,也就是所谓的“基准DN”。基准DN通常使用下面列出的三种格式之一。假定我在名为FooBar的电子商务公司工作,这家公司在Internet上的名字是foobar.com。

o="FooBar, Inc.", c=US

(以X.500格式表示的基准DN)

在 这个例子中,o=FooBar, Inc. 表示组织名,在这里就是公司名的同义词。c=US 表示公司的总部在美国。以前,一般都用这种方式来表示基准DN。但是事物总是在不断变化的,现在所有的公司都已经(或计划)上Internet上。随着 Internet的全球化,在基准DN中使用国家代码很容易让人产生混淆。现在,X.500格式发展成下面列出的两种格式。

o=foobar.com

(用公司的Internet地址表示的基准DN)

这种格式很直观,用公司的域名作为基准DN。这也是现在最常用的格式。

dc=foobar, dc=com

(用DNS域名的不同部分组成的基准DN)

就 象上面那一种格式,这种格式也是以DNS域名为基础的,但是上面那种格式不改变域名(也就更易读),而这种格式把域名:foobar.com分成两部分 dc=foobar, dc=com。在理论上,这种格式可能会更灵活一点,但是对于最终用户来说也更难记忆一点。考虑一下foobar.com这个例子。当 foobar.com和gizmo.com合并之后,可以简单的把“dc=com”当作基准DN。把新的记录放到已经存在的dc=gizmo, dc=com目录下,这样就简化了很多工作(当然,如果foobar.com和wocket.edu合并,这个方法就不能用了)。如果LDAP服务器是新 安装的,我建议你使用这种格式。再请注意一下,如果你打算使用活动目录(Actrive Directory),Microsoft已经限制你必须使用这种格式。

更上一层楼:在目录树中怎么组织数据

在UNIX文件系统中,最顶层是根目录(root)。在根目录的下面有很多的文件和目录。象上面介绍的那样,LDAP目录也是用

posted @ 2006-11-30 20:45 Alex| 编辑 收藏

key words:jotspot,wiki,项目管理

最近听说了jotspot,这个东西确实很好,原来一直打算自己建立一个wiki,作一些公司项目资源的管理,进度管理,以及提供其他团队成员了解项目信息的窗口,原来打算用jspwiki,可是建在哪呢? 在我的笔记本上还是公司的服务器上,似乎都不是很好,还是建在jotspot上比较好。zheng
给我看了一个截图:

jotspot_project.gif


功能还挺强大,我想做我的日常项目管理应该够了


posted @ 2006-11-09 22:03 Alex 阅读(695) | 评论 (2)编辑 收藏


key words:google搜索技巧,in site,inurl,filetype

最常用的是下面这个

IDEA in site:blogjava.net inurl:Alex filetype:ppt


下面转录一篇
玩转GOOGLE的十九招秘技


第一招:

  在输入多个词的时候,Google默认的是并且式的查询,如果想使用或者式的查询,使用OR,例如:java OR c++

第二招
  google是不区分大小写的,搜索Java和搜索JAVA或者java是完全一样的。

  第三招
  逻辑关系优先级使用圆括号,例如查找包含java和(JVM或者虚拟机):Java (JVM或者虚拟机)。

  第四招
  要搜索词组需要使用引号括起来,例如搜索Java虚拟机可以使用:"Java虚拟机"。

  第五招
  要想在搜索结果中不包含某些结果可以使用减号,例如搜索java又不包含培训可以使用:java -培训。

  第六招
  一般情况下是不能使用通配符的,通配符只能使用在词组中,例如:"使用*模式"。

  第七招
  只在网页的标题(即html的title指定的部分)内搜索指定的内容,例如:intitle:java。
第八招
  只在网页的url内搜索指定的内容,例如:inurl:java。

  第九招
  只在网页的正文内搜索指定的内容(忽略链接文字、标题和url),例如:intext:java。

  第十招
  只在链接文字(链接Java研究组织的链接文字就是Java研究组织)内搜索指定的内容,例如:inanchor:java。

  第十一招
  只在指定的网站内搜索指定的内容,可以是某个具体的网站或者是某个域名分类,例如:site:javaresearch.org或者site:org。

  第十二招
  只在指定的文件格式内搜索指定的内容,需要注意google只能支持有限的常用文本格式,包含一些诸如doc,xsl,ppt,pdf之类的富文本格式,例如:filetype:htm。

  第十三招
  google在检索的时候对于输入的内容的顺序是敏感的,如果找不到合适的结果可以试试改变一下搜索的关键字的顺序。


  第十四招
  在搜索多个关键字构成的内容时可以试试加引号和不加引号两者情况,结果可能有很大的差异,例如:java虚拟机和"java虚拟机"。

  第十五招
  搜索的策略可以采用先多后少,假设你想搜索Java的的command模式的例子,可以先试试java command pattern,然后试试java pattern或者java command,每次减少的那个关键字应该是你认为相对不重要的一个。
第十六招
  对于搜索中文而言,搜索“虚拟机”和搜索“虚拟机”是不同的,后者一般比前者的结果多并且相关性差一些,一般情况下你应该在各个词之间加空格,而在词内不加空格,这样得到的结果一般更好。

  第十七招
  一个关键字可以重复两次,对结果的排名和数量也会有影响,重复两次以上好像就没有什么影响了,例如搜索"internet"和搜索"internet internet"的结果是不同的。

  第十八招
  搜索内容最多只能包含十个单词,包括搜索intitle之类的选项,多出的部分被忽略。

  第十九招
  对于intitle,inurl,intext,inanchor和site之类的搜索选项一次不要使用多次,否则要么不是你想要的结果,要么根本就没有结果,混合使用这些选项是合法的,但是规则很复杂。

posted @ 2006-11-09 21:49 Alex 阅读(966) | 评论 (0)编辑 收藏

key words:mappingResources,通配符,mappingDirectoryLocations

平时写mapping的文件需要一个一个的放到配置文件里,比如
   1. <property name="mappingResources">  
   2.             
<list>  
   3.                 
<value>net/foxlog/model/Classes.hbm.xml</value>  
   4.                 
<value>net/foxlog/model/Parent.hbm.xml</value>  
   5.                 
<value>net/foxlog/model/Child.hbm.xml</value>  
   6.                 
<value>net/foxlog/model/User.hbm.xml</value>  
   7.             
</list>  
   8.         
</property>  


可以用更一步到位的方法:

   1. <property name="mappingDirectoryLocations">  
   2.             
<list>  
   3.                 
<value>classpath*:/org/springside/bookstore/commons/model/hbm</value>  
   4.             
</list>  
   5.         
</property>  

posted @ 2006-11-09 21:00 Alex 阅读(5013) | 评论 (0)编辑 收藏

key words:hibernate,复合主键,composite-id



基于业务需求,您会需要使用两个字段来作复合主键,例如在User数据表中,您也许会使用"name""phone"两个字段来定义复合主键。

假设您这么建立User表格:

CREATE TABLE user (

    name 
VARCHAR(100NOT NULL,

    phone 
VARCHAR(50NOT NULL,

    age 
INT,

    
PRIMARY KEY(name, phone)

);

在表格中,"name""age"被定义为复合主键,在映像时,您可以让User类别直接带有"name""age"这两个属性,而Hibernate要求复合主键类别要实作Serializable接口,并定义equals()hashCode()方法:

User.java

package onlyfun.caterpillar;

 

import java.io.Serializable;

import org.apache.commons.lang.builder.EqualsBuilder;

import org.apache.commons.lang.builder.HashCodeBuilder;

 

// 复合主键类的对应类别必须实作Serializable接口

public class User implements Serializable {

    
private String name;

    
private String phone;

    
private Integer age;

   

    
public User() {

    }

 

    
public Integer getAge() {

        
return age;

    }

 

    
public void setAge(Integer age) {

        
this.age = age;

    }

 

    
public String getName() {

        
return name;

    }

 

    
public void setName(String name) {

        
this.name = name;

    }

 

    
public String getPhone() {

        
return phone;

    }

 

    
public void setPhone(String phone) {

        
this.phone = phone;

    }

   

    
// 必须重新定义equals()与hashCode()

    
public boolean equals(Object obj) {

        
if(obj == this) {

            
return true;

        }

       

        
if(!(obj instanceof User)) {

            
return false;

        }

       

        User user 
= (User) obj;

        
return new EqualsBuilder()

                 .append(
this.name, user.getName())

                 .append(
this.phone, user.getPhone())

                 .isEquals();

       

    }

   

    
public int hashCode() {

        
return new HashCodeBuilder()

                 .append(
this.name)

                 .append(
this.phone)

                 .toHashCode();

    }

}

equals()hashCode()方法被用作两笔不同数据的识别依据;接着您可以使用<composite-id>在映射文件中定义复合主键与对象的属性对应:

User.hbm.xml

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
>

 

<hibernate-mapping>

 

    
<class name="onlyfun.caterpillar.User" table="user">

        
<composite-id>

            
<key-property name="name"

                          column
="name"

                          type
="java.lang.String"/>

            
<key-property name="phone"

                          column
="phone"

                          type
="java.lang.String"/>

        
</composite-id>

 

        
<property name="age" column="age" type="java.lang.Integer"/>

   

    
</class>

</hibernate-mapping>

在储存数据方面,复合主键的储存没什么区别,现在的问题在于如何依据复合主键来查询数据,例如使用load()方法,您可以创建一个User实例,并设定复合主键对应的属性,接着再透过load()查询对应的数据,例如:

User user = new User();

user.setName(
"bush");

user.setPhone(
"0970123456");

       

Session session 
= sessionFactory.openSession();

// 以实例设定复合主键并加载对应的数据

user 
= (User) session.load(User.class, user);

       

System.out.println(user.getAge() 
+ "\t" +

                                  user.getName() 
+ "\t" +

                                  user.getPhone());

session.close();

 

 

可以将主键的信息独立为一个类别,例如:

UserPK.java

package onlyfun.caterpillar;

 

import java.io.Serializable;

 

import org.apache.commons.lang.builder.EqualsBuilder;

import org.apache.commons.lang.builder.HashCodeBuilder;

 

public class UserPK implements Serializable {

    
private String name;

    
private String phone;

 

    
public String getName() {

        
return name;

    }

 

    
public void setName(String name) {

        
this.name = name;

    }

 

    
public String getPhone() {

        
return phone;

    }

 

    
public void setPhone(String phone) {

        
this.phone = phone;

    }

   

    
public boolean equals(Object obj) {

        
if(obj == this) {

            
return true;

        }

       

        
if(!(obj instanceof User)) {

            
return false;

        }

       

        UserPK pk 
= (UserPK) obj;

        
return new EqualsBuilder()

                 .append(
this.name, pk.getName())

                 .append(
this.phone, pk.getPhone())

                 .isEquals();

       

    }

   

    
public int hashCode() {

        
return new HashCodeBuilder()

                 .append(
this.name)

                 .append(
this.phone)

                 .toHashCode();

    }

}

现在User类别的主键信息被分离出来了,例如:

User.java

package onlyfun.caterpillar;

 

import java.io.Serializable;

 

public class User implements Serializable {

    
private UserPK userPK; // 主键

    
private Integer age;

   

    
public User() {

    }

 

    
public UserPK getUserPK() {

        
return userPK;

    }

 

    
public void setUserPK(UserPK userPK) {

        
this.userPK = userPK;

    }

 

    
public Integer getAge() {

        
return age;

    }

 

    
public void setAge(Integer age) {

        
this.age = age;

    }

}

在映像文件方面,需要指定主键类的信息,例如:

User.hbm.xml

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
>

 

<hibernate-mapping>

 

    
<class name="onlyfun.caterpillar.User" table="user">

        
<composite-id name="userPK"

                      class
="onlyfun.caterpillar.UserPK"

                      unsaved-value
="any">

            
<key-property name="name"

                          column
="name"

                          type
="java.lang.String"/>

            
<key-property name="phone"

                          column
="phone"

                          type
="java.lang.String"/>

        
</composite-id>

       

        
<property name="age" column="age" type="java.lang.Integer"/>

   

    
</class>

 

</hibernate-mapping>

在查询数据时,必须指定主键信息,例如:

UserPK pk = new UserPK();

pk.setName(
"bush");

pk.setPhone(
"0970123456");

      

Session session 
= sessionFactory.openSession();

// 以主键类实例设定复合主键并加载对应的数据

User user 
= (User) session.load(User.class, pk);

      

System.out.println(user.getAge() 
+ "\t" +

                                  user.getUserPK().getName() 
+ "\t" +

                                  user.getUserPK().getPhone());

session.close();

 


=================================================
再参考robbin的一篇文章
一个简单的复合主键的做关联类的例子  
posted @ 2006-11-09 19:22 Alex 阅读(12889) | 评论 (5)编辑 收藏

key words:jdk错误,Unsupported major.minor version

今天用一个工作流的产品,非要用jdk1.4的版本,没办法,只好切换回来,但是换回来后打开页面jsp出错,提示Unsupported major.minor version 49.0错误,到网上查了一下,49.0错误属于jdk1.5的错误,但是我的jdk1.5已经删除了啊?怎么回事呢?

最后想起来,可能是jboss中的1.5 版本产生临时文件class文件删除,删除后OK


资料:Unsupported major.minor version 49.0
posted @ 2006-11-08 10:13 Alex 阅读(561) | 评论 (2)编辑 收藏

key words:hibernate,load,session.get,session.load()

public class Parent implements Serializable {

    
/** identifier field */
    
private Long id;

    
/** persistent field */
    
private List childs;

    
/** full constructor */
    
public Parent(Long id, List childs) {
        
this.id = id;
        
this.childs = childs;
    }

    
/** default constructor */
    
public Parent() {
    }

    
/** 
     *            @hibernate.id
     *             generator-class="assigned"
     *             type="java.lang.Long"
     *             column="id"
     *
     
*/
    
public Long getId() {
        
return this.id;
    }

    
public void setId(Long id) {
        
this.id = id;
    }

    
/**
     *            @hibernate.set
     *             lazy="true"
     *             inverse="true"
     *             cascade="none"
     *            @hibernate.collection-key
     *             column="parent_id"
     *            @hibernate.collection-one-to-many
     *             class="net.foxlog.model.Child"
     *         
     
*/
    
public List getChilds() {
        
return this.childs;
    }

    
public void setChilds(List childs) {
        
this.childs = childs;
    }

    
public String toString() {
        
return new ToStringBuilder(this)
            .append(
"id", getId())
            .toString();
    }

    
public boolean equals(Object other) {
        
if ( !(other instanceof Parent) ) return false;
        Parent castOther 
= (Parent) other;
        
return new EqualsBuilder()
            .append(
this.getId(), castOther.getId())
            .isEquals();
    }

    
public int hashCode() {
        
return new HashCodeBuilder()
            .append(getId())
            .toHashCode();
    }

}


Parent parent = (Parent)session.load(Parent.class,new Integer(7));

运行提示出错,"can't get Entity.."

知道是什么低级错误么?  其实很弱智的东西,但是不注意还就会出现,呵呵,答案如下:


posted @ 2006-11-07 19:28 Alex 阅读(1627) | 评论 (3)编辑 收藏

key words: mysql,外键,fm150

这次在mysql中建立外键关联时发现问题:
alter table CHILD add constraint FK3D1FCFC74B18345 foreign key (PARENTID) references PARENT;      


提示..fm150错误,最后发现是mysql的版本问题,我用的是4.1的,对外键不知道是不支持还是支持的有问题,下载5.0的解决.
posted @ 2006-11-07 09:44 Alex 阅读(421) | 评论 (0)编辑 收藏

     摘要: 什么玩意  阅读全文
posted @ 2006-11-06 12:00 Alex 阅读(3434) | 评论 (9)编辑 收藏

     摘要:   阅读全文
posted @ 2006-10-31 12:32 Alex 阅读(8473) | 评论 (7)编辑 收藏

访问控制背景

    访问控制技术是由美国国防部(Department of Defense, DoD)资助的研究和开发成果演变而来的。这一研究导致两种基本类型访问控制的产生:自主访问控制(Discretionary Access Control, DAC)和强制访问控制(Mandatory Access Control, MAC)。最初的研究和应用主要是为了防止机密信息被未经授权者访问,近期的应用主要是把这些策略应用到为商业领域。

    自主访问控制,允许把访问控制权的授予和取消留给个体用户来判断。为没有访问控制权的个体用户授予和废除许可。自主访问控制机制允许用户被授权和
取 消访问其控制之下的任何客体(object),换句话说,用户就是他们控制下的客体的拥有者。然而,对于多数组织来说,最终用户对所访问的信息没有拥有 权。对于这些组织,公司或代理机构是事实上的系统客体和处理他们的程序的拥有者。访问优先权受组织控制,而且也常常基于雇员功能而不是数据所有权。

   强制访问控制,在美国国防部 Trusted Computer Security Evaluation Criteria (TCSEC) 中定义如下:“一种限制访问客体的手段,它以包含在这些客体中的信息敏感性和访问这些敏感性信息的主体的正式授权信息(如清除)为基础”。
  
   以上访问控制策略对于处理一些无需保密但又敏感的信息的政府和行业组织的需求并不是特别的适合。在这样的环境下,安全目标支持产生于现有法律、道德规范、 规章、或一般惯例的高端组织策略。这些环境通常需要控制个体行为的能力,而不仅仅是如何根据信息的敏感性为其设置标签从而访问这一信息的个人能力。
    


什么是基于角色访问控制(Role-Based Access Control, RBAC)?NIST 有如下定义。
  
   访问是一种利用计算机资源去做某件事情的的能力,访问控制是一种手段,通过它这种能力在某些情况下被允许或者受限制(通常是通过物理上和基于系统的控 制)。基于计算机的访问控制不仅可规定是“谁”或某个操作有权使用特定系统资源,而且也能规定被允许的访问类型。这些控制方式可在计算机系统或者外部设备 中实现。
  
    就基于角色访问控制而言,访问决策是基于角色的,个体用户是某个组织的一部分。用户具有指派的角色(比如医生、护士、出纳、经理)。定义角色的过程应该基于对组织运转的彻底分析,应该包括来自一个组织中更广范围用户的输入。
   
    访问权按角色名分组,资源的使用受限于授权给假定关联角色的个体。例如,在一个医院系统中,医生角色可能包括进行诊断、开据处方、指示实验室化验等;而研究员的角色则被限制在收集用于研究的匿名临床信息工作上。
   
    控制访问角色的运用可能是一种开发和加强企业特殊安全策略,进行安全管理过程流程化的有效手段。
     


用户(User)和角色(Role)

    用户指访问系统中的资源的主体,一般为人,也可为 Agent 等智能程序。角色指应用领域内一种权力和责任的语义综合体,可以是一个抽象概念,也可以是对应于实际系统中的特定语义体,比如组织内部的职务等。针对角色 属性的不同,某些模型中将角色进一步细分为普通角色和管理员角色(可理解为全局角色)。

 

许可(Permissions)和权限(Permission)

    许可描述了角色对计算机资源的访问和操作所具有的权限,其反映的是授权的结果。比如授予某个角色对计算机资源有读的权限,则代表了一个许可的存在,这个许 可表示:角色获取了对计算机资源的读许可。针对操作来说,其描述的是许可和操作之间的一种关联关系,而这层关系则表示了某一角色对某一操作所具有的权限及 权限状态。


     
角色和指派(Assignment)

    指派包含两个方面,用户指派和许可指派。用户指派表示的是,将用户指派给特定的角色。许可指派表示的是为角色指派计算机资源的访问和操作许可。

 

会话(session)

    会话表示的是用户和角色之间的关系。用户每次必须通过建立会话来激活角色,得到相应的访问权限。

 

角色和角色等级(Role Hierarchies)

    角色本身仅仅只是一个名词,其本身并不能代表权限的大小。比如,我们可以定一个“Director”的角色,也可以定一个“Project Leader”的角色。对于现实中我们来说,看到这样两个角色,就清楚 DIR 的权限要比一个 PL 的权限级别高。但是对计算机来说,这两个角色仅仅是两个“词语”,是等同的。可以采用分等级角色,在角色上实现层次化来解决这些问题。也可以采用复合角色 (其表示的就是一个角色组的概念),对角色实现一定的分组和复合,以便于权限指派。在一些 OA 产品中经常出现分等级角色。
   


限制(Constraints)
   
    模型中的职责分离关系(Separation of Duty),用于控制冲突(Conflict)。静态职责分离(Static SD)指定角色的互斥关系,用于用户指派阶段。避免同一用户拥有互斥的角色。实现简单,角色互斥语义关系清楚,便于管理不够灵活,不能处理某些实际情况。 动态职责分离(Dynamic SD)指定角色的互斥关系,用于角色激活阶段。允许同一用户拥有某些互斥的角色,但是不允许该用户同时激活互斥的角色。更灵活,直接与会话挂钩,适应实际 管理需要,实现复杂,不易管理。

             

 

 

参考文献

《AN INTRODUCTION TO ROLE-BASED ACCESS CONTROL》 NIST

《工作流授权控制模型》        胡长城

《基于角色的权限管理综述》 俞诗鹏

 

 

最后,感谢宏云博士对本文翻译提供的指导。   


请注意!引用、转贴本文应注明原作者:Rosen Jiang 以及出处:http://www.blogjava.net/rosen

posted @ 2006-10-26 21:31 Alex 阅读(461) | 评论 (0)编辑 收藏

     摘要: 前言 : 权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“ Who 对 What(Which) ...  阅读全文
posted @ 2006-10-26 21:28 Alex 阅读(360) | 评论 (0)编辑 收藏

转自 javaEye


很多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章介绍一下hibernate的二级缓存的,今天终于忍不住了。
我的经验主要来自hibernate2.1版本,基本原理和3.0、3.1是一样的,请原谅我的顽固不化。

hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了。

二级缓存是SessionFactory级别的全局缓存,它底下可以使用不同的缓存类库,比如ehcache、oscache等,需要设置hibernate.cache.provider_class,我们这里用ehcache,在2.1中就是
hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider
如果使用查询缓存,加上
hibernate.cache.use_query_cache=true

缓存可以简单的看成一个Map,通过key在缓存里面找value。

Class的缓存
对于一条记录,也就是一个PO来说,是根据ID来找的,缓存的key就是ID,value是POJO。无论list,load还是 iterate,只要读出一个对象,都会填充缓存。但是list不会使用缓存,而iterate会先取数据库select id出来,然后一个id一个id的load,如果在缓存里面有,就从缓存取,没有的话就去数据库load。假设是读写缓存,需要设置:
<cache usage="read-write"/>
如果你使用的二级缓存实现是ehcache的话,需要配置ehcache.xml
<cache name="com.xxx.pojo.Foo" maxElementsInMemory="500" eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowToDisk="true" />
其中eternal表示缓存是不是永远不超时,timeToLiveSeconds是缓存中每个元素(这里也就是一个POJO)的超时时间,如 果eternal="false",超过指定的时间,这个元素就被移走了。timeToIdleSeconds是发呆时间,是可选的。当往缓存里面put 的元素超过500个时,如果overflowToDisk="true",就会把缓存中的部分数据保存在硬盘上的临时文件里面。
每个需要缓存的class都要这样配置。如果你没有配置,hibernate会在启动的时候警告你,然后使用defaultCache的配置,这样多个class会共享一个配置。
当某个ID通过hibernate修改时,hibernate会知道,于是移除缓存。
这样大家可能会想,同样的查询条件,第一次先list,第二次再iterate,就可以使用到缓存了。实际上这是很难的,因为你无法判断什么时候 是第一次,而且每次查询的条件通常是不一样的,假如数据库里面有100条记录,id从1到100,第一次list的时候出了前50个id,第二次 iterate的时候却查询到30至70号id,那么30-50是从缓存里面取的,51到70是从数据库取的,共发送1+20条sql。所以我一直认为 iterate没有什么用,总是会有1+N的问题。
(题外话:有说法说大型查询用list会把整个结果集装入内存,很慢,而iterate只select id比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页20条的话,iterate共需要执行21条语句,list虽然选择 若干字段,比iterate第一条select id语句慢一些,但只有一条语句,不装入整个结果集hibernate还会根据数据库方言做优化,比如使用mysql的limit,整体看来应该还是 list快。)
如果想要对list或者iterate查询的结果缓存,就要用到查询缓存了

查询缓存
首先需要配置hibernate.cache.use_query_cache=true
如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了
<cache name="net.sf.hibernate.cache.StandardQueryCache"
maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600"
timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="net.sf.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/>
然后
query.setCacheable(true);//激活查询缓存
query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可选
第二行指定要使用的cacheRegion是myCacheRegion,即你可以给每个查询缓存做一个单独的配置,使用setCacheRegion来做这个指定,需要在ehcache.xml里面配置它:
<cache name="myCacheRegion" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true" />
如果省略第二行,不设置cacheRegion的话,那么会使用上面提到的标准查询缓存的配置,也就是net.sf.hibernate.cache.StandardQueryCache

对于查询缓存来说,缓存的key是根据hql生成的sql,再加上参数,分页等信息(可以通过日志输出看到,不过它的输出不是很可读,最好改一下它的代码)。
比如hql:
from Cat c where c.name like ?
生成大致如下的sql:
select * from cat c where c.name like ?
参数是"tiger%",那么查询缓存的key*大约*是这样的字符串(我是凭记忆写的,并不精确,不过看了也该明白了):
select * from cat c where c.name like ? , parameter:tiger%
这样,保证了同样的查询、同样的参数等条件下具有一样的key。
现在说说缓存的value,如果是list方式的话,value在这里并不是整个结果集,而是查询出来的这一串ID。也就是说,不管是list方 法还是iterate方法,第一次查询的时候,它们的查询方式很它们平时的方式是一样的,list执行一条sql,iterate执行1+N条,多出来的 行为是它们填充了缓存。但是到同样条件第二次查询的时候,就都和iterate的行为一样了,根据缓存的key去缓存里面查到了value,value是 一串id,然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。
可以看出来,查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候,都是既填充查询缓存又填充class缓存的。
这里还有一个很容易被忽视的重要问题,即打开查询缓存以后,即使是list方法也可能遇到1+N的问题!相同条件第一次list的 时候,因为查询缓存中找不到,不管class缓存是否存在数据,总是发送一条sql语句到数据库获取全部数据,然后填充查询缓存和class缓存。但是第 二次执行的时候,问题就来了,如果你的class缓存的超时时间比较短,现在class缓存都超时了,但是查询缓存还在,那么list方法在获取id串以 后,将会一个一个去数据库load!因此,class缓存的超时时间一定不能短于查询缓存设置的超时时间!如果还设置了发呆时间的话,保证class缓存 的发呆时间也大于查询的缓存的生存时间。这里还有其他情况,比如class缓存被程序强制evict了,这种情况就请自己注意了。

另外,如果hql查询包含select字句,那么查询缓存里面的value就是整个结果集了。

当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢?
hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。
当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时 间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这 些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。

Collection缓存
需要在hbm的collection里面设置
<cache usage="read-write"/>
假如class是Cat,collection叫children,那么ehcache里面配置
<cache name="com.xxx.pojo.Cat.children"
maxElementsInMemory="20" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200"
overflowToDisk="true" />
Collection的缓存和前面查询缓存的list一样,也是只保持一串id,但它不会因为这个表更新过就失效,一个collection缓存仅在这个collection里面的元素有增删时才失效。
这样有一个问题,如果你的collection是根据某个字段排序的,当其中一个元素更新了该字段时,导致顺序改变时,collection缓存里面的顺序没有做更新。

缓存策略
只读缓存(read-only):没有什么好说的
读/写缓存(read-write):程序可能要的更新数据
不严格的读/写缓存(nonstrict-read-write):需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好
事务缓存(transactional):缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境,这个我没有怎么研究过

读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。
在hibernate2.1的ehcache实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到60秒后超时。
不严格读写缓存不锁定缓存中的数据。

使用二级缓存的前置条件
你的hibernate程序对数据库有独占的写访问权,其他的进程更新了数据库,hibernate是不可能知道的。你操作数据库必需直接通过 hibernate,如果你调用存储过程,或者自己使用jdbc更新数据库,hibernate也是不知道的。hibernate3.0的大批量更新和删 除是不更新二级缓存的,但是据说3.1已经解决了这个问题。
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,但是你却不能自己写jdbc来优化,很郁闷吧。
SessionFactory也提供了移除缓存的方法,你一定要自己写一些JDBC的话,可以调用这些方法移除缓存,这些方法是:
void evict(Class persistentClass)
Evict all entries from the second-level cache.
void evict(Class persistentClass, Serializable id)
Evict an entry from the second-level cache.
void evictCollection(String roleName)
Evict all entries from the second-level cache.
void evictCollection(String roleName, Serializable id)
Evict an entry from the second-level cache.
void evictQueries()
Evict any query result sets cached in the default query cache region.
void evictQueries(String cacheRegion)
Evict any query result sets cached in the named query cache region.
不过我不建议这样做,因为这样很难维护。比如你现在用JDBC批量更新了某个表,有3个查询缓存会用到这个表,用evictQueries (String cacheRegion)移除了3个查询缓存,然后用evict(Class persistentClass)移除了class缓存,看上去好像完整了。不过哪天你添加了一个相关查询缓存,可能会忘记更新这里的移除代码。如果你的 jdbc代码到处都是,在你添加一个查询缓存的时候,还知道其他什么地方也要做相应的改动吗?

----------------------------------------------------

总结:
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。
如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为 底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。

终于写完了,好累……

posted @ 2006-10-23 14:30 Alex 阅读(604) | 评论 (1)编辑 收藏

在Struts或别的框架中集成Spring的时候,Spring向我们提供了获得context的方法 getApplicationContext,那在jsp中如何获得呢?

ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext
(
this.getServletConfig().getServletContext());

建议在项目中开发的时候提供一个singleton对外公布统一的applicationContext,毕竟不是每个人都一定能获得web环境或servlet.

posted @ 2006-10-23 11:03 Alex 阅读(2521) | 评论 (0)编辑 收藏

碰到一个奇怪的问题,Spring在启动的时候得listener提示启动失败,打开log也没有任何信息,最后把log4j打开 :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        
<layout class="org.apache.log4j.PatternLayout">
            
<param name="ConversionPattern"
                   value
="%p - %C{1}.%M(%L) | %m%n"/>
        
</layout>
    
</appender>

    
<logger name="org.apache">
        
<level value="WARN"/>
    
</logger>


    
<logger name="net.sf.hibernate">
        
<level value="WARN"/>
    
</logger>

    
<logger name="org.springframework">
        
<level value="DEBUG"/>
    
</logger>
    
<!--
    <logger name="org.appfuse">
        <level value="DEBUG"/>
    </logger>
-->
    
<root>
        
<level value="DEBUG"/>
        
<appender-ref ref="CONSOLE"/>
    
</root>

</log4j:configuration>

提示说applicationContext.xml的编码有问题,最后改为UTF-8解决
posted @ 2006-10-23 10:57 Alex 阅读(1367) | 评论 (0)编辑 收藏

打印可以用控件实现,保存为excel也可以用POI实现,不过如果仅仅是对当前页面的指定区域作打印或者Excel导出可以用js实现,还是挺简单的.

保存为Excel:

function saveAsExcel(HeadName, DivName) {
            
var s = "<center>" + HeadName + "</center>" + "\r\n";
            s 
+= DivName.innerHTML;
            
var xlsWindow = window.open("""_blank""width=1,height=1,scrollbars=no,toolbar=no");
            xlsWindow.document.write(s);
            xlsWindow.document.close();
            xlsWindow.document.execCommand('Saveas', 
true, '%homeDrive%\\Data.xls')
            xlsWindow.close();
        }


打印当前页面:

function PrintDataSoure(HeadName1,HeadName2,HeadName3,DivName,TailName1) {
  
var oldBody=document.body.innerHTML;
  
var Div1=DivName.innerHTML;
  
var css = '<style type="text/css" media=all>+
  'p { line
-height: 120%}' +
  '.fhead {   font
-size: 9pt; text-decoration: none; color: 104A7B}' +
  '.ftitle { line
-height: 120%; font-size: 18px; color: #000000}' +
  'td { font
-size: 10px; color: #000000}' +
  '
</style>' ;

  
var body ='<table width="640" border="0" cellspacing="0" cellpadding="5">+
  ' 
<tr> ' +
  ' 
<td class="fbody">+
  ' 
<b><div align="center">'+'<font size="+1" class=fhead>'+ HeadName1 + '</div>'+'</font></b>'+
  ' 
<b><div align="center">'+'<font size="+1" class=fhead>'+ HeadName2 + '     ' + HeadName3 +'</div></font></b>'+
  ' 
</td>+
  ' 
</tr>+
  ' 
<tr> ' +
  ' 
<td class="fbody">+
  ' 
<div align="center" class=ftitle>+ Div1 + '</div>'+
  ' 
<b><div align="right">'+'<font size="+1" class=fhead>'+ '      </div>'+'</font></b>'+
  ' 
<b><div align="right">'+'<font size="+1" class=fhead>'+ TailName1 + '</div>'+'</font></b>'+
  ' 
</td>+
  ' 
</tr>+
  '
</table>';
document.body.innerHTML 
= '<center>+ css + body +'<OBJECT classid="CLSID:8856F961-340A-11D0-A96B-00C04FD705A2" height=0 id=wb name=wb width=0></OBJECT>'+'</center>';
wb.execwb(
6,6);
document.body.innerHTML
=oldBody;
}




posted @ 2006-10-21 12:19 Alex 阅读(3127) | 评论 (0)编辑 收藏

     摘要: key words:Spring,jdbcTemplate 注:因为Spring是以后的一个趋势,Hibernate的集成已经很好了,对于单独的jdbc的操作用DBUtils感觉已经没有什么必要,不如全部转到Spring的jdbc支持,从成本来考虑似乎更合适。 本文转自 这里 ...  阅读全文
posted @ 2006-10-16 16:28 Alex 阅读(2728) | 评论 (0)编辑 收藏

     摘要:   阅读全文
posted @ 2006-10-10 11:09 Alex 阅读(2031) | 评论 (3)编辑 收藏

Facade用的非常的广了,以前刚接触的时候有个误解,总觉得Facade是简单的,而它后面的支撑服务是复杂的,对于客户来说却是简单的,现在来看,不完全对,或者说只是说对了一半,因为有时候恰恰是Facade是复杂的.

我们举一个例子,比如发送短信,我们一般就定义一个MessageService的服务类,里面只提供一个方法就行了,sendToUser(String phone,String content)
但是到了客户端的时候有了自己的 "方言",比如它不是关心一个抽象的用户,它只知道向教师发送短信,或者向学生发送短信,或者向家长发送短信。

示例如下:

facade.png
由图中可以看到,Facade的内容非常丰富,而支撑它的服务类却很简单,在开发过程中我们一般先实现通用的ServiceA,然后根据进一步的需求做一个面向具体复杂的Facade.



在Spring提供的sample里发现一个小技巧,就是Facade和ServiceA都是接口,然后提供一个实现二者的支撑类:


public class PetStoreAnnotationImpl implements PetStoreFacade, OrderService {

    
private AccountDao accountDao;

    
private CategoryDao categoryDao;

    
private ProductDao productDao;

    
private ItemDao itemDao;

    
private OrderDao orderDao;


    
//-------------------------------------------------------------------------
    
// Setter methods for dependency injection
    
//-------------------------------------------------------------------------

    
public void setAccountDao(AccountDao accountDao) {
        
this.accountDao = accountDao;
    }

    
public void setCategoryDao(CategoryDao categoryDao) {
        
this.categoryDao = categoryDao;
    }

    
public void setProductDao(ProductDao productDao) {
        
this.productDao = productDao;
    }

    
public void setItemDao(ItemDao itemDao) {
        
this.itemDao = itemDao;
    }

    
public void setOrderDao(OrderDao orderDao) {
        
this.orderDao = orderDao;
    }


    
//-------------------------------------------------------------------------
    
// Operation methods, implementing the PetStoreFacade interface
    
//-------------------------------------------------------------------------

    
public Account getAccount(String username) {
        
return this.accountDao.getAccount(username);
    }

    
public Account getAccount(String username, String password) {
        
return this.accountDao.getAccount(username, password);
    }

    
public void insertAccount(Account account) {
        
this.accountDao.insertAccount(account);
    }

    
public void updateAccount(Account account) {
        
this.accountDao.updateAccount(account);
    }

    
public List getUsernameList() {
        
return this.accountDao.getUsernameList();
    }

    
public List getCategoryList() {
        
return this.categoryDao.getCategoryList();
    }

    
public Category getCategory(String categoryId) {
        
return this.categoryDao.getCategory(categoryId);
    }

    
public List getProductListByCategory(String categoryId) {
        
return this.productDao.getProductListByCategory(categoryId);
    }

    
public List searchProductList(String keywords) {
        
return this.productDao.searchProductList(keywords);
    }

    
public Product getProduct(String productId) {
        
return this.productDao.getProduct(productId);
    }

    
public List getItemListByProduct(String productId) {
        
return this.itemDao.getItemListByProduct(productId);
    }

    
public Item getItem(String itemId) {
        
return this.itemDao.getItem(itemId);
    }

    
public boolean isItemInStock(String itemId) {
        
return this.itemDao.isItemInStock(itemId);
    }

    
public void insertOrder(Order order) {
        
this.orderDao.insertOrder(order);
        
this.itemDao.updateQuantity(order);
    }

    
public Order getOrder(int orderId) {
        
return this.orderDao.getOrder(orderId);
    }

    
public List getOrdersByUsername(String username) {
        
return this.orderDao.getOrdersByUsername(username);
    }

}


看起来似乎不错,不过仔细想想个人认为还是不是太好,总的感觉就是层次不清晰,因为很多时候Facade和Service之间是被服务与服务的关系,所以理当分开。 同时,这个类有点倾向于"万能类"了,能分还是分开好.这和我们以前提到的dao又背离过来了(以前我们提倡一个业务一个dao,现在觉得只用一个通用的dao更合适),这个并不矛盾,具体问题具体看待.

欢迎各位拍砖.
posted @ 2006-10-09 22:27 Alex 阅读(1354) | 评论 (2)编辑 收藏

设计模式之Visitor

板桥里人 http://www.jdon.com 2002/05/05(转载请保留)

模式实战书籍《Java实用系统开发指南》

Visitor访问者模式定义
作用于某个对象群中各个对象的操作. 它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.

在Java中,Visitor模式实际上是分离了collection结构中的元素和对这些元素进行操作的行为.

为何使用Visitor?
Java的Collection(包括Vector和Hashtable)是我们最经常使用的技术,可是Collection好象是个黑色大染缸,本来有 各种鲜明类型特征的对象一旦放入后,再取出时,这些类型就消失了.那么我们势必要用If来判断,如:


Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
   Object o = iterator.next();
   if (o instanceof Collection)
      messyPrintCollection((Collection)o);
   else if (o instanceof String)
      System.out.println("'"+o.toString()+"'");
   else if (o instanceof Float)
      System.out.println(o.toString()+"f");
   else
      System.out.println(o.toString());
}
在上例中,我们使用了 instanceof来判断 o的类型.

很显然,这样做的缺点代码If else if 很繁琐.我们就可以使用Visitor模式解决它.

如何使用Visitor?
针对上例,定义接口叫Visitable,用来定义一个Accept操作,也就是说让Collection每个元素具备可访问性.

被访问者是我们Collection的每个元素Element,我们要为这些Element定义一个可以接受访问的接口(访问和被访问是互动的,只有访问者,被访问者如果表示不欢迎,访问者就不能访问),取名为Visitable,也可取名为Element。

public interface Visitable
{
   public void accept(Visitor visitor);
}

被访问的具体元素继承这个新的接口Visitable:

public class StringElement implements Visitable
{
   private String value;
   public StringElement(String string) {
      value = string;
   }

   public String getValue(){
      return value;
   }


   //定义accept的具体内容 这里是很简单的一句调用
   public void accept(Visitor visitor) {
      visitor.visitString(this);
   }
}


上面是被访问者是字符串类型,下面再建立一个Float类型的:

public class FloatElement implements Visitable
{
   private Float value;
   public FloatElement(Float value) {
      this.value = value;
   }

   public Float getValue(){
      return value;
   }


   //定义accept的具体内容 这里是很简单的一句调用
   public void accept(Visitor visitor) {
      visitor.visitFloat(this);
   }
}


我 们设计一个接口visitor访问者,在这个接口中,有一些访问操作,这些访问操作是专门访问对象集合Collection中有可能的所有类,目前我们假 定有三个行为:访问对象集合中的字符串类型;访问对象集合中的Float类型;访问对象集合中的对象集合类型。注意最后一个类型是集合嵌套,通过这个嵌套 实现可以看出使用访问模式的一个优点。

接口visitor访问者如下:

public interface Visitor
{

   public void visitString(StringElement stringE);
   public void visitFloat(FloatElement floatE);
   public void visitCollection(Collection collection);

}

访问者的实现:

public class ConcreteVisitor implements Visitor
{
   //在本方法中,我们实现了对Collection的元素的成功访问
   public void visitCollection(Collection collection) {
      Iterator iterator = collection.iterator()
      while (iterator.hasNext()) {
         Object o = iterator.next();
         if (o instanceof Visitable)
            ((Visitable)o).accept(this);
      }
   }

   public void visitString(StringElement stringE) {
      System.out.println("'"+stringE.getValue()+"'");
   }
   public void visitFloat(FloatElement floatE){
      System.out.println(floatE.getValue().toString()+"f");
   }

}

在上面的visitCollection我们实现了对Collection每个元素访问,只使用了一个判断语句,只要判断其是否可以访问.

StringElement只是一个实现,可以拓展为更多的实现,整个核心奥妙在accept方法中,在遍历Collection时,通过相应的accept方法调用具体类型的被访问者。这一步确定了被访问者类型,

如果是StringElement,而StringElement则回调访问者的visiteString方法,这一步实现了行为操作方法。

客户端代码:

Visitor visitor = new ConcreteVisitor();

StringElement stringE = new StringElement("I am a String");
visitor.visitString(stringE);

Collection list = new ArrayList();
list.add(new StringElement("I am a String1"));
list.add(new StringElement("I am a String2"));
list.add(new FloatElement(new Float(12)));
list.add(new StringElement("I am a String3"));
visitor.visitCollection(list);

客户端代码中的list对象集合中放置了多种数据类型,对对象集合中的访问不必象一开始那样,使用instance of逐个判断,而是通过访问者模式巧妙实现了。

至此,我们完成了Visitor模式基本结构.

使用Visitor模式的前提
使用访问者模式是对象群结构中(Collection) 中的对象类型很少改变。

在两个接口Visitor和Visitable中,确保Visitable很少变化,也就是说,确保不能老有新的Element元素类型加进来,可以变化的是访问者行为或操作,也就是Visitor的不同子类可以有多种,这样使用访问者模式最方便.

如果对象集合中的对象集合经常有变化, 那么不但Visitor实现要变化,Visistable也要增加相应行为,GOF建议是,不如在这些对象类中直接逐个定义操作,无需使用访问者设计模式。

但是在Java中,Java的Reflect技术解决了这个问题,因此结合reflect反射机制,可以使得访问者模式适用范围更广了。

Reflect技术是在运行期间动态获取对象类型和方法的一种技术,具体实现参考Javaworld的英文原文.
posted @ 2006-09-28 09:30 Alex 阅读(522) | 评论 (0)编辑 收藏

key words:软件设计 建模 UML

这篇文章以前就看到了,后来再想看的时候居然找不到了,感觉写的不错,作为想把软件开发往深里整地朋友有借鉴作用。

转自这里

前段时间把一个界面框架完成了,今天基于这个框架开发一个小模块,在这里把这个模块设计的全过程记录下来,希望大家讨论并指正。

一、起因

公 司交给我一个任务,为测试员写一个手机模拟界面,以方便她们的手机短信测试。过去她们都是用MC4J直接调用公司服务器的MBean服务来模拟进行测试, 以验证我们整个系统平台。这种测试主要是检查收发短信是否正常,而我的要做的工作就是,让她们在测试的时候更方便更直观。

二、需求

我和测试员陈MM(也就是软件的使用者)约定了一个时间,大家一起来讨论这个软件的需求。

1、首先,我大概了解了一下她们的测试工作,知道我要做个什么东东。

2、然后我回去思考了一下,再次找她详细了解其测试的具体步骤,并在一张白纸上以UML用例图的方式,记录下需求的功能。用例是什么?用例就是需求,就是你的软件应该具有的功能,当然用例图只是概括性的对功能进行了描述。

3、最后,我坐在我的电脑前开始用MagicDraw UML来画用例图(我不喜欢用Rose,那玩意太笨重了,界面友好性也不好)。在画用例图的时候,我发现了一些隐含的功能,这些是陈MM在和我做需求时没有考虑到的(注:开发者应该为用户挖掘隐含需求)。我和陈MM一一确定了这些我新发现的需求,最后得到如下的用例图。

(1)手机前台测试操作的用例图(说明:include是指某用例包含(include)子用例)

[用例]手机.jpg
 (2)后台管理
[用例]后台管理.jpg


三、界面设计

接下来是界面设计。既然是手机模拟,我很自然就拿我的motorola手机的操作界面来做参考。不过这里应该注意到,手机操作环境和电脑操作环境不尽相同(比如说电脑有鼠标,还有键盘可以输入文字),所以没有必要唯妙唯肖的完全模枋,还是以使用者操作方便为主。

界 面设计是很重要的一步,不要一上来就写程序,一定要先做到心中有个大概,否则返工的可能性就很大。而且,把界面拿出来给客户看,客户也就能做到心中有数, 还能尽早提出一些新需求和意见来。千万不要等到软件做完了再拿给客户看,到时客户看了如果要修改,那就做太多白费工了。

由于软件界面相对简单,陈MM基本没有提修改意见,但这不是个好兆头。不过极限编程就是要拥抱变化不是^_^。咱不怕她改,只要大致的界面她能定下来就行了。

界面我喜欢用Visio来画,当然也听说有人喜欢用VB来快速构建界面原型的,看个人喜好了。整个界面如下:
[界面设计]手机.jpg



这个是后台管理界面
[界面设计]号码管理.jpg



四、类图

类图反映了软件的数据模型。在设计数据模型,我参考了界面设计图和用例图,找出一个个的类。然后参照用例图的一个个功能,设计出了各类的属性和方法。设计初始的类图当然不可能很详细,但至少应该看到个大概。有错误不要紧,后期可以慢慢修正,但大体关系就算定下来了。

Neil(公司CTO,一个40岁左右的真正的资深程序员)说:看一个软件的设计主要看两个类:类图和时序图。类图确定了软件数据模型的静态关型,时序图则是数据模型的动态关系。

类图如下,看英文大致可以知道类/属性/方法的含义和作用了,就不一一介绍了。


[类图].jpg



五、时序图

时序图是本文最后一个图,时序图表明了用例图中各功能的实现方案,同时也反应了类图中各类的交互关系。以后程序的逻辑和时序图基本一致。不过,有些人会去画得很详细的时序图,详细到都快赶上伪代码级别了,我觉得这没必要。我把时序图看做反映自己思路的大概过程,所以也就画个大概。

我认为时序图要简洁易懂,这样以后你的后继维护者,拿到这个软件的时序图(当然也包括用例图、类图),就能明白你的大概设计思路。另外,画时序图也能整理自己的思路,同时还可以对类图的设计进行验证。在画这个时序图的过程中,我就纠正了在类图中的几处考虑不周的地方。

总结:时序图可以(1)整理思路(2)验证类的设计(3)是很好的软件文档,对维护者理解代码很有帮助。

这里仅给出其中几个时序图(实际上我也没有把用例都画完,有些类似的简单的,就忽略了)

(1)新增一个手机号码
[时序图]add phone number.jpg

(2)关机
[时序图]power off.jpg
(3)开机
[时序图]power on.jpg
(4)发送短信
[时序图]send message.jpg



到这里设计阶段就完成了,用时一天。下一步是编码,将应用TDD先写测试代码的方式来写代码,下次再介绍了。


作者简介

陈刚,广西桂林人,著作有《Eclipse从入门到精通》
您可以通过其博客了解更多信息和文章:http://www.ChenGang.com.cn
版权声明:本博客所有文章仅适用于非商业性转载,并请在转载时注明出处及作者的署名。

posted @ 2006-09-22 13:16 Alex 阅读(917) | 评论 (0)编辑 收藏

key words:软件架构师
转自here

现在软件架构师满天飞,是个写代码的都称自己为软件架构师,就象开个公司管上四五号人就给自己按个CEO头衔一样,着实让人好笑。于是到网上GOOGLE了一下看看软件构架师具体是个啥东东,有想做货真价实的构架师,就朝着那方向努力吧。网摘如下:

软件架构师的职责:将客户的需求转换为规范的开发计划及文本,并制定这个项目的总体架构,指导整个开发团队完成这个计划。

软件架构师的具体工作:
    (
1)在需求阶段,软件架构师主要负责理解和管理非功能性系统需求,比如软件的可维护性、性能、复用性、可靠性、有效性和可测试性等等,此外,架构师还要经常审查和客户及市场人员所提出的需求,确认开发团队所提出的设计;
    (
2)在需求越来越明确后,架构师的关注点开始转移到组织开发团队成员和开发过程定义上;
    (
3)在软件设计阶段,架构师负责对整个软件体系结构、关键构件、接口和开发政策的设计;
    (
4)在编码阶段,架构师则成为详细设计者和代码编写者的顾问,并且经常性地要举行一些技术研讨会、技术培训班等;
    (
5)随着软件开始测试、集成和交付,集成和测试支持将成为软件架构师的工作重点;
    (
6)在软件维护开始时,软件架构师就开始为下一版本的产品是否应该增加新的功能模块进行决策。
 
软件架构师的要求
      (
1)必须对开发技术非常了解,具有丰富的软件设计与开发经验,关键时候能对技术的选择作出及时、有效的决定。
      (
2)有良好的组织管理能力:沟通、领导、团队协作
      (
3)构件通信机制方面的知识:远程调用、JAVARMI、CORBA、COM/DCOM、各种标准的通信协议、网络服务、面对对象数据库、关系数据库等等

成长为软件架构师的几个阶段:
      (
1)构架师胚胎(程序员):语言基础、设计基础、通信基础等,内容包括java、c、c++、uml、RUP、XML、socket通信(通信协议)
      (
2)构架师萌芽(高级程序员):分布式系统组建等内容,包括分布式系统原理、ejb、corba、com/com+、webservice、网络计算机、高性能并发处理等
      (
3)构架师幼苗(设计师):透彻掌握设计模式,包括设计模式(c++版本、java版本)、ejb设计模式、J2EE构架、UDDI、软件设计模式等。此期间,最好能够了解软件工程在实际项目中的应用以及小组开发、团队管理
posted @ 2006-09-22 13:14 Alex 阅读(629) | 评论 (0)编辑 收藏

key words: DAO模式

今天在看一篇文章时提到了DAO,这个东西以前也经常接触,突然想回顾一下,于是打开Appfuse里看看dao模式(记忆中appfuse里就是很多的dao)

截图如下:
appfusedao.png

很清楚,左边的部分是基础模块,原意是想让右边的DAO和实现能够重用左边的,可是我找了半天也没看到需要重用左边的东西,因为在client调用的所有方法中都是明确的getUser或removeUser,就是没有getObject或者removeObject,那么不禁要问,左边的基础dao和它的实现还有什么意义呢?所以我的第一想法就是把左边的去掉得了,还好,果然有支持我想法的做法,打开springside,我们看到如下的结构:
截图2
springside.png
这里的做法更厉害,连interface也不要了,不过效果确实是很简洁,在bookmanager里完全重用了左边的基本方法 :
public Book get(Integer id) {
        
return (Book) super.get(id);
    }

    
public void save(Book book) {
        
super.save(book);
        logger.info(
"保存图书.图书详情:" + book.toString());
    }

    
public void remove(Integer id) {
        
super.remove(id);
        logger.info(
"删除图书.图书ID:" + id);
    }

这是一个更务实的做法,如果你的项目并不是那么那么的复杂完全可以这么做,当然要说其有什么缺点显然和没有了interface的天生属性决定了的,不可强求,若你对测试隔离面向接口以及你能想到的所有关于interface的好处,那就用你自己的方式吧。

现在我在想一个问题,难道appfuse里的继承的基本关于object的做法就没有地方可用了么?


其时正好碰到java视线这一篇文章有点相关,你可以参考一下:
用DAO模式有什么好处?


ps:
以前是一个基本的dao,然后n个业务dao继承于这个基本dao,现在提供一个通用dao,每个要用到的地方直接继承用就是了,更务实了!
不过,有一个小小的瑕疵,就是对于service中类似getUserByName或者getPeopleByEmail方法中需要提供给dao一个sql语句,从mvc的角度看,在service中看到了db层,有点不雅,不过综合来看这个还是可以或略,不要专牛角尖嘛  :)

posted @ 2006-09-21 16:05 Alex 阅读(2425) | 评论 (0)编辑 收藏

key words: VSS IDEA

vss实在是太恶心了,提交各东西还不支此右键,要是要在开发一段时间后提交多个不同的目录下的文件费死劲了,我压根不知道哪个文件还没有提交,Fuck Microsoft!

至少,我们还有IDEA  :)  在开发工具里提交的一个好处就是直观和简便.Let's go!

具体指导请看这里
你需要填写关于VSS的相关信息 ,截图如下:
vss.png

其中的vss project可能有点困难,属于vss自己的术语,你自己实际上是不知道的,方法是打开vss客户端,通过这个工具查找到你所在的项目的vss project到底是什么,右键项目名称,出现如下dialog
vss2.png

OK了

在IDEA里看看效果,截图如下:
vss3.png


Fine.
posted @ 2006-09-21 11:40 Alex 阅读(2994) | 评论 (3)编辑 收藏

key words :Oracle分页 视图

google了一下关于Oracle的分页方法,方法还不少,大多数效果差不多-有点恶心. 恶心也要作,不过后来就是大家都用得这种方式在我这里出现了新问题,奇怪的是怎么没有别人碰到?

String condition = " teacher_id = " + userId + " and school_id="+siteId;
sql 
=
   
" select * " +
   
" from your_table where " + condition +
   
" and rowid not in ( select rowid from your_table where" + condition +
   
" and rownum <= " + (pageIndex - 1* Constants.PAGE_NUMS + "" +
   
" and rownum <= " + Constants.PAGE_NUMS ;

现在的问题是我需要按照table的某个字段排序,于是改成如下:
String condition = " teacher_id = " + userId + " and school_id="+siteId;
sql 
=
    
" select * " +
    
" from your_table where " + condition +
    
" and rowid not in ( select rowid from your_table where" + condition +
    
" and rownum <= " + (pageIndex - 1* Constants.PAGE_NUMS + "order by id desc" +
    
" and rownum <= " + Constants.PAGE_NUMS + " order by id desc";

这个sql有问题么?
答案是可能有问题,也可能没有问题,因为据说在8i的Oracle版本之前都不行,实际上也不尽然,在我的9i和10g我得到的是同样的错误 "missing right parenthesis",还有一位兄弟估计是DBA建议我去metalink打一个patch,埃,动作太大了,不敢动。

问题还是要解决,试了下类似于select a.*,rownum r from (select * from table where ...) a where rownum < 10 等的方法,效果一样,就是不能加嵌套的order by
最后,用视图的方法间接解决问题,因为我要解决的问题实际就是按某个字段排序,那么在视图里先对table进行排序,再在视图的基础上作操作就OK了.

另,还有一种不错的实现方法,即用OracleCachedRowSet,分页也比较简单,有点类似于hibernate,由于时间关系没有时间去看,感兴趣的朋友可以看一下.


BTW: 对于视图可能rowid有问题,可以改成视图的某个主键替换

String condition = " teacher_id = " + userId + " and school_id="+siteId;
sql 
=
    
" select * " +
    
" from your_table where " + condition +
    
" and id not in ( select id from your_table where" + condition +
    
" and rownum <= " + (pageIndex - 1* Constants.PAGE_NUMS + "order by id desc) " +
    
" and rownum <= " + Constants.PAGE_NUMS + " order by id desc";



posted @ 2006-09-20 15:36 Alex 阅读(1677) | 评论 (3)编辑 收藏

转自 robin

Java本身是一种设计的非常简单,非常精巧的语言,所以Java背后的原理也很简单,归结起来就是两点:

1、JVM的内存管理

理解了这一点,所有和对象相关的问题统统都能解决

2、JVM Class Loader

理解了这一点,所有和Java相关的配置问题,包括各种App Server的配置,应用的发布问题统统都能解决

就像张无忌学太极剑,本质就是一圈一圈的画圆,你要是懂得了太极剑的本质,那么太极剑就那么一招而已,本身是很容易学的,只是难度在于你要能够举一 反三,化一式剑意为无穷无尽的剑招,这就需要一点悟性和不断的实践了;反过来说,如果学剑不学本质,光学剑招,你就是学会了1万招,碰到了第1万零1招, 还是不会招架,败下阵来。

技术世界本来就是丰富多彩,企图统一标准,实际上也做不到,但是世界本质其实并不复杂。学习技术,特别是某种具体的软件工具的时候,应该学会迅速把 握事物的本质,不要过多搅缠细节。软件工具应该为我所用,而不是我被工具所驾驭。当你具备了对整个J2EE架构的设计和实施的能力,你还会被具体的工具束 缚吗?哪种工具适合你的架构,你就用什么,哪种不适合你,你就抛弃它,软件皆臣服于你的脚下,而不是你被什么软件牵着鼻子走,到了这种程度,你难道还害怕 学习什么新的软件?

我自己也在一直朝着这个方向努力,在我心中,设计软件,架构是第一位的,采用什么技术要为架构服务。如果我发现什么技术对我的架构来说很重要,那么 我会花时间去学习,去钻研,就像我花时间去钻研ORM一样,如果我觉得什么技术对我的架构来说没有用,即使技术再火爆,我也不去碰它

总之要学会抓住本质,驾驭技术,而不是被技术所驾驭。当你掌握了本质原理,其实学什么都很快,毕竟都是相通的,我先看JDO,后看 Hibernate,其实两者就很类似,所以学得很快,以后如果有工作需要,要我学习别的ORM,那我也不会觉得有什么困难的,一样手到拿来。

更有说服力的是Unix类的操作系统,那就更相似了,只要抓住了Unix最本质的几点,例如shell命令和编程,文件系统结构和配置,系统启动原 理和过程,所有的Unix都是无师自通的。我自己会用Linux,FreeBSD,SCO Unix, Solaris,HP-UX 和 AIX等6种Unix,更体会到一通百通的道理。

拿刚出了光明顶密道的张无忌来说吧,(我很喜欢张无忌这个角色),他也没有练过什么武功,但是他已经把天下武学之本质:九阳神功 + 乾坤大挪移学会了,所以不管什么功夫,他都是看一遍就会,马上为我所用,看了空性用了一遍龙爪手,就会用龙爪手来破对方;和昆仑派打了一架,就会用昆仑剑 法和灭绝师太过招;七伤拳更是无师自通;太极拳也是看一遍就会。

总之,学习方法还是很重要,别被五花八门的技术给搞不清学习方向了。

posted @ 2006-09-20 12:12 Alex 阅读(444) | 评论 (1)编辑 收藏

转自portian

基本上一个应用程序里面的领域相关的模型里面需要3种对象:
1。值对象(Value Object),没有身份,内容表示一切,譬如我和weihello都去银行里面存取100大洋,那这个100RMB是一个值对象

2。实体对象(Entity),需要持久,不是按照内容,而是按照它的身份来区分,也就是说即使内容完全一样,也不是同一个对象。这个身份在内存 里面是它的实例地址,在数据库里面是关键字,最常见的就是OID.这个实体对象并不是纯数据,它处理本身的实体模型,例如Accout,它的 withDraw,它的子Account等等,它也处理自己和其他实体对象之间的关系,例如订单里面的订单行,都是应该在这个Account里面实现的, 而不应该有一个什么控制类。在一个Web应用程序里面,涉及到对象关系的一般只需要一个(或几个)DTOFactory负责所有对象的DTO和 Entity之间的组装和拆份,不需要专门的管理,这一部分也是和数据建模最相近的地方。
 
3。服务对象(Service),这是为我们提供服务的类,譬如银行里面服务员,她帮助我们把钱从一个账户转到另外一个账户,并记录相应的交易。

对象的作用是对它自己的内部状态负责,如果它需要存取很多其它对象的状态进行运算,那叫做特性忌妒,是要重构的。应该把这些代码移到那个持有这些状态的类里面



辨别一些名词:
1。VO:实际上很模糊,通常指ValueObject和ViewObject
2. ViewObject,界面展现需要的对象,如Struts的FormBean
3。Value Object,早期被作为ValueObject和Transfer Object的总称。实际上Value Object的真正意义在于它的内容,而不是身份
4。Transfer Object:数据传输对象,在应用程序不同层次之间传书对象,在一个分布式应用程序中,通常可以提高整体的性能
5。PO:也许就是Persistent Object,基本上就是Entity了
在不同的体系结构和实现方式里面,这些对象有可能重复,也有可能不重叠。如果你要做一个对所有的体系都能够方便移植的框架,那么每一种对象都需要 严格区分。例如JDO的PO不能作为TO,应为它不能脱离PM,譬如你可以选择用ViewObject(如Struts的FOrmBean)直接作为 TO,但在tapestry和Webwork里面就不合适了。但在很多时候,能够方便实用是最重要的,不要过度设计就是了。
posted @ 2006-09-20 09:53 Alex 阅读(402) | 评论 (1)编辑 收藏

key words: 如何做设计 设计步骤
转自robin

一。需求分析(抽象Use case + 分析Use case之间的关系)

分析软件需求,以用户的角度来使用软件,找出发生的scenerio,抽象成为一个一个Use Case,分析出Use Case之间的关系,这一步是非常重要的,这一步做好了,设计就成功了一半。Use Case的抽象有一些可以遵循的原则,这里不详细谈。

然后用语言描述每一个Use Case,描述用户使用一个Use Case发生的主事件流以及异常流。

这样就完成了需求分析阶段。

二。概要设计(找出实体 + 分析实体类之间的关系 + 提取控制类 + 画序列图)

接下来做概要设计,针对每个Use Case,读Use Case的描述,看事件流,找出所有的实体类,这也有一些可以遵循的原则,例如找出所有的名词,画表格排除等等方法。

然后分析实体类之间的关系,是包含,聚合还是依赖,是1:1,还是1:n,还是其他....,根据这些关系,就可以得出实体类和别的实体类想关联的属性,然后再找出每个实体类本身重要的属性。

然后再次分析Use Case的事件流,一方面check实体类的设计是否合理,另一方面你可以找出动词,分析对实体类的控制逻辑,这样就可以可以设计出业务控制类,一般你可 以一个实体类一个控制类,也可以业务逻辑相关的实体类由一个Facade Session Bean(非EJB含义)来统一控制,这里面的控制类的颗粒度就由你自己来掌握了。一般来说先可以设计一些细颗粒度的控制类,然后再按照模块,用粗粒度封 装细颗粒度的控制类,提供给Web层一个Facade。

然后你可以画序列图,就是用序列图来表达事件流,在这个过程中,你需要不断回到类图,给控制类添加方法,而序列图就是控制类的方法调用。

至此,你已经在Rose里面完成了概要设计,当然你不可能一次设计完善,会有很多次迭代,因此你不能一开始把类设计的太详细,只抓住主要的属性和方法,特别需要注意的是,是抽象的设计,不要用具体的编程语言来表达类。

三。实施(结合xdoclet和Schema工具自动生成代码)

然后你就可以抛开Rose了,转到Eclipse+Togehter里面,根据那些类,规划一下package层次,然后在Together里面进行类的详细设计,所有需要的属性一一写上,当然你还是不可能一下把所有的属性方法写全,不过没有关系,把重要的写好就行了。

然后类框架已经生成好了,给所有的实体类加上xdoclet,然后生成hbm,然后用Hibernate的ExportScheme生成DDL,运行一遍自动创建好所有的表。这样所有的实体相关类全部做好了。

你现在就集中精力把控制类那些方法里面的代码填写上就OK了,在这个过程,你会发现有些实体类缺属性,没有关系,加上属性,然后写好xdoclet,运行一遍,自动生成hbm,自动创建好表,然后继续写你的方法,也有可能你发现控制类缺方法,那么就加上。

基本上实体类就是getter/setter,和少量的实体相关方法,所有的控制逻辑都写在控制类里面。

最后你的软件就基本写好了,用Eclipse生成好一堆你的testCase运行测试,反复修改,除bug。

看看使用OOAD的设计思路,是多么的爽的事情阿!你只需要把精力放到Use Case的抽象,实体类的关系总结,控制类的归纳。而当你使用Eclipse+Together之后,你所需要写的代码只不过是控制类的方法实现代码,其 他的都已经生成好了。另外可能需要写少量工具类。


posted @ 2006-09-20 09:02 Alex 阅读(923) | 评论 (1)编辑 收藏

注:关于跨域登陆cookie的问题在网上搜索了一下,没看到有java下的示例,这个asp的也可以参照一下,有空再在java下测一下.

key words:单点登陆 SSO 跨域cookie


摘要:当你有一个Cookie组(或叫Cookie字典)使用Domain属性指定域名之后,当你在对该组的成员进行修改或新增的时候,一定要在操 作之后加上Resonse.Cookies(CookieName).Domain属性。如果没有必要,请不要修改已设置Domain的Cookie组.
关键字:
正文:
    Cookie跨域操作看来是个简单的问题,因为只要指定Domain属性为指定网站的根域名就可以了.但是笔者在实际使用过程中却遇到了一些问题,的确值得注意.    环境介绍   cookie在www主域名下创建,并写入Domain属性,如:(为方便调试以下代码皆为asp代码)   Write.asp <%
Response.Cookies(CookieName)("UserName") = "SunBird"
Response.Cookies(CookieName)("Password") = "xyz1234"
Response.Cookies(CookieName).Domain = "xxxx.com"
%>
  上面文件放在www主域名下,同时在同目录下放置一个读取cookie的Read.asp   Read.asp <%
Response.Write Request.Cookies(CookieName)("UserName")
Response.Write Request.Cookies(CookieName)("Password")
%>
  再放一个Read.asp文件到另外一个子域名站点里,代码同上。最后我们再做一个清除cookie的Clear.asp放在主域名下   Clear.asp <%
Response.Cookies(CookieName)("UserName") = ""
Response.Cookies(CookieName)("Password") = ""
Response.Cookies(CookieName).Domain = "xxxx.com"
%>
  现在可以通过下面的执行顺序来测试,Write.asp-->主域名的Read.asp-->子域名的Read.asp 所有 Read.asp页面都可以读取到Write.asp创建的cookie的值,然后再运行Clear.asp进行清除,一切都Ok,看上去没有什么问题。   但是把这种方法运用到实际的站点时却出现问题了。   问题描述:   第一次登录一切ok,所有子域名都可以访问到主域名存储的cookie,但是,一旦退出之后,子域名的cookie被清除了,但是主域名的 cookie仍然保留着,强行清除主域名的cookie之后,无论怎样登录主域名下都无法保存cookie了,除非关掉浏览器重新打开。   经过多次尝试之后,无意中发现问题所在,以下是测试经过。   创建一个Write2.asp的页面放在主域名下 <%
Response.Cookies(CookieName)("TEST_COOKIE") = "TEST_COOKIE"
%>
  第一步:关闭浏览器后,按以下顺序执行,Write.asp-->主域名的Read.asp-->子域名的Read.asp 到这里所有Read.asp读取正常。   第二步:Clear.asp-->主域名的Read.asp-->子域名的Read.asp 到这里清除操作是成功的。   第三步:Write.asp--> Write2.asp --> 主域名Read.asp --> 子域名Read.asp 到这里两个Read.asp都可以读取到cookie的值。   第四步:重新执行第二步,发现主域名Read.asp仍然输出了值,而子域名下的Read.asp的值已经被清空了。   根据以上测试总结以下几点再跨域使用cookie时需要注意的地方   1、当你有一个Cookie组(或叫Cookie字典)使用Domain属性指定域名之后,当你在对该组的成员进行修改或新增的时候,一定要在操作之后加上Resonse.Cookies(CookieName).Domain属性。   2、如果没有必要,请不要修改已设置Domain的Cookie组,直接使用Response.Cookies("CookieText") = CookieValue 来创建一个新的Cookie。
posted @ 2006-09-18 20:59 Alex 阅读(2464) | 评论 (0)编辑 收藏

     摘要: 终于翻开这本James都称赞的java经典书籍了,发现比一般的英语书籍要难懂一些。但是里面的Item都是非常实用的,是java程序员应该理解的。 Creating and Destroying ObjectItem 1:考虑用静态工厂方法替代构造器例如:public static Boolean valueOf(boolean b)     {          return (b?Boolean...  阅读全文
posted @ 2006-09-11 18:14 Alex 阅读(671) | 评论 (0)编辑 收藏

从工作流状态机实践中总结状态模式使用心得

banq http://www.jdon.com Dec 7, 2003 12:10 AM 回复此消息回复

状态模式好像是很简单的模式,正因为状态好像是个简单的对象,想复杂化实现设计模式就不是容易,误用情况很多。

我个人曾经设计过一个大型游戏系统的游戏状态机,游戏状态可以说是游戏设计的主要架构,但是由于系统过分复杂
和时间仓促,并没有真正实现状态模式。

目前在实现一个电子政务项目中,需要进行流程状态变化,在电子政务设计中,我发现,如果一开始完全按照工作流
规范开发,难度很大,它和具体项目实践结合无法把握,而且工作流规范现在有wfmc,还有bpml,选择也比较难。因
此,我决定走自创的中间道路。

因为,我需要做一个状态机API,或者说状态机框架,供具体系统调用:类如公文流转应用或信息发报送应用等。

好的状态模式必须做到两点:
1. 状态变化必须从外界其它逻辑划分出来。
2. 状态必须可方便拓展,对其它代码影响非常小。

要做到这两点,必须先明确状态变化机制,状态变化实际是由Event事件驱动的,可以认为是Event-condition-State,
在MVC模式一般是Event-condition-Action实现。状态模式需要封装的是Event-condition-State中的condition-State
部分。

清晰理解状态和流程的关系也非常重要,因为状态不是孤立的,可以说和流程是点和线的关系,状态从一个方面说明
了流程,流程是随时间而改变,状态是截取流程某个时间片。因此,必须明白使用状态模式实现状态机实际是为了更
好地表达和说明流程。

状态和流程以及事件的关系如下:


|Event
___currentState__|______newState___




图中表示了是事件改变了流程的状态,在业务逻辑中,经常发生的是事件,如果不使用状态模式,需要在很多业务逻
辑处实现事件到状态判定和转换,这有很多危险性。

最大的危险是系统没有一个一抓就灵的主体结构,以那个游戏系统为例,在没有状态模式对状态提炼的情况下,状态
改变由每个程序员想当然实现,导致每个程序员开发的功能在整合时就无法调试,因为这个程序员可能不知道那个程
序员的代码在什么运行条件下改变了游戏状态,结果导致自己的代码无法运行。

这种现象实际上拒绝了项目管理的协作性,大大地拖延项目进度(程序员之间要反复商量讨论对方代码设计)。从这
一点也说明,一个好的架构设计是一个项目快速成功完成的基础技术保证,没有这个技术基础,再先进的项目管理手
段也是没有效率的,或者是笨拙的。

状态模式对于很多系统来说,确实是架构组成一个重要部分。

下面继续讨论如何实现一个好的状态模式,为了实现好的状态模式,必须在状态模式中封装下面两个部分:
1. 状态转换规则(行为)
2. 状态属性(属性)

状态转换行为有两种划分标准:
1. run和next两个行为,run是当前状态运行行为,next是指在Event参与下,几种可能转向的下一个状态。
2. stateEnter和stateExit, 状态进入和状态退出。

如果用进入一个个房间来表示状态流程的话, 第一种分析是只重视着“在房间里”和“如何转入下一个房间”,这两种行
为一旦确定,可以被反复使用,进而一个流程的状态切换可以全部表达出来。

第二中分析方法有所区别,只重视进入房间和离开房间这个两个行为,同样,这种模型也可以被反复利用在其它房间,
一个流程的状态切换也可以全部表达出来。

具体选择取决于你的需求,比如,如果你在进入一个状态开始,要做很多初始化工作,那么第二种模型就很适合。

状态变化都离不开一个主体对象,主体对象可以说包含状态变化(行为)和状态属性(属性),假设为StateOwner,
StateOwner有两个部分组成:Task/事情和状态。任何一个Task/事情都会对应一个状态。

这样,我们已经抽象出两个主要对象:状态State和StateOwner。

为了封装状态变化细节,我们可以抽象出一个状态机StateMachine来专门实现状态根据事情实现转换。

这样,客户端外界通过状态机可访问状态模式这个匣子。在实践中,外界客户端需要和状态机实现数据交换,我们把
它也分为两种:属性和行为。

其中属性可能需要外界告诉状态状态变化的主体对象Task,解决状态的主人问题,是谁的问题;行为可能是需要持久
化当前这个主体对象的状态到数据库。

这两种数据交换可以分别通过StateOwner和StateMachine与整个状态机实现数据交换,这样,具体状态和状态切换也
和外界实现了解耦隔离。

因此好的状态模式实现必须有下列步骤:
(1)将每个状态变成State的子类,实现每个状态对象化。
(2)在每个状态中,封装着进入下一个状态可能规则,这些规则是状态变化的核心,换句话说,统一了状态转换的规则。
具体可采取run和next这样的转换行为。

下面是一个子状态代码:


publicclass Running extends StateT{

//
publicvoid run(StateOwner stateOwner){
stateOwner.setCurrentState(this);
}

//转换到下一个状态的规则
//当前是Running状态,下一个状态可能是暂停、结束或者强制退出等
//状态,但是绝对不会是Not_Started这样的状态
//转换规则在这里得到了体现。
public State next(Event e) {
if(transitions == null){
addEventState(new EventImp(
"PAUSE"), new Suspended());
addEventState(new EventImp(
"END"), new Completed());
addEventState(new EventImp(
"STOP"), new Aborted());
}
returnsuper.next(e);
}

}



外界直接调用 StateMachine的关键方法transition;实行状态的自动转变。


publicclass StateMachine {

/**
* 状态切换
* 根据Event参数,运行相应的状态。
* 1. 获得当前状态
* 2. 使用当前状态的next()转换
* |Event
* ___currentState__|______newState___
*
* @param inputs
*/

publicfinalvoid transition(String taskid, Event e) throws Exception {
State currentState = readCurrentState(taskid);
//从数据库获得当前状态
StateOwner stateOwner = new StateOwner(taskid, currentState);
//转换状态
currentState = currentState.next(e);
if (currentState != null) {
currentState.run(stateOwner);
saveCurrentState(stateOwner);
//保存当前状态
}
}

}
posted @ 2006-09-09 10:43 Alex 阅读(967) | 评论 (0)编辑 收藏

设计模式之State

板桥里人 http://www.jdon.com 2002/4/6/

模式实战书籍《Java实用系统开发指南》

State模式的定义 : 不同的状态,不同的行为;或者说,每个状态有着相应的行为.

何时使用 ?
State模式在实际使用中比较多,适合"状态的切换".因为我们经常会使用If elseif else 进行状态切换, 如果针对状态的这样判断切换反复出现,我们就要联想到是否可以采取State模式了.

不 只是根据状态,也有根据属性.如果某个对象的属性不同,对象的行为就不一样,这点在数据库系统中出现频率比较高,我们经常会在一个数据表的尾部,加上 property属性含义的字段,用以标识记录中一些特殊性质的记录,这种属性的改变(切换)又是随时可能发生的,就有可能要使用State.

是否使用?
在实际使用,类似开关一样的状态切换是很多的,但有时并不是那么明显,取决于你的经验和对系统的理解深度.

这里要阐述的是"开关切换状态" 和" 一般的状态判断"是有一些区别的, " 一般的状态判断"也是有 if..elseif结构,例如:

    if (which==1) state="hello";
    else if (which==2) state="hi";
    else if (which==3) state="bye";

这是一个 " 一般的状态判断",state值的不同是根据which变量来决定的,which和state没有关系.如果改成:

    if (state.euqals("bye")) state="hello";
    else if (state.euqals("hello")) state="hi";
    else if (state.euqals("hi")) state="bye";

这就是 "开关切换状态",是将state的状态从"hello"切换到"hi",再切换到""bye";在切换到"hello",好象一个旋转开关,这种状态改变就可以使用State模式了.

如 果单纯有上面一种将"hello"-->"hi"-->"bye"-->"hello"这一个方向切换,也不一定需要使用State模 式,因为State模式会建立很多子类,复杂化,但是如果又发生另外一个行为:将上面的切换方向反过来切换,或者需要任意切换,就需要State了.

请看下例:

public class Context{

  private Color state=null;

  public void push(){

    //如果当前red状态 就切换到blue
    if (state==Color.red) state=Color.blue;

    //如果当前blue状态 就切换到green
    else if (state==Color.blue) state=Color.green;

    //如果当前black状态 就切换到red
    else if (state==Color.black) state=Color.red;

    //如果当前green状态 就切换到black
    else if (state==Color.green) state=Color.black;
    
    Sample sample=new Sample(state);
    sample.operate();
  }

  public void pull(){

    //与push状态切换正好相反

    if (state==Color.green) state=Color.blue;
    else if (state==Color.black) state=Color.green;
    else if (state==Color.blue) state=Color.red;
    else if (state==Color.red) state=Color.black;

    Sample2 sample2=new Sample2(state);
    sample2.operate();
  }

}

在上例中,我们有两个动作push推和pull拉,这两个开关动作,改变了Context颜色,至此,我们就需要使用State模式优化它.

另外注意:但就上例,state的变化,只是简单的颜色赋值,这个具体行为是很简单的,State适合巨大的具体行为,因此在,就本例,实际使用中也不一定非要使用State模式,这会增加子类的数目,简单的变复杂.

例如: 银行帐户, 经常会在Open 状态和Close状态间转换.

例如: 经典的TcpConnection, Tcp的状态有创建 侦听 关闭三个,并且反复转换,其创建 侦听 关闭的具体行为不是简单一两句就能完成的,适合使用State

例如:信箱POP帐号, 会有四种状态, start HaveUsername Authorized quit,每个状态对应的行为应该是比较大的.适合使用State

例如:在工具箱挑选不同工具,可以看成在不同工具中切换,适合使用State.如 具体绘图程序,用户可以选择不同工具绘制方框 直线 曲线,这种状态切换可以使用State.

如何使用
State需要两种类型实体参与:

1.state manager 状态管理器 ,就是开关 ,如上面例子的Context实际就是一个state manager, 在state manager中有对状态的切换动作.
2.用抽象类或接口实现的父类,,不同状态就是继承这个父类的不同子类.

以上面的Context为例.我们要修改它,建立两个类型的实体.
第一步: 首先建立一个父类:

public abstract class State{

  public abstract void handlepush(Context c);
  public abstract void handlepull(Context c);
  public abstract void getcolor();

}

父类中的方法要对应state manager中的开关行为,在state manager中 本例就是Context中,有两个开关动作push推和pull拉.那么在状态父类中就要有具体处理这两个动作:handlepush() handlepull(); 同时还需要一个获取push或pull结果的方法getcolor()

下面是具体子类的实现:

public class BlueState extends State{

  public void handlepush(Context c){
     //根据push方法"如果是blue状态的切换到green" ;
     c.setState(new GreenState());

  }
  public void handlepull(Context c){

     //根据pull方法"如果是blue状态的切换到red" ;
    c.setState(new RedState());

  }

  public abstract void getcolor(){ return (Color.blue)}

}

 

同样 其他状态的子类实现如blue一样.

第二步: 要重新改写State manager 也就是本例的Context:

public class Context{

  private Sate state=null; //我们将原来的 Color state 改成了新建的State state;

  //setState是用来改变state的状态 使用setState实现状态的切换
  pulic void setState(State state){

    this.state=state;

  }

  public void push(){

    //状态的切换的细节部分,在本例中是颜色的变化,已经封装在子类的handlepush中实现,这里无需关心
    state.handlepush(this);
    
    //因为sample要使用state中的一个切换结果,使用getColor()
    Sample sample=new Sample(state.getColor());
    sample.operate();

  }

 

  public void pull(){

    state.handlepull(this);
    
    Sample2 sample2=new Sample2(state.getColor());
    sample2.operate();

  }

}

 

至此,我们也就实现了State的refactorying过程.

以上只是相当简单的一个实例,在实际应用中,handlepush或handelpull的处理是复杂的.

状态模式优点:
(1) 封装转换过程,也就是转换规则
(2) 枚举可能的状态,因此,需要事先确定状态种类。

状态模式可以允许客户端改变状态的转换行为,而状态机则是能够自动改变状态,状态机是一个比较独立的而且复杂的机制,具体可参考一个状态机开源项目:http://sourceforge.net/projects/smframework/

状态模式在工作流或游戏等各种系统中有大量使用,甚至是这些系统的核心功能设计,例如政府OA中,一个批文的状态有多种:未办;正在办理;正在批示;正在审核;已经完成等各种状态,使用状态机可以封装这个状态的变化规则,从而达到扩充状态时,不必涉及到状态的使用者。

在网络游戏中,一个游戏活动存在开始;开玩;正在玩;输赢等各种状态,使用状态模式就可以实现游戏状态的总控,而游戏状态决定了游戏的各个方面,使用状态模式可以对整个游戏架构功能实现起到决定的主导作用。

状态模式实质
使用状态模式前,客户端外界需要介入改变状态,而状态改变的实现是琐碎或复杂的。

使用状态模式后,客户端外界可以直接使用事件Event实现,根本不必关心该事件导致如何状态变化,这些是由状态机等内部实现。

这是一种Event-condition-State,状态模式封装了condition-State部分。

每个状态形成一个子类,每个状态只关心它的下一个可能状态,从而无形中形成了状态转换的规则。如果新的状态加入,只涉及它的前一个状态修改和定义。

状态转换有几个方法实现:一个在每个状态实现next(),指定下一个状态;还有一种方法,设定一个StateOwner,在StateOwner设定stateEnter状态进入和stateExit状态退出行为。

状态从一个方面说明了流程,流程是随时间而改变,状态是截取流程某个时间片。


相关文章:

从工作流状态机实践中总结状态模式使用心得

参考资源:
the State and Stategy
How to implement state-dependent behavior
The state patterns

 

posted @ 2006-09-09 10:42 Alex 阅读(632) | 评论 (0)编辑 收藏

转自

.Net中的设计模式——Composite模式

前言:google到一篇关于复合模式的文章,虽然是关于 .NET的,但是对于开发java同样有借鉴意义.

一、模式概述

描述Composite模式的最佳方式莫过于树形图。从抽象类或接口为根节点开始,然后生枝发芽,以形成树枝节点和叶结点。因此, Composite模式通常用来描述部分与整体之间的关系,而通过根节点对该结构的抽象,使得客户端可以将单元素节点与复合元素节点作为相同的对象来看 待。

由于Composite模式模糊了单元素和复合元素的区别,就使得我们为这些元素提供相关的操作时,可以有一个统一的接口。例如,我们要编写一个字 处理软件,该软件能够处理文字,对文章进行排版、预览、打印等功能。那么,这个工具需要处理的对象,就应该包括单个的文字、以及由文字组成的段落,乃至整 篇文档。这些对象从软件处理的角度来看,对外的接口应该是一致的,例如改变文字的字体,改变文字的位置使其居中或者右对齐,也可以显示对象的内容,或者打 印。而从内部实现来看,我们对段落或者文档进行操作,实质上也是对文字进行操作。从结构来看,段落包含了文字,文档又包含了段落,是一个典型的树形结构。 而其根节点正是我们可以抽象出来暴露在外的统一接口,例如接口IElement:

composite1.GIF

既然文字、段落、文档都具有这些操作,因此它们都可以实现IElement接口:

从上图可以看到,对象Word、Paragraph、Document均实现了IElement接口,但Paragraph和Document与 Word对象不同的是,这两者处除了实现了IElement接口,它们还与IElement接口对象之间具有聚合的关系,且是一对多。也就是说 Paragraph与Document对象内可以包含0个到多个IElement对象,这也是与前面对字处理软件分析获得的结果是一致的。

从整个结构来看,完全符合树形结构的各个要素,接口IElement是根节点,而Paragraph和Document类为枝节点,Word对象为 叶节点。既然作为枝节点,它就具有带叶节点的能力,从上图的聚合关系中我们体现出了这一点。也就是说,Paragraph和Document类除了具有排 版、打印方面的职责外,还能够添加、删除叶节点的操作。那么这些操作应该放在哪里呢?

管理对子节点的管理,Composite模式提供了两种方式:一个是透明方式,也就是说在根节点中声明所有用来管理子元素的方法,包括Add()、 Remove()等方法。这样一来,实现根节点接口的子节点同时也具备了管理子元素的能力。这种实现策略,最大的好处就是完全消除了叶节点和枝节点对象在 抽象层次的区别,它们具备完全一致的接口。而缺点则是不够安全。由于叶节点本身不具备管理子元素的能力,因此提供的Add()、Remove()方法在实 现层次是无意义的。但客户端调用时,却看不到这一点,从而导致在运行期间有出错的可能。

另一种策略则是安全方式。与透明方式刚好相反,它只在枝节点对象里声明管理子元素的方法由于叶节点不具备这些方法,当客户端在操作叶节点时,就不会出现前一种方式的安全错误。然而,这种实现方式,却导致了叶节点和枝节点接口的不完全一致,这给客户端操作时带来了不便。

这两种方式各有优缺点,我们在实现时,应根据具体的情况,作出更加合理的抉择。在字处理软件一例中,我选择了安全方式来实现,因为对于客户端而言, 在调用IElement接口时,通常是将其视为可被排版、打印等操作的对象。至于为Paragraph和Document对象添加、删除子对象,往往是一 种初始化的行为,完全可以放到一个单独的模块中。根据单一职责原则(SRP),我们没有必要让IElement接口负累太重。所以,我们需要对上图作稍许 的修改,在Paragraph和Document对象中增加Add()和Remove()方法:

以下是IElement对象结构的实现代码:
public interface IElement
{
      void ChangeFont(Font font);
      void Show();
      //其他方法略;
}
public class Word
{
 public void ChangeFont(Font font)
 {
      this.font = font;
 }
 public void Show()
 {
      Console.WriteLine(this.ToString());
 }
 //其他方法略;
}
public class Paragraph
{
 private ArrayList elements = new ArrayList();
 public void Add(IElement element)
 {
      elements.Add(element);
 }
 public void Remove(IElement element)
 {
      elements.Remove(element);
 }
 public void ChangeFont(Font font)
 {
      foreach (IElement element in elements)
      {
          element.ChangeFont(font);
  }
 }
 public void Show()
 {
      foreach (IElement element in elements)
      {
          element.Show(font);
  }
 }
 //其他方法略;
}
//Document类略;

实际上,我们在为叶节点实现Add(),Remove()方法时,还需要考虑一些异常情况。例如在Paragraph类中,添加的子元素就不能是 Document对象和Paragraph对象。所以在添加IElement对象时,还需要做一些条件判断,以确定添加行为是否正确,如果错误,应抛出异 常。

采用Composite模式,我们将Word、Paragraph、Document抽象为IElement接口。虽然各自内部的实现并不相同,枝 节点和叶节点的实质也不一样,但对于调用者而言,是没有区别的。例如在类WordProcessor中,包含一个GetSelectedElement ()静态方法,它能够获得当前选择的对象:
public class WordProcessor
{
 public static IElement GetSelectedElement(){……}
}

对于字处理软件的UI来说,如果要改变选中对象的字体,则可以在命令按钮cmdChangeFont的Click事件中写下如下代码:
public void cmdChangeFont_Click(object sender, EventArgs e)
{
    WordProcessor.GetSelectedElement().ChangeFont(currentFont);
}

不管当前选中的对象是文字、段落还是整篇文档,对于UI而言,操作都是完全一致的,根本不需要去判断对象的类别。因此,如果在Business Layer的类库设计时,采用Composite模式,将极大地简化UI表示层的开发工作。此外,应用该模式也较好的支持项目的可扩展性。例如,我们为 IElement接口增加了Sentence类,对于前面的例子而言,只需要修改GetSelectedElement()方法,而 cmdChangeFont命令按钮的Click事件以及Business Layer类库原有的设计,都不需要做任何改变。这也符合OO的开放-封闭原则(OCP),即对于扩展是开放的(Open for extension),对于更改则是封闭的(Closed for modification)。

二、.Net Framework中的Composite模式

在.Net中,最能体现Composite模式的莫过于Windows或Web的控件。在这些控件中,有的包含子控件,有的则不包含且不能包含子控 件,这正好符合叶节点和枝节点的含义。所有Web控件的基类为System.Web.UI.Contril类(如果是Windows控件,则基类为 System.Windows.Forms.Control类)。其子类包含有HtmlControl、HtmlContainerControl等。按 照Composite模式的结构,枝节点和叶节点属于根节点的不同分支,同时枝节点与根节点之间应具备一个聚合关系,可以通过Add()、Remove ()方法添加和移除其子节点。设定HtmlControl为叶节点,而HtmlContaiinerControl为枝节点,那么采用透明方式的设计方 法,在.Net中控件类的结构,就应该如下图所示:

虽然根据透明方式的Composite模式,HtmlControl类与其父类Control之间也应具备一个聚合关系,但实质上该类并不具备管理 子控件的职责,因此我在类图中忽略了这个关系。此时,HtmlControl类中的Add()、Remove()方法,应该为空,或者抛出一个客户端能够 捕获的异常。

然而,从具体实现来考虑,由于HtmlControl类和HtmlContainerControl类在实现细节层次,区别仅在于前者不支持子控 件,但从控件本身的功能来看,很多行为是相同或者相近的。例如HtmlControl类的Render()方法,调用了方法RenderBeginTag ()方法:
protected override void Render(HtmlTextWriter writer)
{
      this.RenderBeginTag(writer);
}
protected virtual void RenderBeginTag(HtmlTextWriter writer)
{
      writer.WriteBeginTag(this.TagName);
      this.RenderAttributes(writer);
      writer.Write(’>');
}

而HtmlContainerControl类也具有Render()方法,在这个方法中也调用了RenderBeginTag()方法,且RenderBeginTag方法的实现和前者完全一致:
protected override void Render(HtmlTextWriter writer)
{
      this.RenderBeginTag(writer);
      this.RenderChildren(writer);
      this.RenderEndTag(writer);
}

按照上面的结构,由于HtmlControl和HtmlContainerControl之间并无继承关系,这就要求两个类中,都要重复实现 RenderBeginTag()方法,从而导致产生重复代码。根据OO的特点,解决的办法,就是让HtmlContainerControl继承自 HtmlControl类(因为HtmlContainerControl的接口比HtmlControl宽,所以只能令 HtmlContainerControl作为子类),并让RenderBeginTag()方法成为HtmlControl类的protected方 法,子类HtmlContainerControl可以直接调用这个方法。然而与之矛盾的是,HtmlContainerControl却是一个可以包含 子控件的枝节点,而HtmlControl则是不能包含子控件的叶节点,那么这样的继承关系还成立吗?

HtmlControl类对Add()方法和Remove()方法的重写后,这两个方法内容为空。由于HtmlContainerControl类 继承HtmlControl类,但我们又要求它的Add()和Remove()方法和Control类保持一致,而父类HtmlControl已经重写这 两个方法,此时是无法直接继承来自父类的方法的。以上是采用透明方式的设计。

如果采用安全方式,仍然有问题。虽然在HtmlControl类中不再有Add()和Remove()方法,但由于Control类和 HtmlContainerControl类都允许添加子控件,它们包含的Add()、Remove()方法,只能分别实现。这样的设计必然会导致重复代 码。这也是与我们的期望不符的。

那么在.Net中,Control类究竟是怎样实现的呢?下面,我将根据.Net实现Control控件的源代码,来分析Control控件的真实结构,以及其具体的实现细节。

三、深入分析.Net中的Composite模式

首先,我们来剖析Web控件的基类Control类的内部实现:

public class Control : IComponent, IDisposable, IParserAccessor, IDataBindingsAccessor
{
       // Events;略  
       // Methods
 public Control()
 {
             if (this is INamingContainer)
             {
                    this.flags[0×80] = true;
             }
 }
 public virtual bool HasControls()
 {
              if (this._controls != null)
              {
                    return (this._controls.Count > 0);
              }
              return false;
 }
 public virtual void DataBind()
        {
              this.OnDataBinding(EventArgs.Empty);   
              if (this._controls != null)           
       {                
              string text1 = this._controls.SetCollectionReadOnly(”Parent_collections_readonly”);
                        int num1 = this._controls.Count;
                 for (int num2 = 0; num2 < num1; num2++)                 
   {
                         this._controls[num2].DataBind();
                   }
                   this._controls.SetCollectionReadOnly(text1);
              }
 }
        protected virtual void Render(HtmlTextWriter writer)
 {
              this.RenderChildren(writer);
 }
 protected virtual ControlCollection CreateControlCollection()
 {
              return new ControlCollection(this);
 }
       // Properties
 public virtual ControlCollection Controls
 {
             get
             {
                   if (this._controls == null)
                   {
                         this._controls = this.CreateControlCollection();
                   }
                   return this._controls;
             }
 }
        // Fields
        private ControlCollection _controls;
}

Control基类中的属性和方法很多,为清晰起见,我只保留了几个与模式有关的关键方法与属性。在上述的源代码中,我们需要注意几点:

1、Control类不是抽象类,而是具体类。这是因为在设计时,我们可能会创建Control类型的实例。根据这一点来看,这并不符合OOP的要 求。一般而言,作为抽象出来的基类,必须定义为接口或抽象类。不过在实际的设计中,也不应拘泥于这些条条框框,而应审时度势,根据实际的情况来抉择最佳的 设计方案。
2、公共属性Controls为ControlCollection类型,且该属性为virtual属性。也就是说,这个属性可以被它的子类 override。同时,该属性为只读属性,在其get访问器中,调用了方法CreateControlCollection();这个方法为 protected虚方法,默认的实现是返回一个ControlCollection实例。
3、方法HasControls(),功能为判断Control对象是否有子控件。它判断的依据是根据私有字段_controls(即公共属性 Controls)的Count值。但是需要注意的是,通过HasControls()方法的返回值,并不能决定对象本身属于叶节点,还是枝节点。因为即 使是枝节点其内部仍然可以不包含任何子对象。
4、 方法DataBind()的实现中,首先调用了自身的OnDataBinding()方法,然后又遍历了Controls中的所有控件,并调用其 DataBind()方法。该方法属于控件的共有行为,从这里可以看出不管是作为叶节点的控件,还是作为枝节点的控件,它们都实现统一的接口。对于客户端 调用而言,枝节点和叶节点是没有区别的。
5、 Control类的完整源代码中,并不存在Add()、Remove()等类似的方法,以提供添加和移除子控件的功能。事实上,继承Control类的所有子类均不存在Add()、Remove()等方法。

显然,在Control类的定义和实现中,值得我们重视的是公共属性Controls的类型ControlCollection。顾名思义,该类必 然是一个集合类型。是否有关子控件的操作,都是在ControlCollection类型中实现呢?我们来分析一下ControlCollection的 代码:
public class ControlCollection : ICollection, IEnumerable
{
       // Methods
 public ControlCollection(Control owner)
 {
               this._readOnlyErrorMsg = null;
               if (owner == null)
               {
                       throw new ArgumentNullException("owner");
               }
               this._owner = owner;
        }
 public virtual void Add(Control child)
        {
               if (child == null)
               {
                throw new ArgumentNullException("child");
               }
               if (this._readOnlyErrorMsg != null)
                   {
                throw new HttpException(HttpRuntime.FormatResourceString(this._readOnlyErrorMsg));
               }
               if (this._controls == null)
               {
                   this._controls = new Control[5];
               }
               else if (this._size >= this._controls.Length)
               {
                   Control[] controlArray1 = new Control[this._controls.Length * 4];
                   Array.Copy(this._controls, controlArray1, this._controls.Length);
                   this._controls = controlArray1;
               }
               int num1 = this._size;
               this._controls[num1] = child;
               this._size++;
               this._version++;
               this._owner.AddedControl(child, num1);
        }
        public virtual void Remove(Control value)
 {
               int num1 = this.IndexOf(value);
               if (num1 >= 0)
               {
                this.RemoveAt(num1);
               }
        }
        // Indexer
 public virtual Control this[int index]
 {
               get
               {
                    if ((index < 0) || (index >= this._size))
                    {
                         throw new ArgumentOutOfRangeException(”index”);
                    }
                    return this._controls[index];
               }
 }
 // Properties    
 public int Count
 {
        get
               {
                    return this._size;
               }
        }
 protected Control Owner
 {
               get
               {
                    return this._owner;
               }
 }
        protected Control Owner { get; }   
        // Fields
        private Control[] _controls;
        private const int _defaultCapacity = 5;
        private const int _growthFactor = 4;
 private Control _owner;    
}

一目了然,正是ControlCollection的Add()、Remove()方法完成了对子控件的添加和删除。例如:
Control parent = new Control();
Control child = new Child();
//添加子控件child;
parent.Controls.Add(child);
//移除子控件child;
parent.Controls.Remove(child);

为什么要专门提供ControlCollection类型来管理控件的子控件呢?首先,作为类库使用者,自然希望各种类型的控件具有统一的接口,尤 其是自定义控件的时候,不希望自己重复定义管理子控件的操作;那么采用透明方式自然是最佳方案。然而,在使用控件的时候,安全也是需要重点考虑的,如果不 考虑子控件管理的合法性,一旦使用错误,会导致整个应用程序出现致命错误。从这样的角度考虑,似乎又应采用安全方式。这里就存在一个抉择。故而,.Net 在实现Control类库时,利用了职责分离的原则,将控件对象管理子控件的属性与行为和控件本身分离,并交由单独的ControlCollection 类负责。同时采用聚合而非继承的方式,以一个公共属性Controls,存在于Control类中。这种方式,集保留了透明方式和安全方式的优势,又摒弃 了这两种方式固有的缺陷,因此我名其为“复合方式”。

“复合方式”的设计,其对安全的保障,不仅仅是去除了Control类关于子控件管理的统一接口,同时还通过异常管理的方式,在ControlCollection类的子类中实现:
public class EmptyControlCollection : ControlCollection
{
        // Methods
 public EmptyControlCollection(Control owner) : base(owner)
 {}
 public override void Add(Control child)
 {
            this.ThrowNotSupportedException();
 }                         
        private void ThrowNotSupportedException()
        {
            throw new HttpException(HttpRuntime.FormatResourceString(”Control_does_not_allow_children”, base.Owner.GetType().ToString()));
        }
}

EmptyControlCollection继承了ControlCollection类,并重写了Add()等添加子控件的方法,使其抛出一个 异常。注意,它并没有重写父类的Remove()方法,这是因为ControlCollection类在实现Remove()方法时,对集合内的数据进行 了非空判断。而在EmptyControlCollection类中,是不可能添加子控件的,直接调用父类的Remove()方法,是不会出现错误的。

既然管理子控件的职责由ControlCollection类型负责,且Control类中的公共属性Controls即为 ControlCollection类型。所以,对于控件而言,如果是树形结构中的叶节点,它不能包含子控件,它的Controls属性就应为 EmptyControlCollection类型,假如用户调用了Controls的Add()方法,就会抛出异常。如果控件是树形结构中的枝节点,它 支持子控件,那么Controls属性就是ControlCollection类型。究竟是枝节点还是叶节点,决定权在于公共属性Controls:
public virtual ControlCollection Controls
{
      get
      {
             if (this._controls == null)
             {
                   this._controls = this.CreateControlCollection();
             }
             return this._controls;
      }
}

在属性的get访问器中,调用了protected方法CreateControlCollection(),它创建并返回了一个ControlCollection实例:
protected virtual ControlCollection CreateControlCollection()
{
     return new ControlCollection(this);
}

很明显,在Control基类实现Controls属性时,采用了Template Method模式,它推迟了ControlCollection的创建,将决定权交给了CreateControlCollection()方法。

如果我们需要定义一个控件,要求它不能管理子控件,就重写CreateControlCollection()方法,返回EmptyControlCollection对象:

protected override ControlCollection CreateControlCollection()
{
     return new EmptyControlCollection(this);
}
 
现在再回过头来看HtmlControl和HtmlContainerControl类。根据前面的分析,我们要求 HtmlContainerControl继承HtmlControl类,同时,HtmlContainerControl应为枝节点,能够管理子控件; HtmlControl则为叶节点,不支持子控件。通过引入ControlCollection类和其子类 EmptyControlCollection,以及Template Method模式后,这些类之间的关系与结构如下所示:

HtmlContainerControl继承了HtmlControl类,这两个类都重写了自己父类的protected方法 CreateControlCollection()。HtmlControl类,该方法返回EmptyControlCollection对象,使其成 为了不包含子控件的叶节点;HtmlContainerControl类中,该方法则返回ControlCollection对象,从而被赋予了管理子控 件的能力,成为了枝节点:
public abstract class HtmlControl : Control, IAttributeAccessor
{
        // Methods
 protected override ControlCollection CreateControlCollection()
 {
            return new EmptyControlCollection(this);
 }
}
public abstract class HtmlContainerControl : HtmlControl
{
       // Methods
 protected override ControlCollection CreateControlCollection()
 {
            return new ControlCollection(this);
 }
}

HtmlControl和HtmlContainerControl类均为抽象类。要定义它们的子类,如果不重写其父类的 CreateControlCollection()方法,那么它们的Controls属性,就与父类完全一致。例如HtmlImage控件继承自 HtmlControl类,该控件不能添加子控件;而HtmlForm控件则继承自HtmlContainerControl类,显然,HtmlForm 控件是支持添加子控件的操作的。

.Net的控件设计采用Composite模式的“复合方式”,较好地将控件的透明性与安全性结合起来,它的特点是:

1、在统一接口中消除了Add()、Remove()等子控件的管理方法,而由ControlCollection类实现,同时通过EmptyControlCollection类保障了控件进一步的安全;
2、控件能否管理子控件,不由继承的层次决定;而是通过重写CreateControlCollection()方法,由Controls属性的真正类型来决定。

如此一来,要定义自己的控件就更加容易。我们可以任意地扩展自己的控件类。不管继承自Control,还是HtmlControl或 HtmlContainerControl,都可以轻松地定义出具有枝节点或叶节点属性的新控件。如果有新的需求要求改变管理子控件的方式,我们还可以定 义继承自ControlCollection的类,并在控件类的方法CreateControlCollection()中创建并返回它的实例。

posted @ 2006-09-09 00:51 Alex 阅读(462) | 评论 (0)编辑 收藏

设计模式之Composite(组合)

板桥里人 http://www.jdon.com 2002/04/27(转载请保留)

 

Composite模式定义:
将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.

Composite比较容易理解,想到Composite就应该想到树形结构图。组合体内这些对象都有共同接口,当组合体一个对象的方法被调用执行 时,Composite将遍历(Iterator)整个树形结构,寻找同样包含这个方法的对象并实现调用执行。可以用牵一动百来形容。

所以Composite模式使用到Iterator模式,和Chain of Responsibility模式类似。

Composite好处:
1.使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
2.更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。

如何使用Composite?
首先定义一个接口或抽象类,这是设计模式通用方式了,其他设计模式对接口内部定义限制不多,Composite却有个规定,那就是要在接口内部定义一个用于访问和管理Composite组合体的对象们(或称部件Component).

下面的代码是以抽象类定义,一般尽量用接口interface,

public abstract class Equipment
{
  private String name;
  //实价
  public abstract double netPrice();
  //折扣价格
  public abstract double discountPrice();
  //增加部件方法  
  public boolean add(Equipment equipment) { return false; }
  //删除部件方法
  public boolean remove(Equipment equipment) { return false; }
  //注意这里,这里就提供一种用于访问组合体类的部件方法。
  public Iterator iter() { return null; }
  
  public Equipment(final String name) { this.name=name; }
}

抽象类Equipment就是Component定义,代表着组合体类的对象们,Equipment中定义几个共同的方法。

public class Disk extends Equipment
{
  public Disk(String name) { super(name); }
  //定义Disk实价为1
  public double netPrice() { return 1.; }
  //定义了disk折扣价格是0.5 对折。
  public double discountPrice() { return .5; }
}

Disk是组合体内的一个对象,或称一个部件,这个部件是个单独元素( Primitive)。
还有一种可能是,一个部件也是一个组合体,就是说这个部件下面还有'儿子',这是树形结构中通常的情况,应该比较容易理解。现在我们先要定义这个组合体:

abstract class CompositeEquipment extends Equipment
{
  private int i=0;
  //定义一个Vector 用来存放'儿子'
  private Lsit equipment=new ArrayList();

  public CompositeEquipment(String name) { super(name); }

  public boolean add(Equipment equipment) {
     this.equipment.add(equipment);
     return true;
   }


  public double netPrice()
  {
    double netPrice=0.;
    Iterator iter=equipment.iterator();
    for(iter.hasNext())
      netPrice+=((Equipment)iter.next()).netPrice();
    return netPrice;
  }

  public double discountPrice()
  {
    double discountPrice=0.;
    Iterator iter=equipment.iterator();
    for(iter.hasNext())
      discountPrice+=((Equipment)iter.next()).discountPrice();
    return discountPrice;
  }
  

  //注意这里,这里就提供用于访问自己组合体内的部件方法。
  //上面dIsk 之所以没有,是因为Disk是个单独(Primitive)的元素.

  public Iterator iter()
  {
    return equipment.iterator() ;
  {
  //重载Iterator方法
   public boolean hasNext() { return i<equipment.size(); }
  //重载Iterator方法
   public Object next()
   {
    if(hasNext())
       return equipment.elementAt(i++);
    else
        throw new NoSuchElementException();
   }
  

}

上面CompositeEquipment继承了Equipment,同时为自己里面的对象们提供了外部访问的方法,重载了Iterator,Iterator是Java的Collection的一个接口,是Iterator模式的实现.

我们再看看CompositeEquipment的两个具体类:盘盒Chassis和箱子Cabinet,箱子里面可以放很多东西,如底板,电源盒,硬盘盒等;盘盒里面可以放一些小设备,如硬盘 软驱等。无疑这两个都是属于组合体性质的。

public class Chassis extends CompositeEquipment
{
   public Chassis(String name) { super(name); }
   public double netPrice() { return 1.+super.netPrice(); }
   public double discountPrice() { return .5+super.discountPrice(); }
}

public class Cabinet extends CompositeEquipment
{
   public Cabinet(String name) { super(name); }
   public double netPrice() { return 1.+super.netPrice(); }
   public double discountPrice() { return .5+super.discountPrice(); }
}

至此我们完成了整个Composite模式的架构。

我们可以看看客户端调用Composote代码:

Cabinet cabinet=new Cabinet("Tower");

Chassis chassis=new Chassis("PC Chassis");
//将PC Chassis装到Tower中 (将盘盒装到箱子里)
cabinet.add(chassis);
//将一个10GB的硬盘装到 PC Chassis (将硬盘装到盘盒里)
chassis.add(new Disk("10 GB"));

//调用 netPrice()方法;
System.out.println("netPrice="+cabinet.netPrice());
System.out.println("discountPrice="+cabinet.discountPrice());

上面调用的方法netPrice()或discountPrice(),实际上Composite使用Iterator遍历了整个树形结构,寻找同样包含这个方法的对象并实现调用执行.

Composite是个很巧妙体现智慧的模式,在实际应用中,如果碰到树形结构,我们就可以尝试是否可以使用这个模式。

以论坛为例,一个版(forum)中有很多帖子(message),这些帖子有原始贴,有对原始贴的回应贴,是个典型的树形结构,那么当然可以使用Composite模式,那么我们进入Jive中看看,是如何实现的.

Jive解剖
在Jive中 ForumThread是ForumMessages的容器container(组合体).也就是说,ForumThread类似我们上例中的 CompositeEquipment.它和messages的关系如图:
[thread]
   |- [message]
   |- [message]
      |- [message]
      |- [message]
         |- [message]

我们在ForumThread看到如下代码:

public interface ForumThread {
   ....
   public void addMessage(ForumMessage parentMessage, ForumMessage newMessage)
         throws UnauthorizedException;

   public void deleteMessage(ForumMessage message)
         throws UnauthorizedException;

  
   public Iterator messages();
      ....

}

类似CompositeEquipment, 提供用于访问自己组合体内的部件方法: 增加 删除 遍历.

结合我的其他模式中对Jive的分析,我们已经基本大体理解了Jive论坛体系的框架,如果你之前不理解设计模式,而直接去看Jive源代码,你肯定无法看懂。

:)

posted @ 2006-09-09 00:44 Alex 阅读(493) | 评论 (0)编辑 收藏

     摘要: 转自: CCIENET 自从J2EE出现以来,就大大简化了在Java下的企业级开发。但是随着J2EE越来越普遍 地被应用到各个领域中,开发者们渐渐意识到需要一种方法来标准化应用程序的开发过程,他们采用的方法是标准化应用程序的结构层。在结构层通常封装了一些独 立于业务逻辑的复杂技术,以便在业务逻辑和底层的架构之间建立起弱连接。无可否认,J2EE是一个很成功的技术,它为一些基本的任务提供了一...  阅读全文
posted @ 2006-09-08 23:59 Alex 阅读(1966) | 评论 (1)编辑 收藏

     摘要: 引言:最近在看一个开源的聊天室AjaxChat 时看到一个被引用的包:javawebparts,处于好奇去看了一下,突然发现这么好的一个常用web组件不去用实在是太可惜了,下面逐一介绍,详细文档大家可以去官方文档看看,最好看它的Demo,很直观。 javawebparts的口号是:不用重新发明轮子 ! 对这点我是严重支持啊,在我的身边看到N多所谓...  阅读全文
posted @ 2006-09-08 21:18 Alex 阅读(2525) | 评论 (7)编辑 收藏

本文主要谈一下密码学中的加密和数字签名,以及其在java中如何进行使用。对密码学有兴趣的伙伴,推荐看 Bruce Schneier的著作:Applied Crypotography。在jdk1.5的发行版本中安全性方面有了很大的改进,也提供了对RSA算法的直接支持,现在我们从实例入手解决问题(本文 仅是作为简单介绍):

  一、密码学上常用的概念 

  1)消息摘要:

   这是一种与消息认证码结合使用以确保消息完整性的技术。主要使用单向散列函数算法,可用于检验消息的完整性,和通过散列密码直接以文本形式保存等,目前 广泛使用的算法有MD4、MD5、SHA-1,jdk1.5对上面都提供了支持,在java中进行消息摘要很简单, java.security.MessageDigest提供了一个简易的操作方法:

/**
*MessageDigestExample.java
*Copyright 2005-2-16
*/
import java.security.MessageDigest;
/**
*单一的消息摘要算法,不使用密码.可以用来对明文消息(如:密码)隐藏保存
*/
public class MessageDigestExample{
 
public static void main(String[] args) throws Exception{
  
if(args.length!=1){
   System.err.println(
"Usage:java MessageDigestExample text");
   System.exit(
1);
  }

  
byte[] plainText=args[0].getBytes("UTF8");

  
//使用getInstance("算法")来获得消息摘要,这里使用SHA-1的160位算法
  MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");

  System.out.println(
"\n"+messageDigest.getProvider().getInfo());
  
//开始使用算法
  messageDigest.update(plainText);
  System.out.println(
"\nDigest:");
  
//输出算法运算结果
  System.out.println(new String(messageDigest.digest(),"UTF8"));
 }
}

  还可以通过消息认证码来进行加密实现,javax.crypto.Mac提供了一个解决方案,有兴趣者可以参考相关API文档,本文只是简单介绍什么是摘要算法。

这里补充另一个运用消息摘要的方式加密的例子:
public class TestEncrypt {

    
public TestEncrypt() {
    }

    
/**
     * 
@param strSrc  :strSrc is a string will be encrypted,
     * 
@param encName : encName is the algorithm name will be used.
     *                encName dafault to "MD5"
     * 
@return String
     
*/
    
public String Encrypt(String strSrc, String encName) {

        MessageDigest md 
= null;
        String strDes 
= null;

        
byte[] bt = strSrc.getBytes();
        
try {
            
if (encName == null || encName.equals("")) {
                encName 
= "MD5";
            }
            md 
= MessageDigest.getInstance(encName);
            md.update(bt);
            strDes 
= bytes2Hex(md.digest()); //to HexString
        }
        
catch (NoSuchAlgorithmException e) {
            System.out.println(
"Invalid algorithm.");
            
return null;
        }
        
return strDes;
    }

    
public String bytes2Hex(byte[] bts) {
        String des 
= "";
        String tmp 
= null;
        
for (int i = 0; i < bts.length; i++) {
            tmp 
= (Integer.toHexString(bts[i] & 0xFF));
            
if (tmp.length() == 1) {
                des 
+= "0";
            }
            des 
+= tmp;
        }
        
return des;
    }

    
public static void main(String[]args) {
        TestEncrypt te 
= new TestEncrypt();
        String strSrc 
= "可以加密汉字.Oh,and english";
        System.out.println(
"Source String:" + strSrc);
        System.out.println(
"Encrypted String:");
        System.out.println(
"Use Def:" + te.Encrypt(strSrc, null));
        System.out.println(
"Use MD5:" + te.Encrypt(strSrc, "MD5"));
        System.out.println(
"Use SHA:" + te.Encrypt(strSrc, "SHA-1"));
        System.out.println(
"Use SHA-256:" + te.Encrypt(strSrc, "SHA-256"));
    }
}

另外,在javawebparts中的 RequestHelpers里的generateGUID方法也涉及到了MD5的方法,代码如下:
public static String generateGUID(HttpServletRequest request) {

    String out 
= "";
    
try {
      
// Construct a string that is comprised of:
      
// Remote IP Address + Host IP Address + Date (yyyyMMdd) +
      
// Time (hhmmssSSa) + Requested Path + Session ID +
      
// HashCode Of ParameterMap
      StringBuffer sb = new StringBuffer(1024);
      sb.append(request.getRemoteAddr());
      InetAddress ia 
= InetAddress.getLocalHost();
      sb.append(ia.getHostAddress());
      sb.append(
new SimpleDateFormat("yyyyMMddhhmmssSSa").format(new Date()));
      String path 
= request.getServletPath();
      String pathInfo 
= request.getPathInfo();
      
if (pathInfo != null) {
        path 
+= pathInfo;
      }
      sb.append(path);
      sb.append(request.getSession(
false));
      sb.append(request.getParameterMap().hashCode());
      String str 
= sb.toString();
      
// Now encode the string using an MD5 encryption algorithm.
      MessageDigest md = MessageDigest.getInstance("md5");
      md.update(str.getBytes());
      
byte[] digest = md.digest();
      StringBuffer hexStr 
= new StringBuffer(1024);
      
for (int i = 0; i < digest.length; i++) {
        str 
= Integer.toHexString(0xFF & digest[i]);
        
if (str.length() < 2) {
          str 
= "0" + str;
        }
        hexStr.append(str);
      }
      out 
= hexStr.toString();
    } 
catch (NoSuchAlgorithmException nsae) {
      log.error(nsae);
    } 
catch (UnknownHostException uhe) {
      log.error(uhe);
    }
    
// Return the encrypted string.  It should be unique based on the
    
// components that comprise the plain text string, and should always be
    
// 32 characters thanks to the MD5 algorithm.
    return out;

  } 
// End generateGUID().


  2)私钥加密:

  消息摘要只能检查消息的完整性,但是单向的,对明文消息并不能加密,要加密明文的消息的话,就要使用其他的算法,要确保机密性,我们需要使用私钥密码术来交换私有消息。

  这种最好理解,使用对称算法。比如:A用一个密钥对一个文件加密,而B读取这个文件的话,则需要和A一样的密钥,双方共享一个私钥(而在web环境下,私钥在传递时容易被侦听):

   使用私钥加密的话,首先需要一个密钥,可用javax.crypto.KeyGenerator产生一个密钥(java.security.Key), 然后传递给一个加密工具(javax.crypto.Cipher),该工具再使用相应的算法来进行加密,主要对称算法有:DES(实际密钥只用到56 位),AES(支持三种密钥长度:128、192、256位),通常首先128位,其他的还有DESede等,jdk1.5种也提供了对对称算法的支持, 以下例子使用AES算法来加密:

/**
*PrivateExmaple.java
*Copyright 2005-2-16
*/
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import java.security.Key;

/**
*私鈅加密,保证消息机密性
*/
public class PrivateExample{
 
public static void main(String[] args) throws Exception{
  
if(args.length!=1){
   System.err.println(
"Usage:java PrivateExample <text>");
   System.exit(
1);
  }
  
byte[] plainText=args[0].getBytes("UTF8");

  
//通过KeyGenerator形成一个key
  System.out.println("\nStart generate AES key");
  KeyGenerator keyGen
=KeyGenerator.getInstance("AES");
  keyGen.init(
128);
  Key key
=keyGen.generateKey();
  System.out.println(
"Finish generating DES key");

  
//获得一个私鈅加密类Cipher,ECB是加密方式,PKCS5Padding是填充方法
  Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
  System.out.println(
"\n"+cipher.getProvider().getInfo());

  
//使用私鈅加密
  System.out.println("\nStart encryption:");
  cipher.init(Cipher.ENCRYPT_MODE,key);
  
byte[] cipherText=cipher.doFinal(plainText);
  System.out.println(
"Finish encryption:");
  System.out.println(
new String(cipherText,"UTF8"));

  System.out.println(
"\nStart decryption:");
  cipher.init(Cipher.DECRYPT_MODE,key);
  
byte[] newPlainText=cipher.doFinal(cipherText);
  System.out.println(
"Finish decryption:");

  System.out.println(
new String(newPlainText,"UTF8"));

 }
}

  3)公钥加密:

   上面提到,私钥加密需要一个共享的密钥,那么如何传递密钥呢?web环境下,直接传递的话很容易被侦听到,幸好有了公钥加密的出现。公钥加密也叫不对称 加密,不对称算法使用一对密钥对,一个公钥,一个私钥,使用公钥加密的数据,只有私钥能解开(可用于加密);同时,使用私钥加密的数据,只有公钥能解开 (签名)。但是速度很慢(比私钥加密慢100到1000倍),公钥的主要算法有RSA,还包括Blowfish,Diffie-Helman等, jdk1.5种提供了对RSA的支持,是一个改进的地方:

/**
*PublicExample.java
*Copyright 2005-2-16
*/
import java.security.Key;
import javax.crypto.Cipher;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
/**
*一个简单的公鈅加密例子,Cipher类使用KeyPairGenerator生成的公鈅和私鈅
*/
public class PublicExample{
 
public static void main(String[] args) throws Exception{
  
if(args.length!=1){
   System.err.println(
"Usage:java PublicExample <text>");
   System.exit(
1);
  }

  
byte[] plainText=args[0].getBytes("UTF8");
  
//构成一个RSA密钥
  System.out.println("\nStart generating RSA key");
  KeyPairGenerator keyGen
=KeyPairGenerator.getInstance("RSA");
  keyGen.initialize(
1024);
  KeyPair key
=keyGen.generateKeyPair();
  System.out.println(
"Finish generating RSA key");

  
//获得一个RSA的Cipher类,使用公鈅加密
  Cipher cipher=Cipher.getInstance("RSA/ECB/PKCS1Padding");
  System.out.println(
"\n"+cipher.getProvider().getInfo());

  System.out.println(
"\nStart encryption");
  cipher.init(Cipher.ENCRYPT_MODE,key.getPublic());
  
byte[] cipherText=cipher.doFinal(plainText);
  System.out.println(
"Finish encryption:");
  System.out.println(
new String(cipherText,"UTF8"));

  
//使用私鈅解密
  System.out.println("\nStart decryption");
  cipher.init(Cipher.DECRYPT_MODE,key.getPrivate());
  
byte[] newPlainText=cipher.doFinal(cipherText);
  System.out.println(
"Finish decryption:");
  System.out.println(
new String(newPlainText,"UTF8"));
 }
}

posted @ 2006-09-07 23:59 Alex 阅读(1149) | 评论 (0)编辑 收藏

key words: Digester  解析xml

假设有下列xml文件:
<?xml version='1.0' encoding='utf-8'?>
<address-book>
    
<contact myType="individual">
        
<name>Zane Pasolini</name>
        
<address>999 W. Prince St.</address>
        
<city>New York</city>
        
<province>NY</province>
        
<postalcode>10013</postalcode>
        
<country>USA</country>
        
<telephone>1-212-345-6789</telephone>
    
</contact>
    
<contact myType="business">
        
<name>SAMOFIX d.o.o.</name>
        
<address>Ilica 47-2</address>
        
<city>Zagreb</city>
        
<province></province>
        
<postalcode>10000</postalcode>
        
<country from="cn">Croatia</country>
        
<telephone>385-1-123-4567</telephone>
    
</contact>
</address-book>

这是一份常用到的文件,现在我们需要将之映射到java bean,用Digester解析显得非常简单
public class AddressBookParser
{
    
/**
     * Prints the contact information to standard output.
     *
     * 
@param contact the <code>Contact</code> to print out
     
*/
    
public void addContact(Contact contact)
    {
        System.out.println(
"TYPE: " + contact.getType());
        System.out.println(
"NAME: " + contact.getName());
        System.out.println(
"    ADDRESS:    " + contact.getAddress());
        System.out.println(
"    CITY:       " + contact.getCity());
        System.out.println(
"    PROVINCE:   " + contact.getProvince());
        System.out.println(
"    POSTALCODE: " + contact.getPostalcode());
        System.out.println(
"    COUNTRY:    " + contact.getCountry());
        System.out.println(
"    COUNTRY-From:    " + contact.getCountryFrom());
        System.out.println(
"    TELEPHONE:  " + contact.getTelephone());
    }

    
/**
     * Configures Digester rules and actions, parses the XML file specified
     * as the first argument.
     *
     * 
@param args command line arguments
     
*/
    
public static void main(String[] args) throws IOException, SAXException
    {
        
// instantiate Digester and disable XML validation
        Digester digester = new Digester();
        digester.setValidating(
false);

        
// instantiate AddressBookParser class
        digester.addObjectCreate("address-book", AddressBookParser.class );
        
// instantiate Contact class
        digester.addObjectCreate("address-book/contact", Contact.class );

        
// set type property of Contact instance when 'type' attribute is found
        
//对有属性的值通过setProperties方法

        digester.addSetProperties(
"address-book/contact",         "myType""type" );

        
// set different properties of Contact instance using specified methods
        
//addCallMethod与addBeanPropertySetter等价
        
// 参数 0代表一个参数,默认就是当前读的数据

        digester.addCallMethod(
"address-book/contact/name",       "setName"0);
        digester.addCallMethod(
"address-book/contact/address",    "setAddress"0);
        digester.addCallMethod(
"address-book/contact/address",    "setAddress",0);
        digester.addCallMethod(
"address-book/contact/city",       "setCity"0);
        digester.addCallMethod(
"address-book/contact/province",   "setProvince"0);
        digester.addCallMethod(
"address-book/contact/postalcode""setPostalcode"0);
        digester.addCallMethod(
"address-book/contact/country",    "setCountry"0);



        
//增加country的属性 : from
        digester.addSetProperties("address-book/contact/country","from","countryFrom");
        digester.addCallMethod(
"address-book/contact/telephone",  "setTelephone"0);

        
// call 'addContact' method when the next 'address-book/contact' pattern is seen
        digester.addSetNext("address-book/contact",               "addContact" );

        
// now that rules and actions are configured, start the parsing process
        AddressBookParser abp = (AddressBookParser) digester.parse(new File("c:\\addressbook.xml"));
    }

    
/**
     * JavaBean class that holds properties of each Contact entry.
     * It is important that this class be public and static, in order for
     * Digester to be able to instantiate it.
     
*/
    
public static class Contact
    {
        
private String type;
        
private String name;
        
private String address;
        
private String city;
        
private String province;
        
private String postalcode;
        
private String country;
        //增加一个country的属性: from
        private String countryFrom;
        private String telephone;

        
public void setType(String newType)
        {
            type 
= newType;
        }
        
public String getType()
        {
            
return type;
        }

        
public void setName(String newName)
        {
            name 
= newName;
        }
        
public String getName()
        {
            
return name;
        }

        
public void setAddress(String newAddress)
        {
            address 
= newAddress;
        }
        
public String getAddress()
        {
            
return address;
        }

        
public void setCity(String newCity)
        {
            city 
= newCity;
        }
        
public String getCity()
        {
            
return city;
        }

        
public void setProvince(String newProvince)
        {
            province 
= newProvince;
        }
        
public String getProvince()
        {
            
return province;
        }

        
public void setPostalcode(String newPostalcode)
        {
            postalcode 
= newPostalcode;
        }
        
public String getPostalcode()
        {
            
return postalcode;
        }

        
public void setCountry(String newCountry)
        {
            country 
= newCountry;
        }
        
public String getCountry()
        {
            
return country;
        }

        
public void setTelephone(String newTelephone)
        {
            telephone 
= newTelephone;
        }
        
public String getTelephone()
        {
            
return telephone;
        }

        
public String getCountryFrom() {
            
return countryFrom;
        }

        
public void setCountryFrom(String countryFrom) {
            
this.countryFrom = countryFrom;
        }
    }
}


AjaxChat 中的读取房间信息的方式显得更简洁
房间的xml配置文件如下:
<rooms>
  
<room id="1" name="General Topics" />
  
<room id="2" name="Programming" />
  
<room id="3" name="Movies" />
  
<room id="4" name="Music" />
  
<room id="5" name="Television" />
</rooms>

解析代码如下 :
public synchronized void init(InputStream isConfigFile) {

        log.debug(
"init()");
        
if (isConfigFile != null) {
            
// Read in rooms config and create beans, hand off to DAO.
            Digester digester = new Digester();
            digester.setValidating(
false);
            digester.push(
this);
            digester.addObjectCreate(
"rooms/room",
                    
"org.apache.struts.apps.ajaxchat.dto.RoomDTO");
            //注意这里,如果xl的属性名称和bean的属性名称完全对应,则直接提供xml的位置即可
            digester.addSetProperties(
"rooms/room");
            digester.addSetNext(
"rooms/room""addRoom");
            
try {
                digester.parse(isConfigFile);
                log.info(
"***** Rooms = " + rooms);
            } 
catch (IOException ioe) {
                ioe.printStackTrace();
            } 
catch (SAXException se) {
                se.printStackTrace();
            }
        }

    } 
// End init().

如果在xml文件中增加非attribute则更改后的配置文件如下:

<rooms>
  
<room id="1" name="General Topics" />
  
<room id="2" name="Programming" />
  
<room id="3" name="Movies" />
  
<room id="4" name="Music" />
  
<room id="5" name="Television" />
  
<room>
    
<id>6</id>
    
<name>shit</name>
  
</room>
  
<room>
    
<id>7</id>
    
<name>haha</name>
  
</room>
</rooms>
对应的解析如下:
public synchronized void init(InputStream isConfigFile) {

        log.debug(
"init()");
        
if (isConfigFile != null) {
            
// Read in rooms config and create beans, hand off to DAO.
            Digester digester = new Digester();
            digester.setValidating(
false);
            digester.push(
this);
            digester.addObjectCreate(
"rooms/room",
                    
"org.apache.struts.apps.ajaxchat.dto.RoomDTO");
            digester.addSetProperties(
"rooms/room");
            //增加addCallMethod方法
            digester.addCallMethod(
"rooms/room/id","setId",0);
            digester.addCallMethod(
"rooms/room/name","setName",0);
            digester.addSetNext(
"rooms/room""addRoom");
            
try {
                digester.parse(isConfigFile);
                log.info(
"***** Rooms = " + rooms);
            } 
catch (IOException ioe) {
                ioe.printStackTrace();
            } 
catch (SAXException se) {
                se.printStackTrace();
            }
        }

    } 
// End init().

posted @ 2006-09-06 23:32 Alex 阅读(20848) | 评论 (19)编辑 收藏

how to set the SSH timeout?
a: set 'TMOUT=3600' in /etc/profile

fine.
posted @ 2006-09-06 16:07 Alex 阅读(833) | 评论 (0)编辑 收藏

key words:struts国际化

一、Struts的国际化
    Struts是一种支持国际化的MVC的Web Framework。可是如何来使用struts国际化是一个问题。下面我们来探讨一下,如何实现Struts的国际化。Web程式的国际化涉及到3个层面的东西。第一、jsp部分的输入/输出;第二、应用处理程序的国际化;第三、DB的国际化问题。这里主要探讨的是jsp部分的输入/输出问题。

二、静态部分的国际化
   Struts的jsp页面静态内容(包括静态文字,静态图片)国际化问题,是通过资源文件来实现的。要实现国际化,需要做如下几项工作:1、定义web.xml的动ActionServlet的参数;2、定义资源文件;3、定义JSP页面的字符集合;4、在JSP页面获取资源文件里面的内容。
1、定义web.xml的动ActionServlet的参数

<servlet>
  
<servlet-name>action</servlet-name>
  
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>   
  
<init-param>
    
<param-name>config</param-name>
    
<param-value>/WEB-INF/struts-config.xml</param-value>
  
</init-param>
  
<init-param>
    
<param-name>application</param-name>
    
<param-value>ApplicationResources</param-value> <!-- 默认资源文件名 -->
  
</init-param>
  
<load-on-startup>2</load-on-startup>
</servlet>

2、定义资源文件
在/WEB-INF/classes下面添加UTF-8资源束文件。每一个资源文件是“键-值”对的集合。在JSP页面里面可以通过键来找到相应的数据值。本例子的文件名是ApplicationResources,所以相应的资源文件束是(包括e文,简体中文,繁体中文)
ApplicationResources.properties : 默认资源文件。当在其他资源文件里面找不到某个资源的时候,就使用该资源文件里面的定义。
ApplicationResources_zh_CN.properties:简体中文资源文件。
ApplicationResources_zh_TW.properties:繁体中文资源文件。

资源文件的格式为:默认资源文件名_国别_语言.properties。其中每个文件都是通过%JAVA_HONE%/BIN/native2ascii.exe工具转换而来。你也可以使用其他工具来处理得到(http://java.sun.com/products/jilkit/ 有一个工具Internationalization Java Internationalization and Localization Toolkit 可以处理)。下面是一个例子,我们显示如何使用%JAVA_HONE%/BIN/native2ascii.exe命令来定义资源束文件。
2.1 准备文件
//ApplicationResources.properties ;默认资源文件,通常里面的内容是英文的。
label.username=USERNAME :
label.password=PASSWORD :

//ApplicationResources_zh_CN.bak ;简体中文的资源文件。里面的内容是中文的。它需要工具将其中的内容处理成UTF-8
label.username=用户名 :
label.password=密  码 :

//ApplicationResources_zh_TW.bak : 繁体中文的资源文件。里面的内容是中文的。它需要工具将其中的内容处理成UTF-8,下面的内容是繁体码。
label.username=ノめ?W :
label.password=ノめ?W :

2.2 准备完成以后,使用如下的命令创建UTF-8资源文件束
native2ascii -encoding gb2312 ApplicationResources_zh_CN.bak ApplicationResources_zh_CN.properties
native2ascii -encoding big5 Applica tionResources_zh_TW.bak ApplicationResources_zh_TW.properties

3、定义JSP页面的字符集合
定义JSP页面的语言为UTF-8。在每个JSP页面,必须有如下的内容(如果使用的模板技术,则只是需要在模板页面添加,其他使用该模板的页面无需添加)
<%@ page contentType="text/html;charset=UTF-8"%>

4、在JSP页面获取资源文件里面的内容。
在JSP里面需要显示静态内容的地方使用<bean:message />strus的bean tag包里面的message标签。例如下面的页面

<table>
  <tr>
    <td align="right"><bean:message key="label.username" /></td>   
  </tr>
  <tr>
    <td align="right"><bean:message key="label.password" /></td>
  </tr>
</table>

好了,在这个页面显示的时候,如果客户的IE的语言集合是zh_CN的话,就会显示
用户名:
口  令:

如果是客户的IE的语言是zh_TW的话,就会显示
用户名:
用户名:

可以在IE的工具->Internet选项->语言的地方,来选择,定义IE的语言。

三、表单的数据的处理。
对于表单数据的处理,我们是通过添加一个Filter来实现的。所有提交的请求,都需要做字符处理。然后在web.xml里面定义该Filter。这样我们就不需要在程序里面做任何的字符处理。
3.1 定义Filter。下面是一个例子。
package com.webapps.commons;

import java.io.*;
import javax.servlet.*;

public class CharsetEncodingFilter implements Filter{
  private FilterConfig config = null;
  private String defaultEncode = "UTF-8";

  public void init(FilterConfig config) throws ServletException {
    this.config = config;
    if(config.getInitParameter("Charset")!=null){
        defaultEncode=config.getInitParameter("Charset");
    }
  }

  public void destroy() {
    this.config = null;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                       FilterChain chain) throws IOException, ServletException {
    ServletRequest srequest=request;
    srequest.setCharacterEncoding(defaultEncode);
    chain.doFilter(srequest,response);
  }
}

3.2 在web.xml里面声明使用该Filter
<filter>
  <filter-name>Character Encoding</filter-name>
  <filter-class>com.webapps.commons.CharsetEncodingFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>Character Encoding</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

四、扩展
待续的是应用程序部分的国际化问题,和DB的国际化问题。


posted @ 2006-09-05 22:22 Alex 阅读(709) | 评论 (0)编辑 收藏

使用 AppFuse 的七个理由

学习 Java 开放源码工具 —— 并使用这些工具提高生产效率


级别: 初级

Matt Raible (mraible@virtuas.com), 开放源码实践先驱, Virtuas Open Source Solutions

2006 年 8 月 31 日

开 始学习在 Java™ 平台上使用诸如 Spring、Hibernate 或 MySQL 之类的开放源码工具时可能非常困难。再加上 Ant 或 Maven,以及与 DWR 一起的小 Ajax,还有 Web 框架 —— 即 JSF,我们必须睁大眼睛盯着如何配置应用程序。AppFuse 减少了集成开放源码项目的痛苦。它可以把测试变成一等公民,让我们可以从数据库表生成整个 UI,并使用 XFire 来支持 Web 服务。另外,AppFuse 的社区也非常健全,这是不同 Web 框架用户可以一起融洽相处的地方之一。

AppFuse 是一个开放源码的项目和应用程序,它使用了在 Java 平台上构建的开放源码工具来帮助我们快速而高效地开发 Web 应用程序。我最初开发它是为了减少在为客户构建新 Web 应用程序时所花费的那些不必要的时间。从核心上来说,AppFuse 是一个项目骨架,类似于通过向导创建新 Web 项目时 IDE 所创建的东西。当我们使用 AppFuse 创建一个项目时,它会提示我们将使用开放源码框架,然后才创建项目。它使用 Ant 来驱动测试、代码生成、编译和部署。它提供了目录和包结构,以及开发基于 Java 语言的 Web 应用程序所需要的库。

与大部分 “new project” 向导不同,AppFuse 创建的项目从最开始就包含很多类和文件。这些文件用来实现特性,不过它们同时也会在您开发应用程序时被用作示例。通过使用 AppFuse 启动新项目,我们通常可以减少一到两周的开发时间。我们不用担心如何将开放源码框架配置在一起,因为这都已经完成了。我们的项目都已提前配置来与数据库进 行交互,它会部署到应用服务器上,并对用户进行认证。我们不必实现安全特性,因为这都早已集成了。

当我最初开发 AppFuse 时,它只支持 Struts 和 Hibernate。经过几年的努力,我发现了比 Struts 更好的 Web 框架,因此我还添加了为这些 Web 框架使用的选项。现在,AppFuse 可以支持 Hibernate 或 iBATIS 作为持久性框架。对于 Web 框架来说,我们可以使用 JavaServer Faces(JSF)、Spring MVC、Struts、Tapestry 或 WebWork。

AppFuse 提供了很多应用程序需要的一些特性,包括:

  • 认证和授权
  • 用户管理
  • Remember Me(这会保存您的登录信息,这样就不用每次都再进行登录了)
  • 密码提醒
  • 登记和注册
  • SSL 转换
  • E-mail
  • URL 重写
  • 皮肤
  • 页面修饰
  • 模板化布局
  • 文件上载

这种 “开箱即用” 的功能是 AppFuse 与其他 CRUD 代 框架的区别之一(CRUD 取自创建、检索、更新删除 几个操作的英文首字母),包括 Ruby on Rails、Trails 和 Grails。上面提到的这些框架,以及 AppFuse,都让我们可以从数据库表或现有的模型对象中生成主页/细节页。

图 1 阐述了一个典型 AppFuse 应用程序的概念设计:


图 1. 典型的 AppFuse 应用程序
典型的 AppFuse 应用程序

清单 1 给出了我们在创建 devworks 项目时所使用的命令行交互操作,同时还给出了所生成的结果。这个项目使用了 WebWork 作为自己的 Web 框架(请参考下面 参考资料 一节给出的链接)。


清单 1. 使用 AppFuse 创建新项目
alotta:~/dev/appfuse mraible$ ant new
Buildfile: build.xml

clean:
[echo] Cleaning build and distribution directories

init:

new:
[echo]
[echo] +-------------------------------------------------------------+
[echo] | -- Welcome to the AppFuse New Application Wizard! -- |
[echo] | |
[echo] | To create a new application, please answer the following |
[echo] | questions. |
[echo] +-------------------------------------------------------------+

[input] What would you like to name your application [myapp]?
devworks
[input] What would you like to name your database [mydb]?
devworks
[input] What package name would you like to use [org.appfuse]?
com.ibm
[input] What web framework would you like to use [webwork,tapestry,spring,js
f,struts]?
webwork
[echo] Creating new application named 'devworks'...
[copy] Copying 359 files to /Users/mraible/Work/devworks
[copy] Copying 181 files to /Users/mraible/Work/devworks/extras
[copy] Copying 1 file to /Users/mraible/Work/devworks
[copy] Copying 1 file to /Users/mraible/Work/devworks

install:
[echo] Copying WebWork JARs to ../../lib
[copy] Copying 6 files to /Users/mraible/Work/devworks/lib
[echo] Adding WebWork entries to ../../lib.properties
[echo] Adding WebWork classpath entries
[echo] Removing Struts-specific JARs
[delete] Deleting directory /Users/mraible/Work/devworks/lib/struts-1.2.9
[delete] Deleting directory /Users/mraible/Work/devworks/lib/strutstest-2.1.3
[echo] Deleting struts_form.xdt for XDoclet
[delete] Deleting directory /Users/mraible/Work/devworks/metadata/templates
[echo] Deleting Struts merge-files in metadata/web
[delete] Deleting 7 files from /Users/mraible/Work/devworks/metadata/web
[echo] Deleting unused Tag Libraries and Utilities
[delete] Deleting 2 files from /Users/mraible/Work/devworks/src/web/org/appfu
se/webapp
[echo] Modifying appgen for WebWork
[copy] Copying 12 files to /Users/mraible/Work/devworks/extras/appgen
[echo] Replacing source and test files
[delete] Deleting directory /Users/mraible/Work/devworks/src/web/org/appfuse/
webapp/form
[delete] Deleting directory /Users/mraible/Work/devworks/src/web/org/appfuse/
webapp/action
[copy] Copying 13 files to /Users/mraible/Work/devworks/src
[delete] Deleting directory /Users/mraible/Work/devworks/test/web/org/appfuse
/webapp/form
[delete] Deleting directory /Users/mraible/Work/devworks/test/web/org/appfuse
/webapp/action
[copy] Copying 5 files to /Users/mraible/Work/devworks/test
[echo] Replacing web files (images, scripts, JSPs, etc.)
[delete] Deleting 1 files from /Users/mraible/Work/devworks/web/scripts
[copy] Copying 34 files to /Users/mraible/Work/devworks/web
[delete] Deleting: /Users/mraible/Work/devworks/web/WEB-INF/validator-rules-c
ustom.xml
[echo] Modifying Eclipse .classpath file
[echo] Refactoring build.xml
[echo] ----------------------------------------------
[echo] NOTE: It's recommended you delete extras/webwork as you shouldn't ne
ed it anymore.
[echo] ----------------------------------------------
[echo] Repackaging info written to rename.log
[echo]
[echo] +-------------------------------------------------------------+
[echo] | -- Application created successfully! -- |
[echo] | |
[echo] | Now you should be able to cd to your application and run: |
[echo] | > ant setup test-all |
[echo] +-------------------------------------------------------------+

BUILD SUCCESSFUL
Total time: 15 seconds

为什么使用 WebWork?
Struts 社区最近在热情地拥抱 WebWork,这种结合导致为 Java 平台提供了一个非常优秀的新 Web 框架:Struts 2。当然,Spring MVC 是一个非常优秀的基于请求的框架,但是它不能像 Struts 2 一样支持 JSF。基于内容的框架(例如 JSF 和 Tapestry)也都很好,但是我发现 WebWork 更为直观,更容易使用(更多有关 Structs 2 和 JSF 的内容请参看 参考资料)。

在创建一个新项目之后,我们就得到了一个类似于图 2 所示的目录结构。Eclipse 和 Intellij IDEA 项目文件都是作为这个过程的一部分创建的。


图 2. 项目的目录结构
项目的目录结构

这个目录结构与 Sun 为 Java 2 Platform Enterprise Edition(J2EE)Web 应用程序推荐的目录结构非常类似。在 2.0 版本的 AppFuse 中,这个结构会变化成适合 Apache Maven 项目的标准目录结构(有关这两个目录介绍的内容,请参看 参考资料 中的链接)。AppFuse 还会从 Ant 迁移到 Maven 2 上,从而获得相关下载的能力和对生成 IDE 项目文件的支持。目前基于 Ant 的系统要求提交者维护项目文件,而 Maven 2 可以通过简单地使用项目的 pom.xml 文件生成 IDEA、Eclipse 和 NetBeans 项目文件。(这个文件位于您项目的根目录中,是使用 Maven 构建应用程序所需要的主要组件)。它与利用 Ant 所使用的 build.xml 文件非常类似。)

现在我们对 AppFuse 是什么已经有一点概念了,在本文剩下的部分中,我们将介绍使用 AppFuse 的 7 点理由。即使您选择不使用 AppFuse 来开始自己的项目,也会看到 AppFuse 可以为您提供很多样板代码,这些代码可以在基于 Java 语言的 Web 应用程序中使用。由于它是基于 Apache 许可证的,因此非常欢迎您在自己的应用程序中重用这些代码。

理由 1:测试

测试是在软件开发项目中很少被给予足够信任的一个环节。注意我并不是说在软件开发的一些刊物中没有得到足够的信任!很多文章和案例研究都给出了测试 优先的开发方式和足够的测试覆盖面以提高软件的质量。然而,测试通常都被看作是一件只会延长项目开发时间的事情。实际上,如果我们使用测试优先的方法在编 写代码之前就开始撰写测试用例,我相信我们可以发现这实际上会加速 开发速度。另外,测试优先也可以使维护和重用更加 容易。如果我们不编写代码来测试自己的代码,那么就需要手工对应用程序进行测试 —— 这通常效率都不高。自动化才是关键。

当我们首次开始使用 AppFuse 时,我们可能需要阅读这个项目 Web 站点上提供的快速入门指南和教程(请参看 参考资料 中的链接)。这些教程的编写就是为了您可以首先编写测试用例;它们直到编写接口和/或实现之后才能编译。如果您有些方面与我一样,就会在开始编写代码之前 就已经编写好测试用例了;这是真正可以加速编写代码的惟一方式。如果您首先编写了代码的实现,通过某种方式验证它可以工作,那么您可能会对自己说,“哦, 看起来不错 —— 谁需要测试呢?我还有更多的代码需要编写!”这种情况不幸的一面是您通常都会做一些事情 来测试自己的代码;您简单地跳过了可以自动化进行测试的地方。

AppFuse 的文档展示了如何测试应用程序的所有 层次。它从数据库层开始入手,使用了 DbUnit(请参看 参考资料)在运行测试之前提前使用数据来填充自己的数据库。在数据访问(DAO)层,它使用了 Spring 的 AbstractTransactionalDataSourceSpringContextTests 类(是的,这的确是一个类的名字!)来允许简单地加载 Spring 上下文文件。另外,这个类对每个 testXXX() 方法封装了一个事务,并当测试方法存在时进行回滚。这种特性使得测试 DAO 逻辑变得非常简单,并且不会对数据库中的数据造成影响。

在服务层,jMock (请参看 参考资料)用来编写那些可以消除 DAO 依赖的真正 单元测试。这允许进行验证业务逻辑正确的快速测试;我们不用担心底层的持久性逻辑。

HtmlUnit 支持
HtmlUnit 团队在 1.8 发行版中已经完成了相当多的工作来确保包可以与流行的 Ajax 框架(Prototype 和 Scriptaculous)很好地工作。

在 Web 层,测试会验证操作(Struts/WebWork)、控件(Spring MVC)、页面(Tapestry)和管理 bean(JSF)如我们所期望的一样进行工作。Spring 的 spring-mock.jar 可以非常有用地用来测试所有这些框架,因为它包含了一个 Servlet API 的仿真实现。如果没有这个有用的库,那么测试 AppFuse 的 Web 框架就会变得非常困难。

UI 通常是开发 Web 应用程序过程中最为困难的一部分。它也是顾客最经常抱怨的地方 —— 这既是由于它并不是非常完美,也是由于它的工作方式与我们期望的并不一样。另外,没有什么会比在客户面前作演示的过程中看到看到异常堆栈更糟糕的了!您的 应用程序可能会非常可怕,但是客户可能会要求您做到十分完美。永远不要让这种事情发生。Canoo WebTest 可以对 UI 进行测试。它使用了 HtmlUnit 来遍历测试 UI,验证所有的元素都存在,并可以填充表单的域,甚至可以验证一个假想的启用 Ajax 的 UI 与我们预期的工作方式一样。(有关 WebTest 和 HTMLUnit 的链接请参看 参考资料。)

为了进一步简化 Web 的测试,Cargo(请参看 参考资料)对 Tomcat 的启动和停止(分别在运行 WebTest 测试之前和之后)进行了自动化。





理由 2:集成

正如我在本文简介中提到的一样,很多开放源码库都已经预先集成到 AppFuse 中了。它们可以分为以下几类:

  • 编译、报告和代码生成:Ant、Ant Contrib Tasks、Checkstyle、EMMA、Java2Html、PMD 和 Rename Packages
  • 测试框架:DbUnit、Dumbster、jMock、JUnit 和 Canoo WebTest
  • 数据库驱动程序:MySQL 和 PostgreSQL
  • 持久性框架:Hibernate 和 iBATIS
  • IoC 框架:Spring
  • Web 框架:JSF、Spring MVC、Struts、Tapestry 和 WebWork
  • Web 服务:XFire
  • Web 工具:Clickstream、Display Tag、DWR、JSTL、SiteMesh、Struts Menu 和 URL Rewrite Filter
  • Security:Acegi Security
  • JavaScript 和 CSS:Scriptaculous、Prototype 和 Mike Stenhouse 的 CSS Framework

除了这些库之外,AppFuse 还使用 Log4j 来记录日志,使用 Velocity 来构建 e-mail 和菜单模板。Tomcat 可以支持最新的开发,我们可以使用 1.4 或 5 版本的 Java 平台来编译或构建程序。我们应该可以将 AppFuse 部署到任何 J2EE 1.3 兼容的应用服务器上;这已经经过了测试,我们知道它在所有主要版本的 J2EE 服务器和所有主要的 servlet 容器上都可以很好地工作。

图 3 给出了上面创建的 devworks 项目的 lib 目录。这个目录中的 lib.properties 文件控制了每个依赖性的版本号,这意味着我们可以简单地通过把这些包的新版本放到这个目录中并执行诸如 ant test-all -Dspring.version=2.0 之类的命令来测试这些包的新版本。


图 3. 项目依赖性
AppFuse 项目依赖性

预先集成这些开放源码库可以在项目之初极大地提高生产效率。尽管我们可以找到很多文档介绍如何集成这些库,但是定制工作示例并简单地使用它来开发应用程序要更加简单。

除了可以简化 Web 应用程序的开发之外,AppFuse 让我们还可以将 Web 服务简单地集成到自己的项目中。尽管 XFire 也在 AppFuse 下载中一起提供了,不过如果我们希望,也可以自己集成 Apache Axis(请参看 参考资料 中有关 Axis 集成的教程)。另外,Spring 框架和 XFire 可以一起将服务层作为 Web 服务非常简单地呈现出来,这就为我们提供了开发面向服务架构的能力。

另外,AppFuse 并不会将我们限定到任何特定的 API 上。它只是简单地对可用的最佳开放源码解决方案重新进行打包和预先集成。AppFuse 中的代码可以处理这种集成,并实现了 AppFuse 的基本安全性和可用性特性。只要可能,就会减少代码,以便向 AppFuse 的依赖框架添加一个特性。例如,AppFuse 自带的 Remember Me 和 SSL 切换特性最近就因为类似的特性而从 Acegi Security 中删除了。







理由 3:自动化

Ant 使得简化了从编译到构建再到部署的自动化过程。Ant 是 AppFuse 中的一等公民,这主要是因为我发现在命令行中执行操作比从 IDE 中更加简单。我们可以使用 Ant 实现编译、测试、部署和执行任何代码生成的任务。

尽管这种能力对于有些人来说非常重要,但是它并不适用于所有的人。很多 AppFuse 用户目前都使用 Eclipse 或 Intellij IDEA 来构建和测试自己的项目。在这些 IDE 中运行 Ant 的确可以工作,但是这样做的效率通常都不如使用 IDE 内置的 JUnit 支持来运行测试效率高。

幸运的是,AppFuse 支持在 IDE 中运行测试,不过管理这种特性对于 AppFuse 开发人员来说就变得非常困难了。最大的痛苦在于 XDoclet 用来生成 Hibernate 映射文件和 Web 框架所使用的一些工件(例如 ActionForms 和 Struts 使用的 struts-config.xml)。IDE 并不知道需要生成的代码,除非我们配置使用 Ant 来编译它们,或者安装了一些可以认识 XDoclet 的插件。

这种对知识的缺乏是 AppFuse 2.0 切换到 JDK 5 和 Maven 2 上的主要原因。JDK 5、注释和 Struts 2 将让我们可以摆脱 XDoclet。Maven 2 将使用这些生成的文件和动态类路径来生成 IDE 项目文件,这样对项目的管理就可以进行简化。目前基于 Ant 的编译系统已经为不同的层次生成了一些工件(包括 dao.jar、service.jar 和 webapp.war),因此切换到 Maven 的模型上应该是一个非常自然的调整。

除了 Ant 之外(它对于编译、测试、部署和报告具有丰富的支持),对于 CruiseControl 的支持也构建到了 AppFuse 中。CruiseControl 是一个 Continuous Integration 应用程序,让我们可以在源代码仓库中代码发生变化时自动运行所有的测试。extras/cruisecontrol 目录包含了我们为基于 AppFuse 的项目快速、简单地设置 Continuous Integration 时所需要的文件。

设置 Continuous Integration 是软件开发周期中我们首先要做的事情之一。它不但激发程序员去编写测试用例,而且还通过 “You broke the build!” 游戏促进了团队之间的合作和融合。







理由 4:安全特性和可扩展性

AppFuse 最初是作为我为 Apress 编写的书籍 Pro JSP 中示例应用程序的一部分开发的。这个示例应用程序展示了很多安全特性和用于简化 Struts 开发的特性。这个应用程序中的很多安全特性在 J2EE 的安全框图中都不存在。使用容器管理认证(CMA)的认证方法非常简单,但是 Remember Me、密码提示、SSL 切换、登记和用户管理等功能却都不存在。另外,基于角色的保护方法功能在非 EJB 环境中也是不可能的。

最初,AppFuse 使用自己的代码和用于 CMA 的解决方案完全实现了这些特性。我在 2004 年年初开始学习 Spring 时就听说过有关 Acegi Security 的知识。我对 Acegi 所需要的 XML 的行数(175)与 web.xml 中所需要的 CMA 的行数(20)进行了比较,很快就决定丢弃 Acegi 了,因为它太过复杂了。

一年半之后 —— 在为另外一本书 Spring Live 中编写了一章有关使用 Acegi Security 的内容之后 —— 我就改变了自己的想法。Acegi 的确(目 前仍然)需要很多 XML,但是一旦我们理解了这一点,它实际上是相当简单的。当我们最终作出改变,使用 Acegi Security 的特性来全部取代 AppFuse 的特性之后,我们最终删除了大量的代码。类之上的类都已经没有了,“Acegi handles that now” 中消失的部分现在全部进入了 CVS 的 Attic 中了。

Acegi Security 是 J2EE 安全模型中曾经出现过的最好模型。它让我们可以实现很多有用的特性,这些特性在 Servlet API 的安全模型中都不存在:认证、授权、角色保护方法、Remember Me、密码加密、SSL 切换、用户切换和注销。它让我们还可以将用户证书存储到 XML 文件、数据库、LDAP 或单点登录系统(例如 Yale 的 Central Authentication Service (CAS) 或者 SiteMinder)中。

AppFuse 对很多与安全性相关的特性的实现从一开始都是非常优秀的。现在 AppFuse 使用了 Acegi Security,这些特性 —— 以及更多特性 —— 都非常容易实现。Acegi 有很多地方都可以进行扩充:这是它使用巨大的 XML 配置文件的原因。正如我们已经通过去年的课程对 Acegi 进行集成一样,我们已经发现对很多 bean 的定义进行定制可以更加紧密地与 AppFuse 建立联系。

Spring IoC 容器和 Acegi Security 所提供的简单开发、容易测试的代码和松耦合特性的组合是 AppFuse 是这么好的一种开发平台的主要原因。这些框架都是不可插入的,允许生成干净的可测试代码。AppFuse 集成了很多开放源码项目,依赖注入允许对应用程序层进行简单的集成。







理由 5:使用 AppGen 生成代码

有些人会将代码生成称为代码气味的散播(code smell)。在他们的观点中,如果我们需要生成代码,那么很可能就会做一些错事。我倾向于这种确定自己代码使用的模式和自动化生成代码的能力应该称为代码香味的弥漫(code perfume)。如果我们正在编写类似的 DAO、管理器、操作或控件,并且不想为它们生成代码,那么这就需要根据代码的气味来生成代码。当然,当语言可以为我们提供可以简化任务的特性时,一切都是那么美好;不过代码生成通常都是一个必需 —— 通常其生产率也非常高 —— 的任务。

AppFuse 中提供了一个基于 Ant 和 XDoclet 的代码生成工具,名叫 AppGen。默认情况下,常见的 DAO 和管理器都可以允许我们对任何普通老式 Java 对象(POJO)进行 CRUD 操作,但是在 Web 层上这样做有些困难。AppGen 有几个特性可以用来执行以下任务:

  • (使用 Middlegen 和 Hibernate 工具)从数据库表中生成 POJO
  • 从 POJO 生成 UI
  • 为 DAO、管理器、操作/控制器和 UI 生成测试

在运行 AppGen 时,您会看到提示说 AppGen 要从数据库表或 POJO 中生成代码。如果在命令行中执行 ant install-detailed,AppGen 就会安装 POJO 特定的 DAO、管理器以及它们的测试。运行 ant install 会导致 Web 层的类重用通用的 DAO 和默认存在的管理器。

为了阐述 AppGen 是如何工作的,我们在 devworks MySQL 数据库中创建了如清单 2 所示的表:


清单 2. 创建一个名为 cat 的数据库表
    create table cat (
cat_id int(8) auto_increment,
color varchar(20) not null,
name varchar(20) not null,
created_date datetime not null,
primary key (cat_id)
) type=InnoDB;

在 extras/appgen 目录中,运行 ant install-detailed。这个命令的输出结果对于本文来说实在太长了,不过我们在清单 3 中给出了第一部分的内容:


清单 3. 运行 AppGen 的 install-detailed 目标
$ ant install-detailed
Buildfile: build.xml

init:
[mkdir] Created dir: /Users/mraible/Work/devworks/extras/appgen/build
[echo]
[echo] +-------------------------------------------------------+
[echo] | -- Welcome to the AppGen! -- |
[echo] | |
[echo] | Use the "install" target to use the generic DAO and |
[echo] | Manager, or use "install-detailed" to general a DAO |
[echo] | and Manager specifically for your model object. |
[echo] +-------------------------------------------------------+

[input] Would you like to generate code from a table or POJO? (table,pojo)
table
[input] What is the name of your table (i.e. person)?
cat
[input] What is the name, if any, of the module for your table (i.e. organization)?

[echo] Running Middlegen to generate POJO...

要对 cat 表使用这个新生成的代码,我们需要修改 src/dao/com/ibm/dao/hibernate/applicationContext-hibernate.xml,来为 Hibernate 添加 Cat.hbm.xml 映射文件。清单 3 给出了我们修改后的 sessionFactory bean 的样子:


清单 4. 将 Cat.hbm.xml 添加到 sessionFactory bean 中
    <bean id="sessionFactory" class="...">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>com/ibm/model/Role.hbm.xml</value>
<value>com/ibm/model/User.hbm.xml</value>
<value>com/ibm/model/Cat.hbm.xml</value>
</list>
</property>
...
</bean>

在运行 ant setup deploy 之后,我们就应该可以在部署的应用程序中对 cat 表执行 CRUD 操作了:


图 4. Cat 列表
所生成的主屏幕

图 5. Cat 表单
所生成的详细屏幕

我们在上面的屏幕快照中看到的记录都是作为代码生成的一部分创建的,因此现在就有测试数据了。








理由 6:文档

我们可以找到 AppFuse 各个风味的教程,并且它们都以 6 种不同的语言给出了:中文、德语、英语、韩语、葡萄牙语和西班牙语。使用风味(flavor) 一词,我的意思是不同框架的组合,例如 Spring MVC 加上 iBATIS、Spring MVC 加上 Hibernate 或 JSF 加上 Hibernate。使用这 5 种 Web 框架和两种持久框架,可以有好几种组合。有关它们的翻译,AppFuse 为自己的默认特性提供了 8 种翻译。可用语言包括中文、荷兰语、德语、英语、法语、意大利语、葡萄牙语和西班牙语。

除了核心教程之外,还添加了很多教程(请参看 参考资料) 来介绍与各种数据库、应用服务器和其他开放源码技术(包括 JasperReports、Lucene、Eclipse、Drools、Axis 和 DWR)的集成。





理由 7:社区

Apache 软件基金会对于开放源码有一个有趣的看法。它对围绕开放源码项目开发一个开放源码社区最感兴趣。它的成员相信如果社区非常强大,那么产生高质量的代码就是一个自然的过程。下面的内容引自 Apache 主页:

“我们认为自己不仅仅是一组共享服务器的项目,而且是一个开发人员和用户的社区。”

AppFuse 社区从 2003 年作为 SourceForge 上的一个项目(是 struts.sf.net 的一部分)启动以来,已经获得了极大的增长。通过在 2004 年 3 月转换到 java.net 上之后,它已经成为这里一个非常流行的项目,从 2005 年 1 月到 3 月成为访问量最多的一个项目。目前它仍然是一个非常流行的项目(有关 java.net 项目统计信息的链接,请参看 参考资料),不过在这个站点上它正在让位于 Sun 赞助的很多项目。

在 2004 年年末,Nathan Anderson 成为继我之后第一个提交者。此后有很多人都加入了进来,包括 Ben Gill、David Carter、Mika G?ckel、Sanjiv Jivan 和 Thomas Gaudin。很多现有的提交者都已经通过各种方式作出了自己的贡献,他们都帮助 AppFuse 社区成为一个迅速变化并且非常有趣的地方。

邮件列表非常友好,我们试图维护这样一条承诺 “没有问题是没有人理会的问题”。我们的邮件列表归档文件中惟一一条 “RTFM” 是从用户那里发出的,而不是从开发者那里发出的。我们绝对信奉 Apache 开放源码社区的哲学。引用我最好的朋友 Bruce Snyder 的一句话,“我们为代码而来,为人们而留下”。目前,大部分开发者都是用户,我们通常都喜欢有一段美妙的时间。另外,大部分文档都是由社区编写的;因此, 这个社区的知识是非常渊博的。







结束语

我们应该尝试使用 AppFuse 进行开发,这是因为它允许我们简单地进行测试、集成、自动化,并可以安全地生成 Web 应用程序。其文档非常丰富,社区也非常友好。随着其支撑框架越来越好,AppFuse 也将不断改进。

从 AppFuse 2.0 开始,我们计划迁移到 JDK 5(仍然支持部署到 1.4)和 Maven 2 上去。这些工具可以简化使用 AppFuse 的开发、安装和升级。我们计划充分利用 Maven 2 的功能来处理相关依赖性。我们将碰到诸如 appfuse-hibernate-2.0.jar 和 appfuse-jsf-2.0.jar 之类的工件。这些工件都可以在 pom.xml 文件中进行引用,它们负责提取其他相关依赖性。除了在自己的项目中使用 AppFuse 基类之外,我们还可以像普通的框架一样在 JAR 中对这些类简单地进行扩展,这应该会大大简化它的升级过程,并鼓励更多用户将自己希望的改进提交到这个项目中。

如果没有其他问题,使用 AppFuse 可以让您始终处于 Java Web 开发的技术前沿上 —— 就像我们一样!







参考资料

学习

获得产品和技术
  • AppFuse on java.net:下载不同风味的 AppFuse。

  • WebWork:了解这个易于使用的 Web 框架。

  • DbUnit:查看更多有关 JUnit 扩展的内容。

  • jMock:创建动态仿真对象来简化真正的单元测试。

  • Canoo WebTest:自动化 Web 应用程序的 UI 测试。

  • HtmlUnit:WebTest 的优秀 JavaScript 支持背后的基础。

  • Cargo:自动启动和停止容器。

  • Greenbox:一种代码生成框架。


讨论






关于作者

Matt Raible 居住在美国科罗拉多州的丹佛,他在那里是 Spring 和 Web 框架对 Virtuas Open Source Solutions 的实践先驱。他在开放源码领域具有丰富的经验,是这个领域的专家。他在这个领域中既是用户,又是一名开发人员。Matt 是 SourceBeat PublishingSpring Live 的作者。他还为 Apress 的书籍 Pro JSP Third Edition 作出了很大的贡献。他是很多开放源码会议的积极倡导者,包括 ApacheCon、MySQL User's Conference 和 OSCON,同时他还是 http://raibledesigns.com 上一名非常活跃的博客。Raible 的大部分生活都被计算机所包围了,尽管他是在连电都没有的 Montana 长大的。当不工作的时候时,他总是试图让妻子 Julie 成为世界上最幸福的女人,或者与他们的孩子 Abbie 和 Jack 一起玩耍。

posted @ 2006-09-04 20:34 Alex 阅读(580) | 评论 (0)编辑 收藏

外观模式(Facade pattern)涉及到子系统的一些类。所谓子系统,是为提供一系列相关的特征(功能)而紧密关联的一组类。例如,一个Account类、Address类和CreditCard类相互关联,成为子系统的一部分,提供在线客户的特征。

   在真实的应用系统中,一个子系统可能由很多类组成。子系统的客户为了它们的需要,需要和子系统中的一些类进行交互。客户和子系统的类进行直接的交互会导 致客户端对象和子系统(Figure1)之间高度耦合。任何的类似于对子系统中类的接口的修改,会对依赖于它的所有的客户类造成影响。

  
Figure1: Client Interaction with Subsystem Classes before Applying the Facade Pattern

  外观模式(Facade pattern)很适用于在上述情况。外观模式(Facade pattern)为子系统提供了一个更高层次、更简单的接口,从而降低了子系统的复杂度和依赖。这使得子系统更易于使用和管理。

  外观是一个能为子系统和客户提供简单接口的类。当正确的应用外观,客户不再直接和子系统中的类交互,而是与外观交互。外观承担与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统和客户的耦合度(Figure2).

  
Figure2: Client Interaction with Subsystem Classes after Applying the Facade Pattern

  从Figure2中我们可以看到:外观对象隔离了客户和子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受到影响。

  尽管客户使用由外观提供的简单接口,但是当需要的时候,客户端还是可以视外观不存在,直接访问子系统中的底层次的接口。这种情况下,它们之间的依赖/耦合度和原来一样。

  例子:

  让我们建立一个应用:

  (1) 接受客户的详细资料(账户、地址和信用卡信息)

  (2) 验证输入的信息

  (3) 保存输入的信息到相应的文件中。

  这个应用有三个类:Account、Address和CreditCard。每一个类都有自己的验证和保存数据的方法。

  Listing1: AccountClass

public class Account {
 String firstName;
 String lastName;
 final String ACCOUNT_DATA_FILE = "AccountData.txt";
 public Account(String fname, String lname) {
  firstName = fname;
  lastName = lname;
 }
 public boolean isValid() {
  /*
  Let's go with simpler validation
  here to keep the example simpler.
  */
  …
  …
 }
 public boolean save() {
  FileUtil futil = new FileUtil();
  String dataLine = getLastName() + ”," + getFirstName();
  return futil.writeToFile(ACCOUNT_DATA_FILE, dataLine,true, true);
 }
 public String getFirstName() {
  return firstName;
 }
 public String getLastName() {
  return lastName;
 }
}

  Listing2: Address Class

public class Address {
 String address;
 String city;
 String state;
 final String ADDRESS_DATA_FILE = "Address.txt";
 public Address(String add, String cty, String st) {
  address = add;
  city = cty;
  state = st;
 }
 public boolean isValid() {
  /*
  The address validation algorithm
  could be complex in real-world
  applications.
  Let's go with simpler validation
  here to keep the example simpler.
  */
  if (getState().trim().length() < 2)
   return false;
  return true;
 }
 public boolean save() {
  FileUtil futil = new FileUtil();
  String dataLine = getAddress() + ”," + getCity() + ”," + getState();
  return futil.writeToFile(ADDRESS_DATA_FILE, dataLine,true, true);
 }
 public String getAddress() {
  return address;
 }
 public String getCity() {
  return city;
 }
 public String getState() {
  return state;
 }
}

  Listing3: CreditCard Class

public class CreditCard {
 String cardType;
 String cardNumber;
 String cardExpDate;
 final String CC_DATA_FILE = "CC.txt";
 public CreditCard(String ccType, String ccNumber,
 String ccExpDate) {
  cardType = ccType;
  cardNumber = ccNumber;
  cardExpDate = ccExpDate;
 }
 public boolean isValid() {
  /*
  Let's go with simpler validation
  here to keep the example simpler.
  */
  if (getCardType().equals(AccountManager.VISA)) {
   return (getCardNumber().trim().length() == 16);
  }
  if (getCardType().equals(AccountManager.DISCOVER)) {
   return (getCardNumber().trim().length() == 15);
  }
  if (getCardType().equals(AccountManager.MASTER)) {
   return (getCardNumber().trim().length() == 16);
  }
  return false;
 }
 public boolean save() {
  FileUtil futil = new FileUtil();
  String dataLine = getCardType() + ,”" + getCardNumber() + ”," + getCardExpDate();
  return futil.writeToFile(CC_DATA_FILE, dataLine, true, true);
 }
 public String getCardType() {
  return cardType;
 }
 public String getCardNumber() {
  return cardNumber;
 }
 public String getCardExpDate() {
  return cardExpDate;
 }
}

  
Figure3: Subsystem Classes to Provide the Necessary Functionality to Validate and Save the Customer Data



让我们建立一个客户AccountManager,它提供用户输入数据的用户界面。

  Listing4: Client AccountManager Class

public class AccountManager extends JFrame {
 public static final String newline = "\n";
 public static final String VALIDATE_SAVE = "Validate & Save";
 …
 …
 public AccountManager() {
  super(" Facade Pattern - Example ");
  cmbCardType = new JComboBox();
  cmbCardType.addItem(AccountManager.VISA);
  cmbCardType.addItem(AccountManager.MASTER);
  cmbCardType.addItem(AccountManager.DISCOVER);
  …
  …
  //Create buttons
  JButton validateSaveButton = new JButton(AccountManager.VALIDATE_SAVE);
  …
  …
 }
 public String getFirstName() {
  return txtFirstName.getText();
 }
 …
 …
}//End of class AccountManager

  当客户AccountManage运行的时候,展示的用户接口如下:

  
Figure4: User Interface to Enter the Customer Data

  为了验证和保存输入的数据,客户AccountManager需要:

  (1) 建立Account、Address和CreditCard对象。

  (2) 用这些对象验证输入的数据

  (3) 用这些对象保存输入的数据。

  下面是对象间的交互顺序图:

 
Figure5: How a Client Would Normally Interact (Directly) with Subsystem Classes to Validate and Save the Customer Data

   在这个例子中应用外观模式是一个很好的设计,它可以降低客户和子系统组件(Address、Account和CreditCard)之间的耦合度。应用 外观模式,让我们定义一个外观类CustomerFacade (Figure6 and Listing5)。它为由客户数据处理类(Address、Account和CreditCard)所组成的子系统提供一个高层次的、简单的接口。

CustomerFacade
address:String
city:String
state:String
cardType:String
cardNumber:String
cardExpDate:String
fname:String
lname:String
setAddress(inAddress:String)
setCity(inCity:String)
setState(inState:String)
setCardType(inCardType:String)
setCardNumber(inCardNumber:String)
setCardExpDate(inCardExpDate:String)
setFName(inFName:String)
setLName(inLName:String)
saveCustomerData()

  
Figure6: Facade Class to Be Used by the Client in the Revised Design

  Listing5: CustomerFacade Class

public class CustomerFacade {
 private String address;
 private String city;
 private String state;
 private String cardType;
 private String cardNumber;
 private String cardExpDate;
 private String fname;
 private String lname;
 public void setAddress(String inAddress) {
  address = inAddress;
 }
 public void setCity(String inCity) {
  city = inCity;
 }
 public void setState(String inState) {
  state = inState;
 }
 public void setFName(String inFName) {
  fname = inFName;
 }
 public void setLName(String inLName) {
  lname = inLName;
 }
 public void setCardType(String inCardType) {
  cardType = inCardType;
 }
 public void setCardNumber(String inCardNumber) {
  cardNumber = inCardNumber;
 }
 public void setCardExpDate(String inCardExpDate) {
  cardExpDate = inCardExpDate;
 }
 public boolean saveCustomerData() {
  Address objAddress;
  Account objAccount;
  CreditCard objCreditCard;
  /*
   client is transparent from the following
   set of subsystem related operations.
  */
  boolean validData = true;
  String errorMessage = "";
  objAccount = new Account(fname, lname);
  if (objAccount.isValid() == false) {
   validData = false;
   errorMessage = "Invalid FirstName/LastName";
  }
  objAddress = new Address(address, city, state);
  if (objAddress.isValid() == false) {
   validData = false;
   errorMessage = "Invalid Address/City/State";
  }
  objCreditCard = new CreditCard(cardType, cardNumber, cardExpDate);
  if (objCreditCard.isValid() == false) {
   validData = false;
   errorMessage = "Invalid CreditCard Info";
  }
  if (!validData) {
   System.out.println(errorMessage);
   return false;
  }
  if (objAddress.save() && objAccount.save() && objCreditCard.save()) {
   return true;
  } else {
   return false;
  }
 }
}

   CustomerFacade类以saveCustomData方法的形式提供了业务层次上的服务。客户AccountManager不是直接和子系统 的每一个组件交互,而是使用了由CustomFacade对象提供的验证和保存客户数据的更高层次、更简单的接口(Figure7).

 
Figure7: Class Association with the Fa?ade Class in Place 。

  在新的设计中,为了验证和保存客户数据,客户需要:

  (1) 建立或获得外观对象CustomFacade的一个实例。

  (2) 传递数据给CustomFacade实例进行验证和保存。

  (3) 调用CustomFacade实例上的saveCustomData方法。

  CustomFacade处理创建子系统中必要的对象并且调用这些对象上相应的验证、保存客户数据的方法这些细节问题。客户不再需要直接访问任何的子系统中的对象。

  Figure8展示了新的设计的消息流图:

 
Figure 22.8: In the Revised Design, Clients Interact with the Fa?ade Instance to Interface with the Subsystem

  重要提示

  下面是应用外观模式的注意事项:

  (1) 在设计外观时,不需要增加额外的功能。

  (2) 不要从外观方法中返回子系统中的组件给客户。例如:有一个下面的方法:

  CreditCard getCreditCard()

  会报漏子系统的细节给客户。应用就不能从应用外观模式中取得最大的好处。

  (3)应用外观的目的是提供一个高层次的接口。因此,外观方法最适合提供特定的高层次的业务服务,而不是进行底层次的单独的业务执行。
posted @ 2006-09-03 00:07 Alex 阅读(477) | 评论 (0)编辑 收藏

小结:适配器模式用插座的适配器最为形象,插头是2口的,插座是3口的,中间的适配器就是同时支持2口和三口的。从对象的角度就是一般继承一个实现一个,总之,前方百计把两者都关联起来 。


通常,客户类(clients of class)通过类的接口访问它提供的服务。有时,现有的类(existing class)可以提供客户类的功能 需要,但是它所提供的接口不一定是客户类所期望的。这是由于现有的接口太详细或者缺乏详细或接口的名称与客户类所查找的不同等诸多不同原因导致的。

  在这种情况下,现有的接口需要转化(convert) 为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能。适配器模式 (Adapter Pattern)可以完成这样的转化。适配器模式建议定义一个包装类,包装有不兼容接口的对象。这个包装类指的就是适配器 (Adapter),它包装的对象就是适配者(Adaptee)。适配器提供客户类需要的接口,适配器接口的实现是把客户类的请求转化为对适配者的相应接 口的调用。换句话说:当客户类调用适配器的方法时,在适配器类的内部调用适配者类的方法,这个过程对客户类是透明的,客户类并不直接访问适配者类。因此, 适配器可以使由于借口不兼容而不能交互的类可以一起工作(work together)。

  在上面讨论的接口:

  (1)    不是指在JAVA编程语言中接口的概念,虽然类的接口可以通过JAVA借扩来定义。

  (2)    不是指由窗体和GUI控件所组成的GUI应用程序的用户接口。

  (3)    而是指类所报漏的,被其他类调用的编程接口,

  类适配器(Class Adapter)VS对象适配器(Object Adapter)

  适配器总体上可以分为两类??类适配器(Class Adapter)VS对象适配器(Object Adapter)
    

 类适配器:


  类适配器是通过继承类适配者类(Adaptee Class)实现的,另外类适配器实现客户类所需要的接口。当客户对象调用适配器类方法的时候,适配器内部调用它所继承的适配者的方法。
    

 对象适配器:

  对象适配器包含一个适配器者的引用(reference),与类适配器相同,对象适配器也实现了客户类需要的接口。当客户对象调用对象适配器的方法的时候,对象适配器调它所包含的适配器者实例的适当方法。

  下表是类适配器(Class Adapter)和对象适配器(Object Adapter)的详细不同

  
    
  例子:

  让我们建立一个验证给定客户地址的应用。这个应用是作为大的客户数据管理应用的一部分。

  让我们定义一个Customer类:

Customer 



Figure 20.1: Customer Class 
Listing 20.1: Customer Class 
  1. class Customer { 
  2.   public static final String US = "US"
  3.   public static final String CANADA = "Canada"
  4.   private String address; 
  5.   private String name; 
  6.   private String zip, state, type; 
  7.   public boolean isValidAddress() { 
  8.           … 
  9.           … 
  10.   } 
  11.   public Customer(String inp_name, String inp_address, 
  12.                   String inp_zip, String inp_state, 
  13.                   String inp_type) { 
  14.     name = inp_name; 
  15.     address = inp_address; 
  16.     zip = inp_zip; 
  17.     state = inp_state; 
  18.     type = inp_type; 
  19.   } 
  20. }//end of class 
   不同的客户对象创建Customer对象并调用(invoke)isValidAddress方法验证客户地址的有效性。为了验证客户地址的有效性, Customer类期望利用一个地址验证类(address validator class),这个验证类提供了在接口 AddressValidator中声明的接口。

  Listing 20.2: AddressValidator as an Interface 
  1. public interface AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state); 
  4. }//end of class 

  让我们定义一个USAddress的验证类,来验证给定的U.S地址。

  Listing 20.3: USAddress Class 
  1. class USAddress implements AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state) { 
  4.    if (inp_address.trim().length() < 10) 
  5.      return false
  6.    if (inp_zip.trim().length() < 5) 
  7.      return false
  8.    if (inp_zip.trim().length() > 10) 
  9.      return false
  10.    if (inp_state.trim().length() != 2) 
  11.      return false
  12.    return true
  13.   } 
  14. }//end of class 

  USAddress类实现AddressValidator接口,因此Customer对象使用USAddress实例作为验证客户地址过程的一部分是没有任何问题的。

  Listing 20.4: Customer Class Using the USAddress Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.  public boolean isValidAddress() { 
  5.    //get an appropriate address validator 
  6.    AddressValidator validator = getValidator(type); 
  7.    //Polymorphic call to validate the address 
  8.    return validator.isValidAddress(address, zip, state); 
  9.  } 
  10.  private AddressValidator getValidator(String custType) { 
  11.    AddressValidator validator = null
  12.    if (custType.equals(Customer.US)) { 
  13.      validator = new USAddress(); 
  14.    } 
  15.    return validator; 
  16.  } 
  17. }//end of class 
 


Figure 20.2: Customer/USAddress Validator?Class Association 

  但是当验证来自加拿大的客户时,就要对应用进行改进。这需要一个验证加拿大客户地址的验证类。让我们假设已经存在一个用来验证加拿大客户地址的使用工具类CAAddress。

从下面的CAAdress类的实现,可以发现CAAdress提供了客户类Customer类所需要的验证服务。但是它所提供的接口不用于客户类Customer所期望的。

  Listing 20.5: CAAdress Class with Incompatible Interface 
  1. class CAAddress { 
  2.   public boolean isValidCanadianAddr(String inp_address, 
  3.      String inp_pcode, String inp_prvnc) { 
  4.    if (inp_address.trim().length() < 15) 
  5.      return false
  6.    if (inp_pcode.trim().length() != 6) 
  7.      return false
  8.    if (inp_prvnc.trim().length() < 6) 
  9.      return false
  10.    return true
  11.   } 
  12. }//end of class 

  CAAdress类提供了一个isValidCanadianAddr方法,但是Customer期望一个声明在AddressValidator接口中的isValidAddress方法。

  接口的不兼容使得Customer对象利用现有的CAAdress类是困难的。一种意见是改变CAAdress类的接口,但是可能会有其他的应用正在使用CAAdress类的这种形式。改变CAAdress类接口会影响现在使用CAAdress类的客户。

  应用适配器模式,类适配器CAAdressAdapter可以继承CAAdress类实现AddressValidator接口。

 


  Figure 20.3: Class Adapter for the CAAddress Class 
Listing 20.6: CAAddressAdapter as a Class Adapter 
  1. public class CAAddressAdapter extends CAAddress 
  2.   implements AddressValidator { 
  3.   public boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state) { 
  5.     return isValidCanadianAddr(inp_address, inp_zip, 
  6.            inp_state); 
  7.   } 
  8. }//end of class 

   因为适配器CAAdressAdapter实现了AddressValidator接口,客户端对象访问适配器CAAdressAdapter对象是没 有任何问题的。当客户对象调用适配器实例的isValidAddress方法的时候,适配器在内部把调用传递给它继承的 isValidCanadianAddr方法。

  在Customer类内部,getValidator私有方法需要扩展,以至于它可以 在验证加拿大客户的时候返回一个CAAdressAdapter实例。返回的对象是多态的,USAddress和CAAddressAdapter都实现 了AddressValidator接口,所以不用改变。

Listing 20.7: Customer Class Using the CAAddressAdapter Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.   public boolean isValidAddress() { 
  5.     //get an appropriate address validator 
  6.     AddressValidator validator = getValidator(type); 
  7.     //Polymorphic call to validate the address 
  8.     return validator.isValidAddress(address, zip, state); 
  9.   } 
  10.   private AddressValidator getValidator(String custType) { 
  11.     AddressValidator validator = null
  12.     if (custType.equals(Customer.US)) { 
  13.       validator = new USAddress(); 
  14.     } 
  15.     if (type.equals(Customer.CANADA)) { 
  16.       validator = new CAAddressAdapter(); 
  17.     } 
  18.     return validator; 
  19.   } 
  20. }//end of class 
  CAAddressAdapter设计和对AddressValidator(声明期望的接口)对象的多态调用使Customer可以利用接口不兼容CAAddress类提供的服务。

 


  Figure 20.4: Address Validation Application?Using Class Adapter 

 


  Figure 20.5: Address Validation Message Flow?Using Class Adapter 

  作为对象适配器的地址适配器

   当讨论以类适配器来实现地址适配器时,我们说客户类期望的AddressValidator接口是Java接口形式。现在,让我们假设客户类期望 AddressValidator接口是抽象类而不是java接口。因为适配器CAAdapter必须提供抽象类AddressValidatro中声明 的接口,适配器必须是AddressValidator抽象类的子类、实现抽象方法。
  1. Listing 20.8: AddressValidator as an Abstract Class 
  2. public abstract class AddressValidator { 
  3.   public abstract boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state); 
  5. }//end of class 
  6. Listing 20.9: CAAddressAdapter Class 
  7. class CAAddressAdapter extends AddressValidator { 
  8.           … 
  9.           … 
  10.   public CAAddressAdapter(CAAddress address) { 
  11.     objCAAddress = address; 
  12.   } 
  13.   public boolean isValidAddress(String inp_address, 
  14.      String inp_zip, String inp_state) { 
  15.           … 
  16.           … 
  17.   } 
  18. }//end of class 

  因为多继承在JAVA中不支持,现在适配器CAAddressAdapter不能继承现有的CAAddress类,它已经使用了唯一一次继承其他类的机会。

  应用对象适配器模式,CAAddressAdapter可以包含一个适配者CAAddress的一个实例。当适配器第一次创建的时候,这个适配者的实例通过客户端传递给适配器。通常,适配者实例可以通过下面两种方式提供给包装它的适配器。

  (1)    对象适配器的客户端可以传递一个适配者的实例给适配器。这种方式在选择类的形式上有很大的灵活性,但是客户端感知了适配者或者适配过程。这种方法在适配器不但需要适配者对象行为而且需要特定状态时很适合。

  (2)    适配器可以自己创建适配者实例。这种方法相对来说缺乏灵活性。适用于适配器只需要适配者对象的行为而不需要适配者对象的特定状态的情况。

 


  Figure 20.6: Object Adapter for the CAAddress Class 

  Listing 20.10: CAAddressAdapter as an Object Adapter 
  1. class CAAddressAdapter extends AddressValidator { 
  2.   private CAAddress objCAAddress; 
  3.   public CAAddressAdapter(CAAddress address) { 
  4.     objCAAddress = address; 
  5.   } 
  6.   public boolean isValidAddress(String inp_address, 
  7.      String inp_zip, String inp_state) { 
  8.     return objCAAddress.isValidCanadianAddr(inp_address, 
  9.            inp_zip, inp_state); 
  10.   } 
  11. }//end of class 

  当客户对象调用CAAddressAdapter(adapter)上的isValidAddress方法时, 适配器在内部调用CAAddress(adaptee)上的isValidCanadianAddr方法。


 


  Figure 20.7: Address Validation Application?Using Object Adapter 

  从这个例子可以看出,适配器可以使Customer(client)类访问借口不兼容的CAAddress(adaptee)所提供的服务!

 



  Figure 20.8: Address Validation Message Flow?Using Object Adapter
posted @ 2006-09-03 00:02 Alex 阅读(1031) | 评论 (0)编辑 收藏

Template模板模式定义:
定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.

使用Java的抽象类时,就经常会使用到Template模式,因此Template模式使用很普遍.而且很容易理解和使用。

 

public abstract class Benchmark
{
  /**
  * 下面操作是我们希望在子类中完成
  */
  public abstract void benchmark();

  /**
  * 重复执行benchmark次数
  */
  public final long repeat (int count) {
    if (count <= 0)
      return 0;
    else {
      long startTime = System.currentTimeMillis();

    for (int i = 0; i < count; i++)
      benchmark();

    long stopTime = System.currentTimeMillis();
    return stopTime - startTime;
  }
}
}

在上例中,我们希望重复执行benchmark()操作,但是对benchmark()的具体内容没有说明,而是延迟到其子类中描述:

public class MethodBenchmark extends Benchmark
{
  /**
  * 真正定义benchmark内容
  */
  public void benchmark() {

    for (int i = 0; i < Integer.MAX_VALUE; i++){
      System.out.printtln("i="+i);    
    }
  }
}

至此,Template模式已经完成,是不是很简单?

我们称repeat方法为模板方法, 它其中的benchmark()实现被延迟到子类MethodBenchmark中实现了,

看看如何使用:

Benchmark operation = new MethodBenchmark();
long duration = operation.repeat(Integer.parseInt(args[0].trim()));
System.out.println("The operation took " + duration + " milliseconds");

 

也许你以前还疑惑抽象类有什么用,现在你应该彻底明白了吧? 至于这样做的好处,很显然啊,扩展性强,以后Benchmark内容变化,我只要再做一个继承子类就可以,不必修改其他应用代码.

posted @ 2006-09-02 23:16 Alex 阅读(300) | 评论 (0)编辑 收藏

Command模式是最让我疑惑的一个模式,我在阅读了很多代码后,才感觉隐约掌握其大概原理,我认为理解设计模式最主要是掌握起原理构造,这样才 对自己实际编程有指导作用.Command模式实际上不是个很具体,规定很多的模式,正是这个灵活性,让人有些confuse.

Command定义
n 将来自客户端的请求传入一个对象,无需了解这个请求激活的 动作或有关接受这个请求的处理细节。

这是一种两台机器之间通讯联系性质的模式,类似传统过程语 言的 CallBack功能。

优点:
解耦了发送者和接受者之间联系。 发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。

不少Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作.

将 这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式,换句话说,本来用户(调用者)是直接调用这些命令的,如菜 单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔 离,基本没有关系了.

显然这样做的好处是符合封装的特性,降低耦合度,Command是将对行为进行封装的典型模式,Factory是将创建进行封装的模式,
从Command模式,我也发现设计模式一个"通病":好象喜欢将简单的问题复杂化, 喜欢在不同类中增加第三者,当然这样做有利于代码的健壮性 可维护性 还有复用性.

如何使用?
具体的Command模式代码各式各样,因为如何封装命令,不同系统,有不同的做法.下面事例是将命令封装在一个Collection的List中,任何 对象一旦加入List中,实际上装入了一个封闭的黑盒中,对象的特性消失了,只有取出时,才有可能模糊的分辨出:

典型的Command模式需要有一个接口.接口中有一个统一的方法,这就是"将命令/请求封装为对象":
public interface Command {
  public abstract void execute ( );
}

具体不同命令/请求代码是实现接口Command,下面有三个具体命令
public class Engineer implements Command {

  public void execute( ) {
    //do Engineer's command
  }
}

public class Programmer implements Command {

  public void execute( ) {
    //do programmer's command
  }
}

public class Politician implements Command {

  public void execute( ) {
    //do Politician's command
  }
}


按照通常做法,我们就可以直接调用这三个Command,但是使用Command模式,我们要将他们封装起来,扔到黑盒子List里去:

public class producer{
  public static List produceRequests() {
    List queue = new ArrayList();
    queue.add( new DomesticEngineer() );
    queue.add( new Politician() );
    queue.add( new Programmer() );
    return queue;
  }

}

这三个命令进入List中后,已经失去了其外表特征,以后再取出,也可能无法分辨出谁是Engineer 谁是Programmer了,看下面客户端如何调用Command模式:

public class TestCommand {
  public static void main(String[] args) {
    
    List queue = Producer.produceRequests();
    for (Iterator it = queue.iterator(); it.hasNext(); )
      
 //客户端直接调用execute方法,无需知道被调用者的其它更多类的方法名。
    ((Command)it.next()).execute();
  

  }
}

由此可见,调用者基本只和接口打交道,不合具体实现交互,这也体现了一个原则,面向接口编程,这样,以后增加第四个具体命令时,就不必修改调用者TestCommand中的代码了.

理解了上面的代码的核心原理,在使用中,就应该各人有自己方法了,特别是在如何分离调用者和具体命令上,有很多实现方法,上面的代码是使用"从List过一遍"的做法.这种做法只是为了演示.

使用Command模式的一个好理由还因为它能实现Undo功能.每个具体命令都可以记住它刚刚执行的动作,并且在需要时恢复.

Command模式在界面设计中应用广泛.Java的Swing中菜单命令都是使用Command模式,由于Java在界面设计的性能上还有欠缺,因此界面设计具体代码我们就不讨论,网络上有很多这样的示例.

参考:
http://www.patterndepot.com/put/8/command.pdf

http://www.javaworld.com/javaworld/javatips/jw-javatip68.html

posted @ 2006-08-31 21:46 Alex 阅读(264) | 评论 (0)编辑 收藏


Your Ad Here

小结

本章主要介绍了一个基于 J2EE Web 技术进行设计开发的论坛系统,通过这个系统的剖析,能够了解和掌握 GOF 设计模式,学会 Java 实战中一些处理技巧和技术。

使用 GOF 设计模式的主要优点:使得复杂系统的架构变得更加清晰而且有条理,而这一点正是许多程序员在开发实用系统中所缺乏的,可能导致的结果是大大降低 Java 系统可维护性以及可拓展性,重新回到了传统编程语言的陷阱中。

因此, GOF 设计模式对于 Java 设计编程的重要性是无论怎么强调也不过分,它能够帮助程序员更加深入地理解 Java 完全面向对象特性,从而以真正的面向对象设计概念进行实用系统的设计和开发。

Jive 系统是一个完全的 Web 系统,整个系统的最大特点是自我定制实现,它为了提高数据库的访问性能,使用了自己开发的数据库连接池;为了提高系统的数据处理系统,它使用了缓存机制;为了实现用户安全管理机制,它使用 Proxy 模式实现了角色权限的定位和检查等。这些模块功能在很多系统中都是需要的,但是如果想从 Jive 系统提炼出这些模块功能以达到重用,又是非常困难的。

因此,开发者需要一种具有一定高度的框架技术。在这个框架技术中,所有这些通用技术都能够自动实现,无需再自行设计和开发,能够将更多精力投入到与业务有关的特定功能开发中。 J2EE EJB 技术实际就是这种框架技术。

学习和研究 Jive 论坛系统也非常有助于程序员学习和理解 EJB J2EE 完整的框架技术,因为它们的目的都是一样,只不过实现的途径不一样而已。

posted @ 2006-08-31 12:29 Alex 阅读(418) | 评论 (0)编辑 收藏

7  Jive 安装调试运行

Jive 默认编码方式是 ISO8859_1 。下面以安装在英文 Linux 操作系统上为例:

1 )安装数据库。在 database 目录中选择对应数据库,如果数据库是 MySQL ,则使用 jive_mysql.sql ,然后通过数据库的管理工具将 jive_mysql.sql 导入相应数据库中。 Jive 所需要的数据库都已经准备完成。

2 )由于 Jive Servlet/JSP 系统,必须在 Web 容器支持下才能运行。因此,必须安装 Web 服务器软件,例如 Tomcat 等。

最后需要将 Jive 部署到 Tomcat 中。有两种办法,其中直接复制的办法比较简单,修改 server.xml 办法可以见其他章节介绍。

Jive application 目录下所有文件复制到 Tomcat 应用目录 webapps 的指定目录下,如 Tomcat/webapps/jive 下。

3 )在运行设置 Jive 之前,还需要设定 Jive 的初始值,在上述应用目录的 WEB-INF/classes/ 下,创建或编辑 jive_init.properties ,加入行:

jiveHome=C:\\javasource\\jive\\WEB-INF\\jiveHome

这是指定 jivHome 的目录, jiveHome 主要放置 Jive 的配置文件和搜索等内部工作文件,一般是建立在 WEB-INF 目录下,这样, jiveHome 的内容就不会被外界通过浏览器直接访问到,要注意是绝对路径。

4 )可以安装设置 Jive 系统。通过浏览输入网址: http://localhost:8080/jive/admin/setup/ Jive 的安装导航程序会自动进行安装检查。由于 Jive 编码是 ISO8859_1 ,如果涉及需要设置 Java 编码方式的提问,都设置为英文。

管理员密码最好更改一下,默认用户名和密码是 admin admin

全部设置完成后,进入 http://localhost:8080/jive/ 可以浏览。

进入 http://localhost:8080/jive/admin 可以实现论坛管理,可以设置全局过滤器,一般设置如下过滤器:

·          TextStyle 文本格式。

·          Newline 新一行。

·          Profanity 过渡亵渎或不恰当词语。

·          URLConverter URLConverter

·          ImageFilter ImageFilter 支持上传图片。        

·          CodeHighlighter CodeHighlighter

另外,缓存设置也很重要,可根据访问量、缓存击中率以及实际内存大小调整,可以提高论坛的性能。

图片上传路径的设置需要通过手工修改 jiveHome 下的 jive_config.xml 。打开 jive_config.xml 会发现,所有在管理配置中配置的信息都保存在这里,找到下列配置:

<upload>

        <dir>/home/bqlr/jive/upload/</dir>

        <relurl>upload/</relurl>

</upload>

其中, dir 是上传图片存放的绝对路径; relurl 是网址的相对路径,比如 upload/ ,那就表示输入下列网址:“ http://localhost:8080/jive/upload/xxxx.jpg ”,就可以看到上传的图片。因为上传图片有自动缩小功能,因此需要 Linux 安装时是带图形 X86 的完全安装。用户如为中文名,将影响图片文件名。

以上是 Jive 论坛的安装

posted @ 2006-08-31 12:29 Alex 阅读(757) | 评论 (0)编辑 收藏

6  Jive 图形处理

Jive 提供了强大的论坛功能,但是有些功能离实际需求还是有一定的距离,例如论坛是用于信息交流讨论的场所。而信息不只是文字,有时还包括图片或一些 PDF 等类型的文件,那么如何在 Jive 中实现这样的功能呢?

6.1  图片上传处理

HTML 中,使用表单 Form 主要是用来向服务器提交数据,格式如下:

FORM ACTION="URL"

METHOD="GET|POST"

ENCTYPE="…" TARGET="..."

. . .

/FORM

enctype 指定了表单提交给服务器时的内容形式( Content-Type ),默认值是 "application/x-www-form-urlencoded" ,这时,表单信息一般采用 URL 编码制。

但是,当向服务器传送图片或文件这样包含非 ASCII 字符或二进制数的数据时,根据 RFC1867 规定,就必须使用“ multipart/form-data ”内容类型。

其实无论是默认表单信息,还是图片文件,这些内容都是装载在 HTTP 协议的正文内容部分,都可以看成 HTTP 协议携带的对象,只是两种正文内容形式不一样。前者是 String 字符串类型,而后者则是一个通用的数据对象类型( Object )。在以后章节中将专门讨论 HTTP 协议装载数据对象的底层细节。

使用“ multipart/form-data ”上传文件的格式写法如下:

FORM ACTION="URL" METHOD="GET|POST" ENCTYPE=" multipart/form-data "

  <INPUT TYPE FILE NAME file1>

/FORM

文件通过 HTTP 协议传送到服务器端后,需要在服务器端对该文件进行专门的接受。 HttpServletRequest 没有提供直接获取文件数据的方法,因此需要开发专门的服务器文件处理组件。

目前有两种上传文件处理组件:一种是基于完全 JSP 结构的,使用 JSP 来处理上传的文件,以 SmartUpload http://www.jspsmart.com )最常用;还有一种是完全的 JavaBeans 组件: Cos 文件上传组件包( http://www.servlets.com/cos/index.html ), Cos 可以使用在 JSP 中,也可以使用在 Servlet Servlet Filter 中。

由于在实际应用中,文件上传功能往往和其他正常表单参数一起混合使用,而不是独立使用的。因此,可以设定一个 Servlet 专门用来处理这类混合表单的请求,在将文件接受处理后,自动导向到处理表单正常参数的 JSP/Servlet 去处理。

表单调用示例如下:

<form action="<%=request.getContextPath()%>/multipartformserv"

     method="post" enctype="multipart/form-data">

   <input type="hidden" name="FORWARDNAME" value="login.jsp" > 

   <input type="file" name="file1" >

   <input type="hidden" name="maxwidth" value="120" >

   <input type="hidden" name="maxheight" value="60" >

   <input type="text" name="username" >

   <input type="text" name="password" >

</form>

在这个表单中,既有文件提交,也有 username 这样正常的参数需要提交,提交的 Servlet 名为 multipartformserv 。由 multipartformserv 来处理上传的文件,然后再自动转交到 FORWARDNAME 的值 login.jsp 进行 username 等正常参数的处理。

在表单中,如果设定 maxwidth maxiheight ,那么表示如果上传的图片超过这个尺寸,服务器将缩小图片到这个尺寸。

编制一个专门用来统一处理 Jive 系统中所有文件处理的 Servlet ,这样有利于简化系统。编制 MultipartFormServ 如下:

public class MultipartFormServ extends HttpServlet {

  static final private String CONTENT_TYPE = "text/html";

  // 文件上传处理

  private static MultipartFormHandle mf = MultipartFormHandle.getInstance();

  // 文件上传后存放的临时目录

  private String dirName;

  private ServletContext context;

  public void init() throws ServletException {

    // web.xml 中读取上传目录参数

    dirName = mf.getUploaddir();

    if (dirName == null) {

      throw new ServletException("Please supply uploadDir parameter");

    }

  }

 

  // 处理带有文件内容的请求

  public void doPost(HttpServletRequest request, HttpServletResponse response)

         throws ServletException, IOException {

    try {

       mf.init(dirName,request); // 调用文件上传处理器处理

       // 得到 FORWARDNAME 参数值

       String forward=mf.getForwardProgram();

      if (forward.equals(""))

       {

         errorMessage("no forward program", response);

         return;

      }

      String param=mf.getForwardProgramParam();

      mf.clear();

       // 引导分流到 forward 参数值进行其他文本参数处理

      getServletConfig().getServletContext().

           getRequestDispatcher("/"+forward+"?"+param).forward(request, response);

 

    }catch (Exception Ex) {

          throw new ServletException(Ex.getMessage());

    }

  }

3-8  上传文件处理框架

}

MultipartFormHandler 主要调用 Cos 组件处理上传文件,对上传文件图形大小进行处理,然后将图形搬迁到指定的目录;同时也将请求信号中的正常参数提取出来,以作为转发使用。

这样一个图形或文件上传系统已经形成了一个框架结构,可以重复使用在任何需要图片或文件上传处理的系统中,如图 3-8 所示。

3-8 中上传文件处理额 Servlet 相当于请求信号 Request 的一个过滤器,既然正常的 Request 处理机制不能从 Request 提取携带的文件,那么,使用一个过滤器先将文件提取出来,剩余的再通过正常 Request 处理机制去处理。

6.2  服务器端图形处理

Java 最初是以 Applet 等客户端图形处理为技术起点的,而本节讨论的是如何在 Servlet/JSP 中实现图形处理。

Jive 中,图片可以用来显示用户的头像,用户在上传自己头像图片时,该图片的大小可能不一,但是由于版面原因,显示的头像图片又有大小限制,那么就需要在用户上传图片时对图片大小做一个检查。如果超过规定大小,就进行一定的缩放处理。

缩放处理有两种方式:是在 HTML 显示时,使用 image 语法的 width height 来 限制大小,但是这样做只是解决了表面问题,无法解决大字节图片传送到客户端带来的性能影响,这个图片因为是用户发言的头像,将会在每个帖子里面显示。如果 头像都是巨大图片,对帖子显示速度的影响是很大,因此必须在服务器端进行缩小后,再传送到客户端,这样提高了论坛系统性能。

服务器端的图形处理需要使用到 Java 的图形处理技术,而且图形处理是在服务器端的 Web 容器中进行的。和以往 Java 在客户端进行图形处理稍微有所不同,相同的是都要使用计算机的底层图形支持资源。

J2SE 1.4 提供新的增强的图形处理功能, JDK1.4 中最新的 javax.imageio.ImageIO 对图片进行读写操作,而使用 java.awt.geom.AffineTransform 对图片进行尺寸缩放处理。

import java.io.File;

import java.awt.image.BufferedImage;

import java.awt.Image;

import java.awt.image.AffineTransformOp;

import javax.imageio.ImageIO;

import java.awt.geom.AffineTransform;

 

public class UploadImg{

     /**

     * 参数设置

     * @param fromdir 图片的原始目录

     * @param todir 处理后的图片存放目录

     * @param imgfile 原始图片

     * @param sysimgfile 处理后的图片文件名前缀

     */

           public void init(String fromdir,String todir,String imgfile,String sysimgfile)

           {

                  this.fromdir=fromdir;

                  this.todir=todir;

                  this.imgfile=imgfile;

                  this.sysimgfile=sysimgfile;

             }

    …

    public boolean CreateThumbnail() throws Exception

    {

        //ext 是图片的格式 gif JPG png

        String ext=""

        double Ratio=0.0;

        File oldFile = new File(fromdir,imgfile);

        if (!F.isFile())  // 检查是否存在此图片文件

             throw new Exception(F+" is not image file error in CreateThumbnail!");

 

         // 首先判断上传的图片是 gif 还是 JPG ImageIO ,只能将 gif 转换为 png

         if (isJpg(imgfile)){

            ext="jpg";

        }else{

           ext="png";

        }

        File newFile = new File(todir,sysimgfile+"."+ext);

 

        BufferedImage Bi = ImageIO.read(oldFile);  // 读取原始图片

        if ((Bi.getHeight()>120) || (Bi.getWidth()>120)){

            if (Bi.getHeight()>Bi.getWidth())

              Ratio = 120.0/Bi.getHeight();

            else

              Ratio = 120.0/Bi.getWidth();

       }

       // 进行图片转换

       AffineTransformOp op =

          new AffineTransformOp(AffineTransform.getScaleInstance(Ratio, Ratio), null);

       Image itemp = op.filter(Bi, null);

 

       try { // 写入转换后的图片

           ImageIO.write((BufferedImage) itemp, ext, newFile);

       }catch (Exception ex) {

            throw new Exception(ex.getMessage());

       }

       return (true);

   }

}

该类中由于使用到了 Java AWT ,虽然没有实际显示,但 Linux 系统下需要 X11 Windows 的支持(安装 Linux 时需安装 XFree86 Linux 完全安装方式包括安装 XFree86 )。

该缩放功能是在图片上传到服务器后再进行的处理,可以对 JPG 进行缩小放大;对上传是 GIF 的图片,缩放后变成 PNG 图片格式文件。

posted @ 2006-08-31 12:28 Alex 阅读(420) | 评论 (0)编辑 收藏

     摘要: 5  Jive 的其他组件技术 Jive 是一个比较丰富的知识宝藏,从中可以学习到很多新的实战技巧和具体功能实现方式。前面基本介绍了 Jive 中的一些主要架构技术,通过这些技术可以基本上掌握 Jive 论坛系统。 Jive 中还有很多非常实用的组件技术和工具库,分...  阅读全文
posted @ 2006-08-31 12:27 Alex 阅读(640) | 评论 (0)编辑 收藏

4  Jive 的缓存机制

Jive 论坛的一个主要特点就是其性能速度快,因此很多巨大访问量的网站都采用了 Jive 论坛。这些都是由于 Jive 采取了高速缓存机制。

缓存( Cache )机制是提高系统运行性能必不可少的技术。缓存机制从原理上讲比较简单,就是在原始数据第一次读取后保存在内存中,下次读取时,就直接从内存中读取。原始数据有可能保存在持久化介质或网络上。缓存机制也是代理模式的一种实现。

4.1  缓存原理和实现

Jive Cache 总体来说实现得不是非常精简和有效。它是针对每个具体数据对象逐个实现缓冲,这种“穷尽”的办法不是实践所推荐的用法。通过使用动态代理模式,可以根据具体方法的不同来实现缓存是值得推荐的做法。 Jive 的缓存实现得比较简单,可以用来学习和研究缓存机制。

Jive 中的 Cache 实现了缓存机制的大部分行为,它是将对象用惟一的关键字 Key 作标识保存在 HashMap Hashtable 中。当然,必须知道这些对象的大小,这个前提条件的设定可以保证缓存增长时不会超过规定的最大值。

如果缓存增长得太大,一些不经常被访问的对象将首先从缓存中删除。如果设置了对象的最大生命周期时间,即使这个对象被反复频繁访问,也将从缓存中删除。这个特性可以适用于一些周期性需要刷新的数据,如来自数据库的数据。

Cach 中除了 getObject() 方法的性能依据缓存大小,其他方法的性能都是比较快的。一个 HashMap 用来实现快速寻找,两个 LinkedList 中一个以一定的访问顺序来保存对象,叫 accessed LinkedList ;另外一个以它们加入缓存的顺序保存这些对象,这种保存对象只是保存对象的引用,叫 age LinkedList 。注意,这里的 LinkedList 不是 JDK 中的 LinkedList ,而是 Jive 自己定义的 LinkedList

当对象被加入缓存时,首先被 CacheObject 封装。封装有以下信息:对象大小(以字节计算),一个指向 accessed LinkedList 的引用,一个指向 age LinkedList 的引用。

当从缓存中获取一个对象如 ObjectA 时,首先, HashMap 寻找到指向封装 ObjectA 等信息的 CacheObject 对象。然后,这个对象将被移动到 accessed LinkedList 的前面,还有其他一些动作如缓存清理、删除、过期失效等都是在这个动作中一起触发实现的。

public class Cache implements Cacheable {

    /**

     * 因为 System.currentTimeMillis() 执行非常耗费性能,因此如果 get 操作都执行

* 这条语句将会形成性能瓶颈, 通过一个全局时间戳来实现每秒更新

* 当然,这意味着在缓存过期时间计算上有一到几秒的误差

     */

    protected static long currentTime = CacheTimer.currentTime;

    //CacheObject 对象

    protected HashMap cachedObjectsHash;

    //accessed LinkedList 最经常访问的排列在最前面

    protected LinkedList lastAccessedList;

    // 以缓存加入顺序排列,最后加入排在最前面;越早加入的排在最后面

    protected LinkedList ageList;

    // 缓存最大限制 默认是 128k 可根据内存设定,越大性能越高

    protected int maxSize =  128 * 1024;

    // 当前缓存的大小

    protected int size = 0;

    // 最大生命周期时间,默认是没有

    protected long maxLifetime = -1;

    // 缓存的击中率,用于评测缓存效率

    protected long cacheHits, cacheMisses = 0L;

 

    public Cache() {

        // 构造 HashMap. 默认 capacity 11

        // 如果实际大小超过 11 HashMap 将自动扩充,但是每次扩充都

// 是性能开销,因此期初要设置大一点

        cachedObjectsHash = new HashMap(103);

        lastAccessedList = new LinkedList();

        ageList = new LinkedList();

    }

    public Cache(int maxSize) {

        this();

        this.maxSize = maxSize;

    }

    public Cache(long maxLifetime) {

        this();

        this.maxLifetime = maxLifetime;

    }

    public Cache(int maxSize, long maxLifetime) {

        this();

        this.maxSize = maxSize;

        this.maxLifetime = maxLifetime;

    }

    public int getSize() {        return size;    }

    public int getMaxSize() {        return maxSize;    }

 

    public void setMaxSize(int maxSize) {

        this.maxSize = maxSize;

        // 有可能缓存大小超过最大值,需要激活删除清理动作

        cullCache();

    }

    public synchronized int getNumElements() {

        return cachedObjectsHash.size();

    }

 

    /**

     * 增加一个 Cacheable 对象

* 因为 HashMap 不是线程安全的,所以操作方法要使用同步

* 如果使用 Hashtable 就不必同步

     */

    public synchronized void add(Object key, Cacheable object) {

        // 删除已经存在的 key

        remove(key);

        int objectSize = object.getSize();

        // 如果被缓存对象的大小超过最大值,就放弃

        if (objectSize > maxSize * .90) {            return;        }

        size += objectSize;

        // 创建一个 CacheObject 对象

        CacheObject cacheObject = new CacheObject(object, objectSize);

        cachedObjectsHash.put(key, cacheObject);  // 保存这个 CacheObject

        // 加入 accessed LinkedList Jive 自己的 LinkedList 在加入时可以返回值

        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);

        // 保存引用

        cacheObject.lastAccessedListNode = lastAccessedNode;

        // 加入到 age LinkedList

        LinkedListNode ageNode = ageList.addFirst(key);

        // 这里直接调用 System.currentTimeMillis(); 用法值得讨论

        ageNode.timestamp = System.currentTimeMillis();

        // 保存引用

        cacheObject.ageListNode = ageNode;

        // 做一些清理工作

        cullCache();

    }

    /**

     * 从缓存中获得一个被缓存的对象,这个方法在下面两种情况返回空

     *    <li> 该对象引用从来没有被加入缓存中

     *    <li> 对象引用因为过期被清除 </ul>

     */

    public synchronized Cacheable get(Object key) {

        // 清除过期缓存

        deleteExpiredEntries();

        // Key 从缓存中获取一个对象引用

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);

        if (cacheObject == null) {

            // 不存在,增加未命中率

            cacheMisses++;

            return null;

        }

        // 存在,增加命中率

        cacheHits++;

        // accessed LinkedList 中将对象从当前位置删除

        // 重新插入在第一个

        cacheObject.lastAccessedListNode.remove();

        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

        return cacheObject.object;

    }

    …

}

Cache 中,关键字 Key 是一个对象,为了再次提高性能,可以进一步将 Key 确定为一个 long 类型的整数。

4.2  缓存使用

建立 LongCache 只是为了提高原来的 Cache 性能,本身无多大意义,可以将 LongCache 看成与 Cache 一样的类。

LongCache 的关键字 Key Forum ForumThread 以及 ForumMessage long 类型的 ID ,值 Value Forum ForumThread 以及 ForumMessage 等的对象。这些基本是通过 DatabaseCacheManager 实现完成,在主要类 DbForumFactory 的初始化构造时,同时构造了 DatabaseCacheManager 的实例 cacheManager

前面过滤器功能分析中, Message 对象获得方法的第一句如下:

protected ForumMessage getMessage(long messageID, long threadID, long forumID) throws

      ForumMessageNotFoundException {

    DbForumMessage message = cacheManager.messageCache.get(messageID);

    …

}

其中, cacheManager DatabaseCacheManager 的实例, DatabaseCacheManager 是一个缓存 Facade 类。在其中包含了 5 种类型的缓存,都是针对 Jive 5 个主要对象, DatabaseCacheManager 主要代码如下:

public class DatabaseCacheManager {

    public UserCache userCache;                          // 用户资料缓存

    public GroupCache groupCache;                       // 组资料缓存

    public ForumCache forumCache;                       //Forum 论坛缓存

    public ForumThreadCache threadCache;                //Thread 主题缓存

    public ForumMessageCache messageCache;          //Message 缓存

    public UserPermissionsCache userPermsCache;     // 用户权限缓存

 

    public DatabaseCacheManager(DbForumFactory factory) {

        …

        forumCache =

            new ForumCache(new LongCache(forumCacheSize, 6*HOUR), factory);

        threadCache =

            new ForumThreadCache(

                  new LongCache(threadCacheSize, 6*HOUR), factory);

        messageCache = new ForumMessageCache(

                  new LongCache(messageCacheSize, 6*HOUR), factory);

        userCache = new UserCache(

                  new LongCache(userCacheSize, 6*HOUR), factory);

        groupCache = new GroupCache(

                  new LongCache(groupCacheSize, 6*HOUR), factory);

        userPermsCache = new UserPermissionsCache(

                new UserPermsCache(userPermCacheSize, 24*HOUR), factory

        );

    }

    …

}

从以上代码看出, ForumCache 等对象生成都是以 LongCache 为基础构建的,以 ForumCache 为例,代码如下:

public class ForumCache extends DatabaseCache {

    // Cache 构建 ID 缓存

    protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);

    // LongCache 构建整个对象缓存

    public ForumCache(LongCache cache, DbForumFactory forumFactory) {

        super(cache, forumFactory);

    }

 

    public DbForum get(long forumID) throws ForumNotFoundException {

        …

        DbForum forum = (DbForum)cache.get(forumID);

        if (forum == null) {    // 如果缓存没有从数据库中获取

            forum = new DbForum(forumID, factory);

            cache.add(forumID, forum);

        }

        return forum;

    }

 

public Forum get(String name) throws ForumNotFoundException {

         // name key ,从 forumIDCache 中获取 ID

 CacheableLong forumIDLong = (CacheableLong)forumIDCache.get(name);

        if (forumIDLong == null) { // 如果缓存没有 从数据库获得

            long forumID = factory.getForumID(name);

            forumIDLong = new CacheableLong(forumID); // 生成一个缓存对象

            forumIDCache.add(name, forumIDLong);

        }

        return get(forumIDLong.getLong());

    }

    …

}

由此可以看到, LongCache 封装了 Cache 的核心功能,而 ForumCache 等类则是在 LongCache 核心外又包装了与应用系统相关的操作,这有点类似装饰( Decorator )模式。

从中也可以看到 Cache LongCache 两种缓存的用法。

使用 Cache 时的关键字 Key 是任何字段。如上面代码中的 String name ,如果用户大量帖子主题查询中, Key query + blockID ,见 DbForum 中的 getThreadBlock 方法;而值 Value 则是 Long 类型的 ID ,如 ForumID ThreadID 等。

LongCache 的关键字 Key Long 类型的 ID ,如 ForumID ThreadID 等;而值 Value 则是 Forum ForumThread ForumMessage 等主要具体对象。

在实际使用中,大多数是根据 ID 获得对象。但有时并不是这样,因此根据应用区分了两种 Cache ,这其实类似数据库的数据表,除了主关键字外还有其他关键字。

4.3  小结

缓存中对象是原对象的映射,如何确保缓存中对象和原对象的一致性?即当原对象发生变化时,缓存中的对象也必须立即更新。这是缓存机制需要解决的另外一个基本技术问题。

Jive 中是在原对象发生变化时,立即进行清除缓存中对象,如 ForumMessage 对象的创建。在 DbForumThread AddMessage 方法中有下列语句:

factory.cacheManager.threadCache.remove(this.id);

factory.cacheManager.forumCache.remove(this.forumID);

即当有新的帖子加入时,将 ForumThreadCache ForumCache 相关缓冲全部清除。这样,当有相关对象读取时,将直接从数据库中读取,这是一种非常简单的缓存更新方式。

在复杂的系统,例如有一台以上的服务器运行着 Jive 系统。如果一个用户登陆一台服务器后,通过这台服务器增加新帖。那么按照上述原理,只能更新本服务器 JVM 中的缓存数据,而其他服务器则无从得知这种改变,这就需要一种分布式的缓存机制。

3-7  Jive 主要对象的访问

到目前可以发现 整个 Jive 系统其实是围绕 Forum ForumThread ForumMessage 等这些主要对象展开的读取、修改或创建等操作。由于这些对象原先持久化保存在数据库中,为了提高性能和加强安全性, Jive 在这些对象外面分别实现两层包装,如图 3-7 所示。

客户端如果需要访问这些对象,首先要经过它们的代理对象。进行访问权限的检查,然后再从缓存中获取该对象。只有缓存不存在时,才会从数据库中获取。

这套机制是大多数应用系统都面临的必须解决的基本功能,因此完全可以做成一个通用的可重复使用的框架。这样在具体应用时,不必每个应用系统都架设开发这样的机制。其实 EJB 就是这样一套框架,实体 Bean 都由缓存机制支持,而通过设定 ejb-jar.xml 可以实现访问权限控制,这些工作都直接由 EJB 容器实现了,不必在代码中自己来实现。剩余的工作是调整 EJB 容器的参数,使之适合应用系统的具体要求,这些将在以后章节中讨论。

Jive 中,图 3-7 的机制是通过不同方式实现的。基本上是一配二模式:一个对象有一个缓冲对象和一个代理对象,这样做的一个缺点是导致对象太多,系统变得复杂。这点在阅读 Jive 源码时可能已经发现。

如果建立一个对象工厂,工厂内部封装了图 3-7 机制实现过程,客户端可以根据不同的工厂输入参数获得具体不同的对象。这样也许代码结构要更加抽象和紧凑, Java 的动态代理 API 也许是实现这个工厂的主要技术基础。有兴趣者可以进一步研究提炼。

posted @ 2006-08-31 12:27 Alex 阅读(428) | 评论 (0)编辑 收藏

3  Jive 安全管理机制

Jive 中除了前面介绍的有关设计模式实现组件外,还有其他有一定特点的组件功能,分析研究这些组件功能可以更加完整透彻地理解 Jive 论坛系统。

Jive 安全管理机制基本是由下列部分组成:

·          安全验证机制。主要是验证用户名和密码组合是否与数据库中注册时的数据一致,以确认该用户身份为注册用户。这是对所有的 JSP 访问都进行拦截访问。

·          访问权限控制( ACL )。对不同的数据不同用户拥有不同的访问权限,例如,一个帖子普通用户可以浏览,但是不能更该;但是管理员却可以编辑删除。这部分功能是通过代理模式实现,为每个关键数据都建立一个代理类用来实现访问权限检查,这在前面讨论过。

·          用户资料管理系统。主要是管理用户的资料数据,进行用户组和用户关系的建立等。

安全验证机制

Jive 的安全验证机制是按照比较通用的思路设计的。类似前面“简单的用户注册管理系统”中的介绍, Jive 也是在所有的 JSP 页面中 include 一个安全检验功能的 global.jsp 。由于 global.jsp 是在每个 JSP 一开始必须执行的功能,因此通过拦截 global.jsp 拦截发往各个 JSP 页面的请求( request )。如果这个请求是合法的,将被允许通过;如果不是,将注明请求者身份是 Anonymous (匿名者)。

global.jsp 代码如下:

boolean isGuest = false;

Authorization authToken = SkinUtils.getUserAuthorization(request, response);

if (authToken == null) {// 未被验证通过

    authToken = AuthorizationFactory.getAnonymousAuthorization();

    isGuest=true;

}

Jive 中,以 Authorization 对象作为验证通过的标志,它的接口代码如下:

public interface Authorization {

    public long getUserID();   

    public boolean isAnonymous();

}

具体实现是 DbAuthorization ,代码如下:

public final class DbAuthorization implements Authorization, Serializable {

    private long userID;

    protected DbAuthorization(long userID) {

        this.userID = userID;

    }

    public long getUserID() {

        return userID;

    }

    public boolean isAnonymous() {

        return userID == -1;

    }

}

此类只是一个 userID ,因此只是一个象征性的标志。

SkinUtils 是一个为 JSP 服务的类,它的 getUserAuthorization 代码如下:

public static Authorization getUserAuthorization

        (HttpServletRequest request, HttpServletResponse response)

  {

    HttpSession session = request.getSession();

    // HttpSession 中获取 Authorization 实例

    Authorization authToken =

(Authorization)session.getAttribute(JIVE_AUTH_TOKEN);

    if (authToken != null) {     return authToken;  }

 

    // 如果 HttpSession 中没有,检查用户浏览器 cookie

    Cookie cookie = getCookie(request, JIVE_AUTOLOGIN_COOKIE);

    if (cookie != null) {

        try {

           String[] values = decodePasswordCookie(cookie.getValue());

           String username = values[0];

           String password = values[1];

           // cookie 中获得用户名和密码后,进行安全验证

           authToken = AuthorizationFactory.getAuthorization(username,password);

        }catch (Exception e) {}

        // put that token in the user's session:

        if (authToken != null) {// 如果通过验证,保存 authToken http Session

           session.setAttribute(JIVE_AUTH_TOKEN, authToken);

        }

       // return the authorization token

        return authToken;

    }

    return null;

}

用户验证预先通过两个步骤。首先检查 HttpSession 中是否保存了该用户的验证信息,如果用户第一次验证通过,反复访问,这道关口检查就可以通过。

如果 HttpSession 中没有验证信息,那么从该用户的浏览器 cookie 中寻找用户名和密码。如果该用户激活了 cookie 保存这些登录信息,那么应该可以找到用户名和密码,这样就省却了用户再次从键盘输入用户名和密码,将用户名和密码通过下列语句进行数据库验证:

authToken = AuthorizationFactory.getAuthorization(username,password);

这一举是验证关键。 AuthorizationFactory 是一个抽象类,定义了 Jive 安全验证机制所需的所有方法, AuthorizationFactory 的实现类似前面讨论的 ForumFactory 实现,是使用工厂模式加动态类反射机制完成的,代码如下:

public abstract class AuthorizationFactory {

   // 定义一个数据库具体实现

    private static String className =

        " com.Yasna.forum.database.DbAuthorizationFactory";

 

    private static AuthorizationFactory factory = null;

    // 验证方法 如果没有 UnauthorizedException 抛出,表示验证通过

    public static Authorization getAuthorization(String username,

            String password) throws UnauthorizedException

    {

        loadAuthorizationFactory();

        return factory.createAuthorization(username, password);

    }

    // 匿名者处理方法

    public static Authorization getAnonymousAuthorization() {

        loadAuthorizationFactory();

        return factory.createAnonymousAuthorization();

    }

    // 需要具体实现的抽象方法

    protected abstract Authorization createAuthorization(String username,

            String password) throws UnauthorizedException;

    protected abstract Authorization createAnonymousAuthorization();

    // 动态配置 AuthorizationFactory 的具体实现,可以在配置文件中定义一个

    // 基于 LDAP 的实现。类似 ForumFactory getInstance 方法

    private static void loadAuthorizationFactory() {

        …

    }

}

AuthorizationFactory 看上去很复杂,实际只有一个核心方法 getAuthorization 。实现用户名和密码的验证。如果无法通过验证,有两个信息实现显示:一个是抛出 UnauthorizedException ,另外一个是返回空的 Authorization 对象。

那么,子类 DbAuthorizationFactory 毫无疑问就是查询数据库,将输入的用户名和密码与数据库保存的用户名和密码进行校验。

Jive 的安全验证机制比较简单易懂,值得在实践中学习借鉴。但是注意到这套安全验证机制只是 Web 层的“手工”验证,资源访问权限( ACL )也是自己“手工”来实现的。如果使用 EJB 技术,因为 EJB 容器本身有一定的资源访问控制体系,因此在 Web 层验证通过后,需要将这些登录信息传递到 EJB 层。当然如果直接使用 Web 容器的安全验证机制,那么 Web 层与 EJB 层之间的登录信息传递将由容器实现,这样就更加简单方便。

Jive 这种的安全验证并不是使用 Web 容器的安全验证机制,如何使用 Web 容器的安全验证机制将在以后章节介绍。尽管如此, Jive 这套安全验证机制对付小型系统的应用也是足够的。


用户资料管理

Jive 中,用户 User 对象的操作访问类似于论坛 Forum 对象的访问,与 User 对象有关的操作都封装在一个类中操作,这是外观( Facade )模式的应用。

Jive 中,用户资料管理属于大系统中的一个子系统,在这个子系统中,用户子系统和其他系统又有一定的关系,涉及的类不少,通过建立一个 UserManager 类来统一对外接口,使得整个子系统条目结构清晰。

UserManager 中无外乎用户数据的管理,如用户的创建、修改、查询和删除。 DbUserManager UserManager 的一个数据库实现,可是看看 DbUserManager 中除了删除功能是直接通过 SQL 语句进行数据库删除操作外,其他都委托给 User 的具体实现 DbUser 实现的。这种实现非常类似于 EJB Session Bean 和实体 Bean 之间的关系。以创建用户资料为例,代码如下:

public User createUser(String username, String password, String email)

            throws UserAlreadyExistsException

 {

        User newUser = null;

        try {

            // username 查询改用户是否存在

            User existingUser = getUser(username);

            // 如果没有抛出 UserNotFoundException 异常,表示该用户存在

            //The user already exists since now exception, so:

            throw new UserAlreadyExistsException();

        } catch (UserNotFoundException unfe) {

            // 该用户不存在,创建一个新用户

            newUser = new DbUser(username, password, email, factory);

        }

        return newUser;

}

DbUser 的构造方法实际是用户资料的新增创建:

protected DbUser(String username, String password, String email,

            DbForumFactory factory)

{

        this.id = SequenceManager.nextID(JiveGlobals.USER);  // 获得自增 ID

        this.username = username;

        // Compute hash of password.

        this.passwordHash = StringUtils.hash(password);  // 获得加密的密码

        this.email = email;

        this.factory = factory;

        long now = System.currentTimeMillis();

        creationDate = new java.util.Date(now);

        modifiedDate = new java.util.Date(now);

        properties = new Hashtable();

        insertIntoDb();              // 数据库插入数据

}

Jive 中,数据修改的保存是由 DbUser saveToDb 方法实现的,而 saveToDb 方法调用是在每个 setXXXX 方法中。即每当外界调用 DbUser setXXXX ,则表示需要改变某些字段属性值,在这个方法中直接进行数据库存储,这也类似 EJB CMP 实体 Bean 的数据字段修改保存。

Jive 中组 Group 与用户 User 处理几乎差不多,只是在 Group 中整合了权限方面的信息,这种做法是有一定的局限性,不是很值得借鉴,要想设计一个动态扩展灵活的权限系统,必须在用户或组与权限之间引入角色概念,也就是比较先进的基于角色的权限系统( RBAC Roled-Based Access Control ,相关网址: http://csrc.nist.gov/rbac/ )。

RBAC 中,用户组只是用户的一个集合,应该是通过角色和权限发生联系。所以 RBAC 认为,如果给用户组赋予权限,那么用户组也接近角色的概念。

posted @ 2006-08-31 12:26 Alex 阅读(677) | 评论 (0)编辑 收藏

     摘要: 2  Jive 与设计模式 Jive 论坛系统使用大量设计模式巧妙地实现了一系列功能。因为设计模式的通用性和可理解性,将帮助更多人很快地理解 Jive 论坛源码,从而可以依据一种“协定”来动态地扩展它。那么使用设计模式还有哪些好处? ...  阅读全文
posted @ 2006-08-31 12:26 Alex 阅读(742) | 评论 (0)编辑 收藏

1  Jive 功能需求

Jive 功能需求分析类似于一个新系统的需求分析。只有了解 Jive 系统实现了哪些论坛功能,才能进一步研究和学习它是怎样巧妙、优雅地实现这些功能的。

论坛系统是网络交流的一种主要互动功能系统,如图 3-1 所示。通过论坛系统,用户可以共同就某个话题不断进行讨论,通过发贴功能发布新的话题,通过回贴功能回复别人的话题。 Jive 论坛系统可以允许管理员动态地创建新的论坛、编辑论坛的内容、设置论坛过滤信息以及管理注册用户等。

3-1  Jive 用例图

Jive 论坛系统中,用户角色和权限是紧密联系在一起的。主要分两大角色:普通用户和管理员,具体的表现形式是通过权限组合来体现的。管理方面的权限有:

·          SYSTEM_ADMIN ,系统管理员,可以管理整个系统。

·          FORUM_ADMIN ,论坛管理员,可以管理某个特定的论坛。

·          USER_ADMIN GROUP_ADMIN ,用户和组管理员,可以管理一些特定用户和用户 组。

论坛的读写权限包括:读权限,创建一个新主题,创建一个新的帖子等。

Jive 中没有明确定义普通用户和管理员角色,而是直接通过以上权限组合和具体用户直接建立联系,并将这种直接联系保存到数据库中。

在权限不是很复杂的情况下,这种没有引入角 色的做法比较简单直接。但由于用户和权限直接挂钩,而用户和权限都可能在不断地动态变化,那么它们之间由于联系太直接和紧密,对各自变化形成了限制。所 以,对于复杂的权限系统,引入了基于角色的权限系统,这将在以后章节中进一步讨论。

Jive 论坛业务对象主要分为 Forum ForumThread ForumMessage ,它们之间的关系如图 3-2 所示。

每个论坛 Forum 包含一系列 ForumThread (主题),而每个主题都是由很多内容帖子 ForumMessage 组成的,这是一个聚集关系。这 3 种对象中每一个对象都涉及到对象数据的创建、编辑、查询和删除,这些对象数据分别保存在数据库中。这 3 个对象对于不同的角色可操作访问权限是不一样的,只有系统管理员和论坛管理员可以对 Forum 相关数据实行操作,普通用户可以创建或编辑 ForumThread ForumMessage

Jive 论坛为了实现不同用户对不同基本对象的不同操作权限,通过设定一个统一的入口,在这个入口将检查客户端每次对数据的操作权限,如图 3-3 所示。

  

3-2  基本对象关系图                             3-3  入口示意图

客户端每次对数据库的操作,都要经过 ForumFactory 入口进入。在 ForumFactory 中会动态生成一个访问控制代理 ForumFactoryProxy ,通过 ForumFactoryProxy 检查客户端访问方法是否符合整体权限访问控制要求。

下面将从 ForumFactory 作为 Jive 论坛系统分析入手,结合设计模式逐步分解论坛功能的具体实现。

posted @ 2006-08-31 12:25 Alex 阅读(2084) | 评论 (1)编辑 收藏

JDBMonitor是一个开源项目。使用它开发者可以很轻松为系统增加数据库执行日志功能。它使用十分方便,您所需要做的唯一事情就是在您系统的JDBC连接字符串前增加类似于 "listenerconfig=/config.xml:url=" 的字符即可,不用写任何代码。

使用 JDBMonitor,您可以把数据库执行情况记录通过各种方式记录下来,比如打印到控制台、输出到文件或者通过socket传送给远程客户端。JDBMonitor是可扩展的,您可以扩展它来将执行情况通过其他方式记录下来,您所需要做的就是写一个实现IDBListener接口的类即可。

JDBMonitor遵守 GNU Lesser General Public Licence (LGPL)协议。此协议包含在发行包中。

入门

几乎所有大型数据库应用都包含有自己的SQL执行日志功能,此功能不仅能帮助开发人员调试,而且可以为DBA(数据库管理员)提供系统的运行信息。

(1)很难将业务逻辑同日志代码分离

(2)降低了代码的可读性。

(3)降低了系统的运行速度。在记录日志的时候,程序会暂停运行等待直到记录完成,而I/O操作是相当耗时的。

(4)很难记录运行耗时、语句参数等其他信息

(5)很难为我们无法修改代码的系统(例如没有源代码的系统)或者很难增加记录日志功能代码的系统(比如系统使用了ORMapping)增加日志功能。

JDBMonitor 则不同:

(1)您最多只需要修改一行代码。您需要修改的代码就是这一行:Class.forName("com.cownew.JDBMonitor.jdbc.DBDriver") ,然后再修改一下 JDBC连接字符串,只要从 “jdbc:db2://10.74.198.247:50000/app”修改成” listenerconfig=config.xml:url= jdbc:db2://10.74.198.247:50000/app”就可以了。在您使用WebLogic ,Tomcat或其他服务器的数据源功能的时候,连修改代码这一步都是无需的。

(2)JDBMonitor另起一个线程来记录SQL,所以它不会对程序运行速度有任何影响。

(3)它是高度可扩展的,所以您可以扩展它来把执行情况通过其他方式记录。比如,您可以写一个扩展类,来通过电子邮件将日志发送出去。

取得 JDBMonitor

JDBMonitor的最新稳定版本可以在JDBMonitor的网站上取得:

http://www.cownew.com/JDBMonitor

使用 JDBMonitor

1 将 jdbmonitor.jar放到您系统的类路径下。

2 让系统加载 JDBMonitor的JDBC驱动。

这一步将会依您系统加载JDBC驱动的方式的不同而不同。

(1)如果您通过代码的形式加载JDBC驱动,例如:

   Class.forName(“com.microsoft.jdbc.sqlserver.SQLServerDriver”);
   Connection cn = DriverManager.getConnection(……);

在这种情况下 ,您必须修改 “Class.forName”这一句来加载JDBMonitor的JDBC驱动(“com.cownew.JDBMonitor.jdbc.DBDriver”),而非以前的数据库JDBC驱动。

例如:

Class.forName(“com.cownew.JDBMonitor.jdbc.DBDriver”);
   Connection cn = DriverManager.getConnection(……);

(2)如果您在配置文件中指定JDBC驱动,比如,数据源配置文件或者其他类似的文件。

请修改原来的  JDBC驱动类为 “com.cownew.JDBMonitor.jdbc.DBDriver” 。

3 让 JDBMonitor加载能够加载原来的JDBC驱动

JDBMonitor的工作原理就是截获JDBC驱动的SQL语句调用、记录SQL语句,然后将SQL语句重新转发给原来的JDBC驱动,所以JDBMonitor必须首先向DriverManager注册JDBC驱动。

原来的JDBC驱动定义在配置文件的“JdbcDrivers” 段中。
<JdbcDrivers>
    <JdbcDriver class=" com.mysql.jdbc.Driver"/>
  </JdbcDrivers>

4 在原来的JDBC连接字符串前增加 JDBMonitor所需的信息。

您所需要做的就是将” listenerconfig=<configfilepath>:url=” 增加到原来的JDBC连接字符串前。“<configfilepath>”代表配置文件的路径,下面集中路径都是合法的:

/com/jdbmonitor/config.xml
com/jdbmonitor/config.xml
c:/ jdbmonitor /config.xml

JDBMoinitor使用getClass().getResourceAsStream加载类似于“/com/jdbmonitor/config.xml” and “com/jdbmonitor/config.xml” 的类路径文件,使用 FileInputStream加载类似于 “c:/ jdbmonitor /config.xml”的配置文件。

5 指定您要使用监听器:

您可以把数据库执行情况记录通过各种方式记录下来,比如打印到控制台、输出到文件或者通过socket传送给远程客户端。

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListene、DataBaseDBListener。当然您也可以开发满足您要求的监听器。
监听器定义在配置文件的 “Listeners”段中:

<Listeners>
    <!--ConsoleDBListener no arguments-->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>
   
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/aaa.txt"/>
   
    <!--the arguments of SocketDBListener is the bound socket port of the listener server -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.SocketDBListener" arg="9527"/>
  </Listeners>

搞定!启动您的系统。耶!SQL语句被记录下来了,我们可以在控制台、文件甚至远程监视器中看到日志了。

举例

mvnforum的例子:

您可以从http://www.mvnForum.com得到mvnforum。我演示用的版本是1.0。

(1)打开webapp\WEB-INF\classes\ mvncore.xml,重新配置:

修改之前:

<driver_class_name>com.mysql.jdbc.Driver</driver_class_name>
<database_url>listenerconfig=c:/log/jdbmonitor/config.xml:url= jdbc:mysql://localhost/mvnforum?useUnicode=true&amp;characterEncoding=utf-8</database_url>

修改之后:
<driver_class_name> com.cownew.JDBMonitor.jdbc.DBDriver </driver_class_name>
        <database_url>jdbc:mysql://localhost/mvnforum?useUnicode=true&amp;characterEncoding=utf-8</database_url>

(2)创建文件 c:/log/jdbmonitor/config.xml。我只想将SQL语句记录到文本文件中,所以我做如下配置:
<config>
  <Listeners>
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/log.txt"/>
  </Listeners>
  <JdbcDrivers>
    <JdbcDriver class="com.mysql.jdbc.Driver"/>
  </JdbcDrivers>
</config>
(3) 将 jdbmonitor.jar放到webapp\WEB-INF\lib下。
(4) 搞定!

Jive的例子:

您可以从http://www.jivesoftware.com得到Jive。我演示用的版本是 Jive 2.0 beta版。

(1)打开http://localhost:8080/jive/admin/

“jdbc” 填为:com.cownew.JDBMonitor.jdbc.DBDriver

“server” 填为:c:/log/jdbmonitor/config.xml:url=jdbc:mysql://locahost/jive
(2)将 jdbmonitor.jar放到WEB-INF\lib下
(3) 象mvnforum中一样创建同样的 c:/log/jdbmonitor/config.xml 文件.
(4) 搞定!

代码方式的例子:

尽管直接在代码中指定系统所用的JDBC驱动类名和JDBC连接字符串是不推荐的,但是仍然有系统是这么做的。

比如:

              Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
              Connection conn = null;
              PreparedStatement ps = null;
              try
              {
                     conn = DriverManager
                                   .getConnection("jdbc:odbc:MQIS");
                     for (int i = 0; i < 1000; i++)
                     {                   
                            ps = conn.prepareStatement("update T_Material set fid=fid");
                            ps.execute();
                            ps.close();
                     }
              } finally
              {
                     ....         
              }

(1)修改一下代码为:
               Class.forName("com.cownew.JDBMonitor.jdbc.DBDriver");
              Connection conn = null;
              PreparedStatement ps = null;
              try
              {
                     conn = DriverManager.getConnection("listenerconfig= c:/log/jdbmonitor/config.xml:url=jdbc:odbc:MQIS");
                     for (int i = 0; i < 1000; i++)
                     {
                            ps = conn.prepareStatement("update T_Material set fid=fid");
                            ps.execute();
                            ps.close();
                     }
              } finally
              {
                     ....         
              }

(2)创建c:/log/jdbmonitor/config.xml文件。我想记录SQL语句到文本文件中同时输出到控制台,这样可以辅助我进行调试,所以我配置如下:
<config>
  <Listeners>
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
<Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/log.txt"/>

<!--ConsoleDBListener no arguments-->
<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>
  </Listeners>
  <JdbcDrivers>
    <JdbcDriver class="com.mysql.jdbc.Driver"/>
  </JdbcDrivers>
</config>
(3) 将 jdbmonitor.jar放到类路径下。
(4) 搞定!

监听器

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListener、DataBaseDBListener。

1、ConsoleDBListener 控制台监听器

ConsoleDBListener会将SQL语句打印到控制台中。

这个监听器很容易配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>

2、FileDBListener 文件监听器

FileDBListener 会将SQL语句保存到文本文件中。

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/aaa.txt"/>

arg="c:/aaa.txt"表示日志将保存到文件c:/aaa.txt中。

3、SocketDBListener Socket监听器

SocketDBListener是一个socket服务器,客户端连接到它上边以后就可以接收到它发出的SQL语句。

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.SocketDBListener" arg="9527"/>

arg="9527"表示服务器将在9527端口监听。

我们已经开发了如下两种客户端:SocketConsoleClient(Socket控制台客户端) 和 SocketSwingClient(Socket Swing客户端)。

SocketConsoleClient工作在控制台中:

SocketSwingClient是一个Swing GUI客户端:

您可以运行"java -classpath jdbmonitor.jar com.cownew.JDBMonitor.listenerImpl.sckListenerClient.SocketConsoleClient" 来启动SocketConsoleClient,运行"java -classpath jdbmonitor.jar com.cownew.JDBMonitor.listenerImpl.sckListenerClient.SocketSwingClient"启动SocketSwingClient

您可以编写符合您自己要求的客户端,具体细节请参考com.cownew.JDBMonitor.listenerImpl.sckListenerClient.ListenerClientcom.cownew.JDBMonitor.listenerImpl.sckListenerClient.IDBSocketClientListener.

4、DataBaseDBListener

DataBaseDBListener将会把SQL语句记录到数据库中:

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.DataBaseDBListener"
arg="dburl=jdbc:odbc:MQIS;user=;password=;logtable=T_Log_SQLLog"/>

"dburl=jdbc:odbc:MQIS;user=;password=;"表示目标数据库的JDBC连接字符串。"logtable=T_Log_SQLLog" 表示SQL记录将被保存到哪个表中,默认的是T_Log_SQLLog

如果目标数据库用的JDBC驱动与被监控的数据库不同,请将它加入配置文件的 "JdbcDrivers" 部分,例如:

<config>
<Active>true</Active>
<Listeners>

<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>

<Listener class="com.cownew.JDBMonitor.listenerImpl.DataBaseDBListener"
arg="dburl=jdbc:odbc:MQIS;user=;password=;logtable=T_Log_SQLLog"/>
</Listeners>
<JdbcDrivers>
<JdbcDriver class="com.microsoft.jdbc.sqlserver.SQLServerDriver"/>
<JdbcDriver class="sun.jdbc.odbc.JdbcOdbcDriver"/>
</JdbcDrivers>
</config>

"T_Log_SQLLog"的结构是:

"T_Log_SQLLog"的建库脚本在com/cownew/JDBMonitor/listenerImpl/dataBaseListener,(db2.sql,mssqlserver.sql,oracle.sql)。

DataBaseDBListener是跨数据库的,你可以把记录SQL到任何关系数据库中。

FAQ:

1 如果我暂时不想记录SQL语句执行怎么办?难道我要重新修改成原来的样子?

答:无须如此。您只要修改config.xml,增加<Active>false</Active>到文件中即可。

如下:

<config>
  <Active> false </Active>
  <Listeners>
......
</config>

如何扩展JDBMonitor?

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListener、DataBaseDBListener。当然您也可以开发满足您要求的监听器。所有的监听器必须实现接口:com.cownew.JDBMonitor.commo. IDBListener。IDBListener有两个方法需要实现:

public void init(String arg);
public void logSql(SQLInfo info);

JDBMonitor会将配置文件中监听器定义中“arg”的值传递给 “init”方法、将代表SQL语句执行信息的SQLInfo传递给“logSql”方法。

更多信息请参考API文档。

posted @ 2006-08-31 10:59 Alex 阅读(1573) | 评论 (0)编辑 收藏

     摘要: UML 类图介绍 一、 UML 简介 UML ( Unified Modeling ...  阅读全文
posted @ 2006-08-29 22:30 Alex 阅读(1115) | 评论 (0)编辑 收藏

这一段时间,参加了部门组织的 RUP 教学项目,由一位“外援”架构师为我们指导教练。最近一直在忙于业务建模,今天刚刚将自己负责部分的系统用例识别了一遍。其间一直有一个问题,缠绕着包括我在内的很多同事,那就是用例之间的关系——包含、扩展、泛化——到底该如何使用。

    翻阅了同事去年参加 RUP 培训时带来的材料,终于能基本分清三者之间的关系。

 

用例是从系统外部可见的行为,是系统为某一个或几个参与者( Actor )提供的一段完整的服务。从原则上来讲,用例之间都是独立、并列的,它们之间并不存在着包含从属关系。但是为了体现一些用例之间的业务关系,提高可维护性和一致性,用例之间可以抽象出包含 (include) 、扩展 (extend) 和泛化 (generalization) 这几种关系。

在分开介绍它们之前,先说下它们的共性:都是从现有的用例中抽取出公共的那部分信息,作为一个单独的用例,然后通后过不同的方法来重用这个公共的用例,以减少模型维护的工作量。

 

1 、包含 (include)

  包含关系:使用包含 Inclusion 用例来封装一组跨越多个用例的相似动作(行为片断),以便多个基( Base )用例复用。基用例控制与包含用例的关系,以及被包含用例的事件流是否会插入到基用例的事件流中。基用例可以依赖包含用例执行的结果,但是双方都不能访问对方的属性。


 

包 含关系对典型的应用就是复用,也就是定义中说的情景。但是有时当某用例的事件流过于复杂时,为了简化用例的描述,我们也可以把某一段事件流抽象成为一个被 包含的用例;相反,用例划分太细时,也可以抽象出一个基用例,来包含这些细颗粒的用例。这种情况类似于在过程设计语言中,将程序的某一段算法封装成一个子 过程,然后再从主程序中调用这一子过程。 

    例如:业务中,总是存在着维护某某信息的功能,如果将它作为一个用例,那新建、编辑以及修改都要在用例详述中描述,过于复杂;如果分成新建用例、编辑用例和删除用例,则划分太细。这时包含关系可以用来理清关系。

 

 

 

2 扩展 (extend)

扩展关系:将基用例中一段相对独立并且可选的动作,用扩展( Extension )用例加以封装,再让它从基用例中声明的扩展点( Extension Point )上进行扩展,从而使基用例行为更简练和目标更集中。

扩展用例为基用例添加新的行为。扩展用例可以访问基用例的属性,因此它能根据基用例中扩展点的当前状态来判断是否执行自己。但是扩展用例对基用例不可见。

对于一个扩展用例,可以在基用例上有几个扩展点。

 

例如,系统中允许用户对查询的结果进行导出、打印。对于查询而言,能不能导出、打印查询都是一样的,导出、打印是不可见的。导入、打印和查询相对独立,而且为查询添加了新行为。因此可以采用扩展关系来描述:

 

 

用例详述里面大致可以这样来写:

执行查询

  基本流:

1. 员工选择查询功能

        员工期望查询业务数据时,选择查询链接,从而启动本用例的执行。

2. 系统转入查询页面,并显示备选的查询选项

       

3. 员工填写查询条件并提交

               

4. 系统验证查询条件的合法性

                验证条件的格式以及简单逻辑,如大小、前后、范围

5. 系统将符合条件的信息返回

        系统将查询结果以分页列表的形式显示在页面上

6. 员工退出查询功能

        员工点击退出链接,返回到上一级页面

    扩展点:导出、打印扩展点定义在步骤 5

 

导出

    该用例是在“导出、打印”扩展点上扩展了执行查询用例

    基本流:

        1 .如果员工要求导出,选择导出按钮

        。。。。。。

 

由上例可以看出 : 扩展用例的事件流往往可以也可抽象为基用例的备选流。但是在基用例本身已经是一个很复杂的情况下,选用扩展关系将备选流抽象成为单独的用例可以使基用例行为更简练和目标更集中(当然上面的例子中基用例可能简单了些)。

 

 

4 泛化 (generalization)

泛化关系:子用例和父用例相似,但表现出更特别的行为;子用例将继承父用例的所有结构、行为和关系。子用例可以使用父用例的一段行为,也可以重载它。父用例通常是抽象的。在实际应用中很少使用泛化关系,子用例中的特殊行为都可以作为父用例中的备选流存在。

 

例如,业务中可能存在许多需要部门领导审批的事情,但是领导审批的流程是很相似的,这时可以做成泛化关系表示:

 

用例详述里面大致可以这样来写:

审批

  基本流:

7. 领导选择要审批的记录

        领导期望审批记录时,选择待审批记录链接,从而启动本用例的执行。

8. 系统转入审批页面,并显示记录的详细信息

       

9. 领导填写审批意见

                领导根据记录的合理性来填写个人审批意见

10.              提交审批结果

       

 

工资调整审批

    该用例是审批用例的子用例

    基本流:

        1 .领导选择要审批的记录

 

2. 系统转入审批页面,并显示记录的详细信息

 

3. 领导填写审批意见

 

4. 提交审批结果

                如果调整幅度较大,则要提交上级审批

 

 

上面分析了用例之间的三种关系。其中最容易让人迷惑的就是包含关系和扩展关系得区别。如果你现在对两者还有迷惑,请再仔细的对比一下上面两者的描述:)。


posted @ 2006-08-29 22:23 Alex 阅读(511) | 评论 (1)编辑 收藏

Java 数据库连接(JDBC)由一组用 Java 编程语言编写的类和接口组成。JDBC 为工具/数据库开发人员提供了一个标准的 API,使他们能够用纯Java API 来编写数据库应用程序。然而各个开发商的接口并不完全相同,所以开发环境的变化会带来一定的配置变化。本文主要集合了不同数据库的连接方式。
一、连接各种数据库方式速查表
    下面罗列了各种数据库使用JDBC连接的方式,可以作为一个手册使用。

    1
Oracle8/8i/9i数据库(thin模式) 
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); 
String url="jdbc:oracle:thin:@localhost:1521:orcl"; //orcl
为数据库的SID 
String user="test"; 
String password="test"; 
Connection conn= DriverManager.getConnection(url,user,password); 

    2
DB2数据库 
Class.forName("com.ibm.db2.jdbc.app.DB2Driver ").newInstance(); 
String url="jdbc:db2://localhost:5000/sample"; //sample
为你的数据库名 
String user="admin"; 
String password=""; 
Connection conn= DriverManager.getConnection(url,user,password); 

    3
Sql Server7.0/2000数据库 
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance(); 
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb"; 
//mydb
为数据库 
String user="sa"; 
String password=""; 
Connection conn= DriverManager.getConnection(url,user,password); 

    4
Sybase数据库 
Class.forName("com.sybase.jdbc.SybDriver").newInstance(); 
String url =" jdbc:sybase:Tds:localhost:5007/myDB";//myDB
为你的数据库名 
Properties sysProps = System.getProperties(); 
SysProps.put("user","userid"); 
SysProps.put("password","user_password"); 
Connection conn= DriverManager.getConnection(url, SysProps); 

    5
Informix数据库 
Class.forName("com.informix.jdbc.IfxDriver").newInstance(); 
String url = 
"jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver; 
user=testuser;password=testpassword"; //myDB
为数据库名 
Connection conn= DriverManager.getConnection(url); 

    6
MySQL数据库 
Class.forName("org.gjt.mm.mysql.Driver").newInstance(); 
String url ="jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1" 
//myDB
为数据库名 
Connection conn= DriverManager.getConnection(url); 

    7
PostgreSQL数据库 
Class.forName("org.postgresql.Driver").newInstance(); 
String url ="jdbc:postgresql://localhost/myDB" //myDB
为数据库名 
String user="myuser"; 
String password="mypassword"; 
Connection conn= DriverManager.getConnection(url,user,password); 


    8、access
数据库直连用ODBC的
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") ;
String url="jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/Data/ReportDemo.mdb");
Connection conn = DriverManager.getConnection(url,"","");
 Statement stmtNew=conn.createStatement() ;

二、JDBC连接MySql方式
    下面是使用JDBC连接MySql的一个小的教程     

    1、查找驱动程序
    MySQL目前提供的java驱动程序为Connection/J,可以从MySQL官方网站下载,并找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驱动程序为纯java驱动程序,不需做其他配置。

    2、动态指定classpath
    如果需要执行时动态指定classpath,就在执行时采用-cp方式。否则将上面的.jar文件加入到classpath环境变量中。

    3、加载驱动程序
try{
    Class.forName(com.mysql.jdbc.Driver);
    System.out.println(Success loading Mysql Driver!);
}catch(Exception e)
{
    System.out.println(Error loading Mysql Driver!);
    e.printStackTrace();
}

    4、设置连接的url
    jdbc:mysql://localhost/databasename[?pa=va][&pa=va]

三、以下列出了在使用JDBC来连接Oracle数据库时可以使用的一些技巧,这些技巧能够使我们更好地发挥系统的性能和实现更多的功能(系转载)。

  1、在客户端软件开发中使用Thin驱动程序

  在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、 servlets等客户端软件,另外二种用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,我们可以选择OCI驱动程序或Thin 驱动程序。OCI驱动程序利用Java本地化接口(JNI),通过Oracle客户端软件与数据库进行通讯。Thin驱动程序是纯Java驱动程序,它直 接与数据库进行通讯。为了获得最高的性能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这似乎是正确的。但我建议使用Thin驱动程序,因 为通过多次测试发现,在通常情况下,Thin驱动程序的性能都超过了OCI驱动程序。

  2、关闭自动提交功能,提高系统性能

  在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:

  conn.setAutoCommit(false);

  值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。

  3、在动态SQL或有时间限制的命令中使用Statement对象

  在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用 Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象 时,每次执行一个SQL命令时,都会对它进行解析和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的 速度更快。然而,我进行的测试表明,在客户端软件中,情况并非如此。因此,在有时间限制的SQL操作中,除非成批地处理SQL命令,我们应当考虑使用 Statement对象。

  此外,使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。因此,我认为,Statement对象可以使动态SQL命令的创建和执行变得更加简单。

  4、利用helper函数对动态SQL命令进行格式化

  在创建使用Statement对象执行的动态SQL命令时,我们需要处理一些格式化方面的问题。例如,如果我们 想创建一个将名字O'Reilly插入表中的SQL命令,则必须使用二个相连的“''”号替换O'Reilly中的“'”号。完成这些工作的最好的方法是 创建一个完成替换操作的helper方法,然后在连接字符串心服用公式表达一个SQL命令时,使用创建的helper方法。与此类似的是,我们可以让 helper方法接受一个Date型的值,然后让它输出基于Oracle的to_date()函数的字符串表达式。

  5、利用PreparedStatement对象提高数据库的总体效率

  在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令 缓冲区。然后,每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且 可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够 提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,我会建议在除动 态SQL命令之外的所有情况下使用PreparedStatement对象。

  6、在成批处理重复的插入或更新操作中使用PreparedStatement对象

  如果成批地处理插入和更新操作,就能够显著地减少它们所需要的时间。Oracle提供的Statement和 CallableStatement并不真正地支持批处理,只有PreparedStatement对象才真正地支持批处理。我们可以使用 addBatch()和executeBatch()方法选择标准的JDBC批处理,或者通过利用PreparedStatement对象的 setExecuteBatch()方法和标准的executeUpdate()方法选择速度更快的Oracle专有的方法。要使用Oracle专有的批 处理机制,可以以如下所示的方式调用setExecuteBatch():
PreparedStatement pstmt3D null;
try {
((OraclePreparedStatement)
pstmt).setExecuteBatch(30);
...
pstmt.executeUpdate();
}

  调用setExecuteBatch()时指定的值是一个上限,当达到该值时,就会自动地引发SQL命令执行, 标准的executeUpdate()方法就会被作为批处理送到数据库中。我们可以通过调用PreparedStatement类的sendBatch ()方法随时传输批处理任务。

  7、使用Oracle locator方法插入、更新大对象(LOB)

  Oracle的PreparedStatement类不完全支持BLOB和CLOB等大对象的处理,尤其是 Thin驱动程序不支持利用PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的 值,也不支持利用setCharacterStream()方法设置CLOB的值。只有locator本身中的方法才能够从数据库中获取LOB类型的值。 可以使用PreparedStatement对象插入或更新LOB,但需要使用locator才能获取LOB的值。由于存在这二个问题,因此,我建议使用 locator的方法来插入、更新或获取LOB的值。

  8、使用SQL92语法调用存储过程

  在调用存储过程时,我们可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并没有什么实际的好处,而且会给以后维护你的应用程序的开发人员带来麻烦,因此,我建议在调用存储过程时使用SQL92。

  9、使用Object SQL将对象模式转移到数据库中

  既然可以将Oracle的数据库作为一种面向对象的数据库来使用,就可以考虑将应用程序中的面向对象模式转到数 据库中。目前的方法是创建Java bean作为伪装的数据库对象,将它们的属性映射到关系表中,然后在这些bean中添加方法。尽管这样作在Java中没有什么问题,但由于操作都是在数据 库之外进行的,因此其他访问数据库的应用软件无法利用对象模式。如果利用Oracle的面向对象的技术,可以通过创建一个新的数据库对象类型在数据库中模 仿其数据和操作,然后使用JPublisher等工具生成自己的Java bean类。如果使用这种方式,不但Java应用程序可以使用应用软件的对象模式,其他需要共享你的应用中的数据和操作的应用软件也可以使用应用软件中的 对象模式。

  10、利用SQL完成数据库内的操作

  我要向大家介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。

  如果编程人员要在一个表中查找许多行,结果中的每个行都会查找其他表中的数据,最后,编程人员创建了独立的 UPDATE命令来成批地更新第一个表中的数据。与此类似的任务可以通过在set子句中使用多列子查询而在一个UPDATE命令中完成。当能够在单一的 SQL命令中完成任务,何必要让数据在网上流来流去的?我建议用户认真学习如何最大限度地发挥SQL的功能。

posted @ 2006-08-29 22:01 Alex 阅读(370) | 评论 (0)编辑 收藏

一、 引子

在大学的数据结构这门课上,树是最重要的章节之一。还记得树是怎么定义的吗?树 (Tree) n(n≥0) 个结点的有限集 T T 为空时称为空树,否则它满足如下两个条件:

(1)    有且仅有一个特定的称为根 (Root) 的结点;

(2)   其余的结点可分为 m(m≥0) 个互不相交的子集 Tl T2 Tm ,其中每个子集本身又是一棵树,并称其为根的子树 (SubTree)

上面给出的递归定义刻画了树的固有特性:一棵非空树是由若干棵子树构成的,而子树又可由若干棵更小的子树构成。而这里的子树可以是叶子也可以是分支。

今天要学习的组合模式就是和树型结构以及递归有关系。

 

二、 定义与结构

组合 (Composite) 模式的其它翻译名称也很多,比如合成模式、树模式等等。在《设计模式》一书中给出的定义是:将对象以树形结构组织起来,以达成 部分-整体 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。

从定义中可以得到使用组合模式的环境为: 在设计中想表示对象的 部分 整体 层次结构;希望用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。

看下组合模式的组成。

1)         抽象构件角色 Component :它为组合中的对象声明接口,也可以为共有接口实现缺省行为。

2)       树叶构件角色 Leaf :在组合中表示叶节点对象 —— 没有子节点,实现抽象构件角色声明的接口。

3)       树枝构件角色 Composite :在组合中表示分支节点对象 —— 有子节点,实现抽象构件角色声明的接口;存储子部件。

下图为组合模式的类图表示。

 

如图所示:一个 Composite 实例可以像一个简单的 Leaf 实例一样,可以把它传递给任何使用 Component 的方法或者对象,并且它表现的就像是一个 Leaf 一样。

可以看出来,使用组合模式使得这个设计结构非常灵活,在下面的例子中会得到进一步的印证。

      

三、 安全性与透明性

组合模式中必须提供对子对象的管理方法,不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。但是管理方法是在 Component 中就声明还是在 Composite 中声明呢?

一种方式是在 Component 里面声明所有的用来管理子类对象的方法,以达到 Component 接口的最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别 —— 透明性。但树叶是不存在子类的,因此 Component 声明的一些方法对于树叶来说是不适用的。这样也就带来了一些安全性问题。

 

另一种方式就是只在 Composite 里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。

    


    《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。

 

四、 举例

这里以 JUnit 中的组合模式的应用为例(JUnit)。

JUnit 是一个单元测试框架,按照此框架下的规范来编写测试代码,就可以使单元测试自动化。为了达到“自动化”的目的, JUnit 中定义了两个概念: TestCase TestSuite TestCase 是对一个类或者 jsp 等等编写的测试类;而 TestSuite 是一个不同 TestCase 的集合,当然这个集合里面也可以包含 TestSuite 元素,这样运行一个 TestSuite 会将其包含的 TestCase 全部运行。

然而在真实运行测试程序的时候,是不需要关心这个类是 TestCase 还是 TestSuite ,我们只关心测试运行结果如何。这就是为什么 JUnit 使用组合模式的原因。

JUnit 为了采用组合模式将 TestCase TestSuite 统一起来,创建了一个 Test 接口来扮演抽象构件角色,这样原来的 TestCase 扮演组合模式中树叶构件角色,而 TestSuite 扮演组合模式中的树枝构件角色。下面将这三个类的有关代码分析如下:

 

//Test 接口 —— 抽象构件角色

public interface Test {

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public abstract int countTestCases();

       /**

        * Runs a test and collects its result in a TestResult instance.

        */

       public abstract void run(TestResult result);

}

 

//TestSuite 类的部分有关源码 ——Composite 角色,它实现了接口 Test

public class TestSuite implements Test {

// 用了较老的 Vector 来保存添加的 test

       private Vector fTests= new Vector(10);

       private String fName;

       …… 

/**

        * Adds a test to the suite.

        */

       public void addTest(Test test) {          

// 注意这里的参数是 Test 类型的。这就意味着 TestCase TestSuite 以及以后实现 Test 接口的任何类都可以被添加进来

              fTests.addElement(test);

       }

       ……

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public int countTestCases() {

              int count= 0;

              for (Enumeration e= tests(); e.hasMoreElements(); ) {

                     Test test= (Test)e.nextElement();

                     count= count + test.countTestCases();

              }

              return count;

       }

       /**

        * Runs the tests and collects their result in a TestResult.

        */

       public void run(TestResult result) {

              for (Enumeration e= tests(); e.hasMoreElements(); ) {

                    if (result.shouldStop() )

                           break;

                     Test test= (Test)e.nextElement();
                           //关键在这个方法上面

                     runTest(test, result);

              }

       }
            //这个方法里面就是递归的调用了,至于你的Test到底是什么类型的只有在运行的时候得知
            public void runTest(Test test, TestResult result) {
                   test.run(result);
            }

……

}

 

//TestCase 的部分有关源码 ——Leaf 角色,你编写的测试类就是继承自它

public abstract class TestCase extends Assert implements Test {

       ……

       /**

        * Counts the number of test cases executed by run(TestResult result).

        */

       public int countTestCases() {

              return 1;

       }

/**

        * Runs the test case and collects the results in TestResult.

        */

       public void run(TestResult result) {

              result.run(this);

       }

……

}

       可以看出这是一个偏重安全性的组合模式。因此在使用TestCase和TestSuite时,不能使用Test来代替。

 

五、 优缺点

从上面的举例中可以看到,组合模式有以下优点:

1)         使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。

2)       更容易在组合体内加入对象部件 . 客户端不必因为加入了新的对象部件而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利!

当然组合模式也少不了缺点:组合模式不容易限制组合中的构件。

 

六、 总结

组合模式是一个应用非常广泛的设计模式,在前面已经介绍过的解释器模式、享元模式中都是用到了组合模式。它本身比较简单但是很有内涵,掌握了它对你的开发设计有很大的帮助。

这里写下了我学习组合模式的总结,希望能给你带来帮助,也希望您能给与指正。
posted @ 2006-08-29 21:44 Alex 阅读(437) | 评论 (0)编辑 收藏

一、 引子

这是一个很简单的模式,却被非常广泛的使用。之所以简单是因为在这个模式中仅仅使用到了继承关系。

继承关系由于自身的缺陷,被专家们扣上了 罪恶 的帽子。 使用委派关系代替继承关系 尽量使用接口实现而不是抽象类继承 等等专家警告,让我们这些菜鸟对继承 另眼相看

其实,继承还是有很多自身的优点所在。只是被大家滥用的似乎缺点更加明显了。合理的利用继承关系,还是能对你的系统设计起到很好的作用的。而模板方法模式就是其中的一个使用范例。

 

二、 定义与结构

GOF 给模板方法( Template Method )模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。这里的算法的结构,可以理解为你根据需求设计出来的业务流程。特定的步骤就是指那些可能在内容上存在变数的环节。

可以看出来,模板方法模式也是为了巧妙解决变化对系统带来的影响而设计的。使用模板方法使系统扩展性增强,最小化了变化对系统的影响。这一点,在下面的举例中可以很明显的看出来。

来看下这个简单模式的结构吧:

1)         AbstractClass (抽象类):定义了一到多个的抽象方法,以供具体的子类来实现它们;而且还要实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象方法,也可以调用其他的操作,只要能完成自身的使命。

2)         ConcreteClass (具体类):实现父类中的抽象方法以完成算法中与特定子类相关的步骤。

 

下面是模板方法模式的结构图。直接把《设计模式》上的图拿过来用下:

 

三、 举例

还是在我刚刚分析完源码的 JUnit 中找个例子吧。 JUnit 中的 TestCase 以及它的子类就是一个模板方法模式的例子。在 TestCase 这个抽象类中将整个测试的流程设置好了,比如先执行 Setup 方法初始化测试前提,在运行测试方法,然后再 TearDown 来取消测试设置。但是你将在 Setup TearDown 里面作些什么呢?鬼才知道呢!!因此,而这些步骤的具体实现都延迟到子类中去,也就是你实现的测试类中。

来看下相关的源代码吧。

这是 TestCase 中,执行测试的模板方法。你可以看到,里面正像前面定义中所说的那样,它制定了“算法”的框架——先执行 setUp 方法来做下初始化,然后执行测试方法,最后执行 tearDown 释放你得到的资源。

public void runBare() throws Throwable {

       setUp();

       try {

              runTest();

       }

       finally {

              tearDown();

       }

}

这 就是上面使用的两个方法。与定义中不同的是,这两个方法并没有被实现为抽象方法,而是两个空的无为方法(被称为钩子方法)。这是因为在测试中,我们并不是 必须要让测试程序使用这两个方法来初始化和释放资源的。如果是抽象方法,则子类们必须给它一个实现,不管用到用不到。这显然是不合理的。使用钩子方法,则 你在需要的时候,可以在子类中重写这些方法。

protected void setUp() throws Exception {

}

protected void tearDown() throws Exception {

}

 

四、 适用情况

根据上面对定义的分析,以及例子的说明,可以看出模板方法适用于以下情况:

1)         一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

2)         各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。其实这可以说是一种好的编码习惯了。

3)         控制子类扩展。模板方法只在特定点调用操作,这样就只允许在这些点进行扩展。比如上面 runBare ()方法就只在 runTest 前面适用 setUp 方法。如果你不愿子类来修改你的模板方法定义的框架,你可以采用两种方式来做:一是在 API 中不体现出你的模板方法;二、将你的模板方法置为 final 就可以了。

       可以看出,使用模板方法模式可以将代码的公共行为提取出来,达到复用的目的。而且,在模板方法模式中,是由父类的模板方法来控制子类中的具体实现。这样你在实现子类的时候,根本不需要对业务流程有太多的了解。

 

五、 总结

匆匆忙忙写完了。希望大家指正!
posted @ 2006-08-29 21:39 Alex 阅读(233) | 评论 (0)编辑 收藏

key words: 门面模式 facade模式

一、 引子

门面模式是非常简单的设计模式。

 

二、 定义与结构

门面模式( facade )又称外观模式。 GOF 在《设计模式》一书中给出如下定义:为子系统中的一组接口提供一个一致的界面, Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

定义中提到的子系统是指在设计中为了降低复杂性根据一定的规则(比如业务、功能),对系统进行的划分。子系统中封装有一些类。客户程序在使用子系统的时候,可能会像下图一样零乱。

在上面的实现方法中,客户类紧紧地依赖在子系统的实现上。子系统发生的变化,很可能要影响到客户类的调用。而且子系统在不断优化、可重用化的重构路上,会产生更多更小的类。这对使用子系统的客户类来说要完成一个工作流程,似乎要记住的接口太多了。

门面模式就是为了解决这种问题而产生的。看看使用了门面模式后的图:

这样就减少了客户程序和子系统之间的耦合,增加了可维护性。

很明显,门面模式有三个角色组成:

1)         门面角色( facade ):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。

2)         子系统角色:实现了子系统的功能。对它而言, façade 角色就和客户角色一样是未知的,它没有任何 façade 角色的信息和链接。

3)         客户角色:调用 façade 角色来完成要得到的功能。

   

   

三、 举例

Facade 一个典型应用就是进行数据库连接。一般我们在每一次对数据库进行访问,都要进行以下操作:先得到 connect 实例,然后打开 connect 获得连接,得到一个 statement ,执行 sql 语句进行查询,得到查询结果集。

       我们可以将这些步骤提取出来,封装在一个类里面。这样,每次执行数据库访问只需要将必要的参数传递到这个类中就可以了。

       有兴趣可以在你正在进行的系统中实现一下。

   

   

四、 使用环境和优点

《设计模式》给出了门面模式的使用环境:

1)         当你要为一个复杂子系统提供一个简单接口时。在上面已经描述了原因。

2)         客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性(上面也提到了)。

3)         当你需要构建一个层次结构的子系统时,使用 facade 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过 facade 进行通讯,从而简化了它们之间的依赖关系。

以下是它的优点:

1)         它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。

2)         它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。 Facade 模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。 Facade 模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用 Facade 可以降低编译依赖性,限制重要系统中较小的变化所需的重编译工作。 Facade 模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。

3)         如果应用需要,它并不限制它们使用子系统类。因此你可以让客户程序在系统易用性和通用性之间加以选择。

 

 

  五、 java 中的门面模式

先来想想门面模式和我们已经讲过的哪个模式相像?答案就是 抽象工厂 模式 两者虽然在分类上有所区别,但是都是为了方便客户程序的使用而建立。两者的不同应该在于门面模式不仅方便了客户的使用,而且隐藏了不该让客户知道的类(这些类仅仅为子系统的其他类服务)。

但是在 java 语言中提供的包的概念已经能够很好的解决上面门面模式提到的问题。你可以把一个子系统放在一个包里面,里面要提供给外面访问的类定义为 public ,而不该公布的类就可以设计为非 public

因此,在一定程度上,门面模式在 java 中基本上可以不使用了。

标准的门面模式虽然可以不再使用,但是这种提供一个中间类或者中间方法来方便客户程序使用的思想应该值得我们来实践的

   

   

六、 总结  

门面模式从整体来看,给我的感觉是,它对于使两层之间的调用粗颗粒化很有帮助,避免了大量细颗粒度的访问。这和 SOA 中的一些观点是相同的。

   门面模式大体介绍完了。请指正:)

门面模式从整体来看,给我的感觉是,它对于使两层之间的调用粗颗粒化很有帮助,避免了大量细颗粒度的访问。这和 SOA 中的一些观点是相同的。

   门面模式大体介绍完了。请指正:)

posted @ 2006-08-29 20:46 Alex 阅读(4275) | 评论 (0)编辑 收藏

一、 引子

单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用,在我前面的几篇文章中也结合其它模式使用到了单例模式。这里我们就单例模式进行系统的学习。并对有人提出的 单例模式是邪恶的 这个观点进行了一定的分析。

 

二、 定义与结构

单例模式又叫做单态模式或者单件模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式中的 单例 通常用来代表那些本质上具有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。

单 例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的改变对象的个数。那么怎么来实现单例模式呢?一个类的对象的产生是由类构造 函数来完成的,如果想限制对象的产生,就要将构造函数变为私有的(至少是受保护的),使得外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必 须提供一个自己的对象以及访问这个对象的静态方法。

现在对单例模式有了大概的了解了吧,其实单例模式在实现上是非常简单的 —— 只有一个角色,而客户则通过调用类方法来得到类的对象。

放上一个类图吧,这样更直观一些:

 

单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象, 多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。

 

三、 实现

在单例模式的实现上有几种不同的方式,我在这里将一一讲解。先来看一种方式,它在《 java 与模式》中被称为饿汉式。

 

public class Singleton {

// 在自己内部定义自己一个实例

// 注意这是 private 只供内部调用

private static Singleton instance = new Singleton();

// 如上面所述,将构造函数设置为私有

private Singleton(){

}  

// 静态工厂方法, 提供了一个供外部访问得到对象 的静态方法  
   public static Singleton getInstance() {
     return instance;   
   }
}

 

       下面这种方式被称为懒汉式: P

 

public class Singleton {

       // 和上面有什么不同?

private static Singleton instance = null;

// 设置为私有的构造函数

private Singleton(){

}  

// 静态工厂方法

public static synchronized Singleton getInstance() {

// 这个方法比上面有所改进
          if (instance==null)

              instance new Singleton();
          return instance;   

}

}

 

先让我们来比较一下这两种实现方式。

首先他们的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。  

在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环境中产生多个实例;而在第一种方式中则不存在这种情况。

       在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了同步处理,在反应速度上要比第一种慢一些。

       java 与模式》书中提到,就 java 语言来说,第一种方式更符合 java 语言本身的特点。

       以 上两种实现方式均失去了多态性,不允许被继承。还有另外一种灵活点的实现,将构造函数设置为受保护的,这样允许被继承产生子类。这种方式在具体实现上又有 所不同,可以将父类中获得对象的静态方法放到子类中再实现;也可以在父类的静态方法中进行条件判断来决定获得哪一个对象;在 GOF 中认为最好的一种方式是维护一张存有对象和对应名称的注册表(可以使用 HashMap 来实现)。下面的实现参考《 java 与模式》采用带有注册表的方式。

 

import java.util.HashMap;

 

public class Singleton

{

// 用来存放对应关系

       private static HashMap sinRegistry = new HashMap();

       static private Singleton s = new Singleton();

       // 受保护的构造函数

       protected Singleton()

       {}

       public static Singleton getInstance(String name)

       {

              if(name == null)

                     name = "Singleton";

              if(sinRegistry.get(name)==null)

              {

                     try{

                            sinRegistry.put(name , Class.forName(name).newInstance());

                     }catch(Exception e)

                     {

                            e.printStackTrace();

                     }     

              }

              return (Singleton)(sinRegistry.get(name)); 

       }

       public void test()

       {

              System.out.println("getclasssuccess!");      

       }

}

 

public class SingletonChild1 extends Singleton

{

       public SingletonChild1(){}

       static    public SingletonChild1 getInstance()

       {

              return (SingletonChild1)Singleton.getInstance("SingletonChild1");     

       }

       public void test()

       {

              System.out.println("getclasssuccess111!"); 

       }

}

 

java 中子类的构造函数的范围不能比父类的小,所以可能存在不守规则的客户程序使用其构造函数来产生实例。

 

四、单例模式邪恶论

看这题目也许有点夸张,不过这对初学者是一个很好的警告。单例模式在 java 中的使用存在很多陷阱和假象,这使得没有意识到单例模式使用局限性的你在系统中布下了隐患……

其实这个问题早在 2001 年的时候就有人在网上系统的提出来过,我在这里只是老生常谈了。但是对于大多的初学者来说,可能这样的观点在还很陌生。下面我就一一列举出单例模式在 java 中存在的陷阱。

 

多个虚拟机

当系统中的单例类被拷贝运行在多个虚拟机下的时候,在每一个虚拟机下都可以创建一个实例对象。在使用了 EJB JINI RMI 技术的分布式系统中,由于中间件屏蔽掉了分布式系统在物理上的差异,所以对你来说,想知道具体哪个虚拟机下运行着哪个单例对象是很困难的。

因此,在使用以上分布技术的系统中,应该避免使用存在状态的单例模式,因为一个有状态的单例类,在不同虚拟机上,各个单例对象保存的状态很可能是不一样的,问题也就随之产生。而且在 EJB 中不要使用单例模式来控制访问资源,因为这是由 EJB 容器来负责的。在其它的分布式系统中,当每一个虚拟机中的资源是不同的时候,可以考虑使用单例模式来进行管理。

 

多个类加载器

当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间( namespace )来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。

也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少见。在很多 J2EE 服务器上允许存在多个 servlet 引擎,而每个引擎是采用不同的类加载器的;浏览器中 applet 小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,等等。

       这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一般情况下不要使用存在状态的单例模式。

 

       错误的同步处理

       在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能会达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定要对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。

 

       子类破坏了对象控制

       在上一节介绍最后一种扩展性较好的单例模式实现方式的时候,就提到,由于类构造函数变得不再私有,就有可能失去对对象的控制。这种情况只能通过良好的文档来规范。

 

       串行化(可序列化)

为了使一个单例类变成可串行化的,仅仅在声明中添加“ implements Serializable ”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入 readResolve 方法。 关于这个方法的具体情况请参考《 Effective Java 》一书第 57 条建议。

其实对象的串行化并不仅局限于上述方式,还存在基于 XML 格式的对象串行化方式。这种方式也存在上述的问题,所以在使用的时候要格外小心。

 

       上面罗列了一些使用单例模式时可能会遇到的问题。而且这些问题都和 java 中的类、线程、虚拟机等基础而又复杂的概念交织在一起,你如果稍不留神……。但是这并不代表着单例模式就一无是处,更不能一棒子将其打死。它还是不可缺少的一种基础设计模式,它对一些问题提供了非常有效的解决方案,在 java 中你完全可以把它看成编码规范来学习,只是使用的时候要考虑周全些就可以了。

 

五、 题外话

抛开单例模式,使用下面一种简单的方式也能得到单例,而且如果你确信此类永远是单例的,使用下面这种方式也许更好一些。

public static final Singleton INSTANCE = new Singleton();

而使用单例模式提供的方式,这可以在不改变 API 的情况下,改变我们对单例类的具体要求。

 

六、 总结

竭尽所能写下了关于单例模式比较详细的介绍,请大家指正。
posted @ 2006-08-29 19:05 Alex 阅读(360) | 评论 (0)编辑 收藏

一、引子

装饰模式?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的地方。家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活多一点色彩。而墙还是那堵墙,他的本质一点都没有变,只是多了一层外衣而已。

那设计模式中的装饰模式,是什么样子呢?

 

二、定义与结构

装饰模式( Decorator )也叫包装器模式( Wrapper )。 GOF 在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator 模式相比生成子类更为灵活。

       让我们来理解一下这句话。我们来设计 这个类。假设你根据需求为 类作了如下定义:

现在,在系统的一个地方需要一个能够报警的 Door ,你来怎么做呢?你或许写一个 Door 的子类 AlarmDoor ,在里面添加一个子类独有的方法 alarm() 。嗯,那在使用警报门的地方你必须让客户知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了 Liskov 替换原则。

也许你要说,那就把这个方法添加到 Door 里面,这样不就统一了?但是这样所有的门都必须有警报,至少是个 哑巴 警报。而当你的系统仅仅在一两个地方使用了警报门,这明显是不合理的 —— 虽然可以使用缺省适配器来弥补一下。

       这时候,你可以考虑采用装饰模式来给门动态的添加些额外的功能。

       下面我们来看看装饰模式的组成,不要急着去解决上面的问题,到了下面自然就明白了!

1)         抽象构件角色( Component ):定义一个抽象接口,以规范准备接收附加责任的对象。

2)         具体构件角色 (Concrete Component) :这是被装饰者,定义一个将要被装饰增加功能的类。

3)         装饰角色 (Decorator) :持有一个构件对象的实例,并定义了抽象构件定义的接口。

4)         具体装饰角色 (Concrete Decorator) :负责给构件添加增加的功能。

看下装饰模式的类图:

图中 ConcreteComponent 可能继承自其它的体系,而为了实现装饰模式,他还要实现 Component 接口。整个装饰模式的结构是按照组合模式来实现的,但是注意两者的目的是截然不同的(关于两者的不同请关注我以后的文章)。

 

三、举例

这个例子还是来自我最近在研究的 JUnit ,如果你对 JUnit 还不太了解,可以参考 JUnit入门》 JUnit源码分析(一)》 JUnit源码分析(二)》 JUnit源码分析(三)》 。不愧是由 GoF 之一的 Erich Gamma 亲自开发的,小小的东西使用了 N 种的模式在里面。下面就来看看 JUnit 中的装饰模式。

       JUnit 中, TestCase 是一个很重要的类,允许对其进行功能扩展。

       junit.extensions 包中, TestDecorator RepeatedTest 便是对 TestCase 的装饰模式扩展。下面我们将它们和上面的角色对号入座。

       呵呵,看看源代码吧,这个来的最直接!

       // 这个就是抽象构件角色

       public interface Test {

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public abstract int countTestCases();

       /**

        * Runs a test and collects its result in a TestResult instance.

        */

       public abstract void run(TestResult result);

}

 

// 具体构件对象,但是这里是个抽象类

public abstract class TestCase extends Assert implements Test {

       ……

       public int countTestCases() {

              return 1;

       }

       ……

       public TestResult run() {

              TestResult result= createResult();

              run(result);

              return result;

       }

       public void run(TestResult result) {

              result.run(this);

       }

       ……

}

 

// 装饰角色

public class TestDecorator extends Assert implements Test {

       // 这里按照上面的要求,保留了一个对构件对象的实例

       protected Test fTest;

 

       public TestDecorator(Test test) {

              fTest= test;

       }

       /**

        * The basic run behaviour.

        */

       public void basicRun(TestResult result) {

              fTest.run(result);

       }

       public int countTestCases() {

              return fTest.countTestCases();

       }

       public void run(TestResult result) {

              basicRun(result);

       }

       public String toString() {

              return fTest.toString();

       }

       public Test getTest() {

              return fTest;

       }

}

 

       // 具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数

public class RepeatedTest extends  TestDecorator {

    private int fTimesRepeat;

 

    public RepeatedTest(Test test, int repeat) {

           super(test);

           if (repeat < 0)

                  throw new IllegalArgumentException("Repetition count must be > 0");

           fTimesRepeat= repeat;

    }

    // 看看怎么装饰的吧

    public int countTestCases() {

           return super.countTestCases()*fTimesRepeat;

    }

    public void run(TestResult result) {

           for (int i= 0; i < fTimesRepeat; i++) {

                  if (result.shouldStop())

                         break;

                  super.run(result);

           }

    }

    public String toString() {

           return super.toString()+"(repeated)";

    }

}

       使用的时候,就可以采用下面的方式:

TestDecorator test = new RepeatedTest(new TestXXX() , 3);

让我们在回想下上面提到的 的问题,这个警报门采用了装饰模式后,可以采用下面的方式来产生。

DoorDecorator alarmDoor = new AlarmDoor(new Door());

 

 

四、应用环境

       GOF 书中给出了以下使用情况:

1)         在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2)         处理那些可以撤消的职责。

3)         当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

来分析下 JUnit 的使用是属于哪种情况。首先实现了比静态继承更加灵活的方式,动态的增加功能。试想为 Test 的所有实现类通过继承来增加一个功能,意味着要添加不少的功能类似的子类,这明显是不太合适的。

而且,这就避免了高层的类具有太多的特征,比如上面提到的带有警报的抽象门类。

 

五、透明和半透明

       对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。这样就要求装饰角色和具体装饰角色要满足 Liskov 替换原则。像下面这样:

Component c = new ConcreteComponent();

Component c1 = new ConcreteDecorator(c);

JUnit 中就属于这种应用,这种方式被称为透明式。而在实际应用中,比如 java.io 中往往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能对客户程序隐瞒具体的类型。这种方式称为 半透明式

java.io 中,并不是纯装饰模式的范例,它是装饰模式、适配器模式的混合使用。

 

六、其它

采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。这是 GOF 提到的装饰模式的缺点,你能体会吗?他们所说的小对象我认为是指的具体装饰角色。这是为一个对象动态添加功能所带来的副作用。

 

七、总结

       终于写完了,不知道说出了本意没有。请指正!
posted @ 2006-08-29 18:57 Alex 阅读(468) | 评论 (0)编辑 收藏

一、 引子

还记得警匪片上,匪徒们是怎么配合实施犯罪的吗?一个团伙在进行盗窃的时候,总     有一两个人在门口把风——如果有什么风吹草动,则会立即通知里面的同伙紧急撤退。也许放风的人并不一定认识里面的每一个同伙;而在里面也许有新来的小弟不认识这个放风的。但是这没什么,这个影响不了他们之间的通讯,因为他们之间有早已商定好的暗号。

呵呵,上面提到的放风者、偷窃者之间的关系就是观察者模式在现实中的活生生的例子。

 

二、 定义与结构

观察者( Observer )模式又名发布 - 订阅( Publish/Subscribe )模式。 GOF 给观察者模式如下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在这里先讲一下面向对象设计的一个重要原则——单一职责原则。因此系统的每个对象应该将重点放在问题域中的离散抽象上。因此理想的情况下,一个对象只做一件事情。这样在开发中也就带来了诸多的好处:提供了重用性和维护性,也是进行重构的良好的基础。

因此几乎所有的设计模式都是基于这个基本的设计原则来的。观察者模式的起源我觉得应该是在 GUI 和业务数据的处理上,因为现在绝大多数讲解观察者模式的例子都是这一题材。但是观察者模式的应用决不仅限于此一方面。

下面我们就来看看观察者模式的组成部分。

1)         抽象目标角色( Subject ):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。

2)         抽象观察者角色( Observer ):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。

3)         具体目标角色( Concrete Subject ):将有关状态存入各个 Concrete Observer 对象。当它的状态发生改变时 , 向它的各个观察者发出通知。

4)         具体观察者角色( Concrete Observer ):存储有关状态,这些状态应与目标的状态保持一致。实现 Observer 的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向 Concrete Subject 对象的引用。

放上观察者模式的类图,这样能将关系清晰的表达出来。

       可以看得出来,在 Subject 这个抽象类中,提供了上面提到的功能,而且存在一个通知方法: notify 。还可以看出来 Subject ConcreteSubject 之间可以说是使用了模板模式(这个模式真是简单普遍到一不小心就用到了)。

       这样当具体目标角色的状态发生改变,按照约定则会去调用通知方法,在这个方法中则会根据目标角色中注册的观察者名单来逐个调用相应的 update 方法来调整观察者的状态。这样观察者模式就走完了一个流程。

       在下面的例子中会更深刻的体验到这个流程的。

 

三、 举例

观察者模式是我在《 JUnit 源代码分析》中遗留的一个模式,因此这里将采用 JUnit 来作为例子。

JUnit 为用户提供了三种不同的测试结果显示界面,以后还可能会有其它方式的现实界面……。怎么才能将测试的业务逻辑和显示结果的界面很好的分离开?不用问,就是观察者模式!

下面我们来看看 JUnit 中观察者模式的使用代码:

// 下面是我们的抽象观察者角色, JUnit 是采用接口来实现的,这也是一般采用的方式。

// 可以看到这里面定义了四个不同的 update 方法,对应四种不同的状态变化

public interface TestListener {

       /**

        * An error occurred.

        */

       public void addError(Test test, Throwable t);

       /**

        * A failure occurred.

        */

       public void addFailure(Test test, AssertionFailedError t); 

       /**

        * A test ended.

        */

       public void endTest(Test test);

       /**

        * A test started.

        */

       public void startTest(Test test);

}

 

// 具体观察者角色,我们采用最简单的 TextUI 下的情况来说明( AWT Swing 对于整天做 Web 应用的人来说,已经很陌生了)

public class ResultPrinter implements TestListener {

       // 省略好多啊,主要是显示代码

……

       // 下面就是实现接口 TestListener 的四个方法

       // 填充方法的行为很简单的说

       /**

        * @see junit.framework.TestListener#addError(Test, Throwable)

        */

       public void addError(Test test, Throwable t) {

              getWriter().print("E");

       }

       /**

        * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)

        */

       public void addFailure(Test test, AssertionFailedError t) {

              getWriter().print("F");

       }

       /**

        * @see junit.framework.TestListener#endTest(Test)

        */

       public void endTest(Test test) {

       }

       /**

        * @see junit.framework.TestListener#startTest(Test)

        */

       public void startTest(Test test) {

              getWriter().print(".");

              if (fColumn++ >= 40) {

                     getWriter().println();

                     fColumn= 0;

              }

       }

}

 

来看下我们的目标角色,随便说下,由于 JUnit 功能的简单,只有一个目标—— TestResult ,因此 JUnit 只有一个具体目标角色。

// 好长的代码,好像没有重点。去掉了大部分与主题无关的信息

// 下面只列出了当 Failures 发生时是怎么来通知观察者的

public class TestResult extends Object {

       // 这个是用来存放测试 Failures 的集合

protected Vector fFailures;

// 这个就是用来存放注册进来的观察者的集合

       protected Vector fListeners;

 

       public TestResult() {

              fFailures= new Vector();

              fListeners= new Vector();

       }

       /**

        * Adds a failure to the list of failures. The passed in exception

        * caused the failure.

        */

       public synchronized void addFailure(Test test, AssertionFailedError t) {

              fFailures.addElement(new TestFailure(test, t));

              // 下面就是通知各个观察者的 addFailure 方法

              for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {

                     ((TestListener)e.nextElement()).addFailure(test, t);

              }

       }

       /**

        * 注册一个观察者

        */

       public synchronized void addListener(TestListener listener) {

              fListeners.addElement(listener);

       }

       /**

        * 删除一个观察者

        */

       public synchronized void removeListener(TestListener listener) {

              fListeners.removeElement(listener);

       }

       /**

        * 返回一个观察者集合的拷贝,当然是为了防止对观察者集合的非法方式操作了

     * 可以看到所有使用观察者集合的地方都通过它

        */

       private synchronized Vector cloneListeners() {

              return (Vector)fListeners.clone();

       }

       ……

}

 

嗯,观察者模式组成所需要的角色在这里已经全了。不过好像还是缺点什么……。呵呵,对!就是它们之间还没有真正的建立联系。在 JUnit 中是通过 TestRunner 来作的,而你在具体的系统中可以灵活掌握。

看一下 TestRunner 中的代码:

public class TestRunner extends BaseTestRunner {

       private ResultPrinter fPrinter;

public TestResult doRun(Test suite, boolean wait) {

// 就是在这里注册的

              result.addListener(fPrinter);

……

 

四、 使用情况

GOF 给出了以下使用观察者模式的情况:

1)         当一个抽象模型有两个方面 , 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

2)         当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。

3)         当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。

其实观察者模式同前面讲过的桥梁、策略有着共同的使用环境:将变化独立封装起来,以达到最大的重用和解耦。观察者与后两者不同的地方在于,观察者模式中的目标和观察者的变化不是独立的,而是有着某些联系。

 

五、 我推你拉

观 察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本。一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”;观察者角色如果 想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中“拉”出 来的。

还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要,先给你啦。

这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目标角色比较简单,则“拉模式”就很合适啦。

 

六、 总结

大概的介绍了下观察者模式。希望能对你有所帮助。

posted @ 2006-08-29 18:08 Alex 阅读(287) | 评论 (0)编辑 收藏

一、 引子

记得一年前,我开始陆陆续续在自己的 blog 上连载《深入浅出设计模式》。其内容无出经典巨著《设计模式》之右,仅仅偶有己见,但是它记录了我学习、思考和讲述设计模式的过程。一晃,距离写成最后一片设计模式的文章已有 3 月余,我却迟迟没有对设计模式做一个总结。心想,总不能虎头蛇尾吧,于是便有了这篇文章。

 

二、 回顾 23 种设计模式

先来回顾下这 23 种经典的设计模式吧,下图给出了 GOF 对它们的分类:

 

   

    图中从两个纬度将 23 种设计模式划分为六大类:创建型类模式、创建型对象模式、结构型类模式、结构型对象模式、行为型类模式、行为型对象模式。 GOF 对这 23 种模式的划分是有一定道理的,虽然人为的类型划分,说到底还是有些牵强,但是如 GOF 所说,它至少可以帮助记忆学习。

    被分为六大块的 23 种设计模式并不是割裂开来的,很多模式的使用往往是相生相伴的,像工厂与单例,装饰与组合等等。 GOF 给出了模式间的关系详细描述如下图:

 

   

    记得曾经不止一次有人问我:这模式和那模式感觉上一样啊,有什么区别啊。同样,在很多论坛上也充满了这样的疑问。其实这是很正常的,面向对象设计、编程所能使用的方式不外乎这几种:继承、组合、封装行为、利用多态等等,所以 23 种模式中翻来覆去的使用这几种方式,看起来当然是似曾相逢。有人曾留言给我,让我着重表述这些模式之间的区别与类似。我当时也许诺会在最后写一篇总结性的文章专门讨论这个话题,但是现在我不打算这样干了。

 

三、俯瞰全局、 追踪溯源

    什么是设计模式? GOF 在书中如是说: 设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述; John Vlissides 曾说过:在设计模式中,仅有的、最重要的就是不断的反省;而我将它比作软件开发中经验积累出来的“公式”。

    通看这 23 种模式,就应了 Dennis DeBruler 曾说过的一句话:计算机科学是一门相信所有问题都可以通过多一个间接层 indirection )来解决的科学。在前面关于具体模式的文章中,我曾经不只一次的提到“中间层”。但是直到读到这句话,才使我跳出具体模式的束缚俯瞰全局。

    23 种模式似乎不再神奇,它们在解决问题上的思路是如此的相似——添加间接层。何止模式如此,正如 Dennis DeBruler 所言,我们所接触的很多技术都是采用这种手段,如: J2EE 的分层技术、重构等等。但是在具体操作上, 23 种模式中间接层的应用可大可小,无比灵活。观察者模式在动作触发端与动作执行端之间加入了目标角色层,解除了两端之间的耦合,巧妙地解决了一对多的关系;单例模式将构造方法私有化,并在使用者与构造方法之间添加一个获得唯一实例的静态方法,达到控制实例个数的目的。

    间接层应用如此广泛,得益于它能带来如下好处:共享逻辑(重用)、分离意图和实现(提高灵活性)、 隔 离变化(封装)、解耦等等。既然我们知道了间接层这么一回事,似乎我们可以不用知道设计模式也能做出像设计模式那样好的设计来。但是要记住,间接层应用过 于泛滥,则会过犹不及,它会导致简单问题复杂化、跳跃阅读难以理解等问题。那如何在设计中把握使用间接层的度呢?设计模式也许是很好的范例——你毕竟是站 在了巨人的肩上。

   

再深入一层细看,在设计模式中,广泛遵循了两条设计原则:面向接口编程,而不是实现;优先使用组合,而不是继承。这两条原则带来的好处,自然不用再说了。说到设计原则, 现在为人熟知的设计原则有很多,如: 单一职责原则( SRP )、开闭原则( OCP )、 Liskov 替换原则( LSP )、依赖倒置原则( DIP )和接口隔离原则( ISP )等等。这些原则的出现大多都早于设计模式,但同设计模式一样,是 OOD 过程中经验积累的结晶。它们在 23 种设计模式中都有体现,因此了解设计原则可以帮助你更好的分析和理解设计模式。

当然,这并不是说设计模式就是建立在设计原则的基础之上的。两者之间的关系是互相促进的。设计原则的诞生,也许会促成新的设计模式;设计模式的出现,也许会提炼出新的设计原则。

 

现在不禁要问,为什么使用设计模式。也许你的回答会是:提高设计的重用度、灵活性、可维护性等等。但是我认为更准确的回答应该是:解决系统设计中现有的问题。这便又回到了 GOF 给设计模式下的定义上了,绕了一个圈子,原来答案就这么简单。

这也就是我不想详细讲解各种模式区别的原因了,要解决的问题不一样就是它们之间最大的不同。如果还要详细分析,那它们都已写在 GOF 巨著中了——就是它们的定义、使用范围、优缺点等等。

 

四、 活学活用

是否我们必须按照这本巨著上描述的形式来使用设计模式呢?肯定不是这样的。数学物理公式在不同的条件下还会有不同的衍生式,何况这些在实践中总结的经验呢。

Martin 建 议根据问题的复杂性逐步的引入设计模式。这是个很好的建议,它避免你套用模式而带来了过度的设计,而这些过度的设计也许直到最后都派不上用场。比如,你的 系统中现在就仅有一个适配器角色,或者各个适配器角色没有什么共性,那么目标角色和适配器角色就可以合为一个,这样使得设计模式更加符合你系统的特性。

不 仅如此,做到设计模式的活学活用,我认为还要做到以解决问题为中心,将设计模式融合使用,避免为了设计而模式。当然这是建立在对各种设计模式了如指掌的情 况下。比如,有一段解析字符串的对象,而在使用它之前,还要做一些参数的判断等其他非解析操作,这时,你很快就会想起使用代理模式;但是这个代理提供的接 口可能有两三个(满足不同情形),而解析对象仅有一个接口,命名还不相同,这时你又想到了适配器模式。这时也许将两者融合成你独有的解决方案比笨拙的套用 两个模式好的多得多。

 

五、现在看模式

GOF 说过,这 23 种模式仅仅是对一般设计问题的总结。现在许多专有领域也开始出现了大量的设计模式,至少在我最了解的企业应用这个方向是这样的。其中有一部分模式仅仅是对 GOF 设计模式的再次包装。我们不妨叫 GOF 23 种设计模式为原始设计模式。

但是遗憾的是,越来越方便的框架支持,领域模型简化造成代码过程化、脚本化,使得在企业应用中很难看到原始设计模式的影子(当然还是可以看到遍地的命令模式)。比如: IoC 容器将单例、工厂、原型模式包装了起来,你现在需要做的仅仅是填写配置文件;框架集成了观察者、模版等等模式,你仅仅按照框架说明实现具体对象就可以了;过程化、脚本化的代码里面更不要提什么设计模式了!更甚者在 EJB 中单例模式差点变成了反模式。

原 始的设计模式没有用,过时了吗?如果你不甘心仅仅做一名代码组装工;想对你们部门的高手设计的框架品头论足的话,答案就是否定的。原始设计模式有很多的确 是难得一见了,但是了解它们绝对不是在浪费你的时间,它可以让你在解决问题的时候思路更开阔一些——它的思想比它的架势更重要。          

 

 

写在最后(其实应该在最前面)

    细想自己在学习设计模式时,常常埋怨《 Java 与模式》肤浅无物,为了模式而模式;又感叹 GOF 写 得高度概括,苦于理解。于是便有了将自己对设计模式的认识写下来的想法。正巧参与了部门组织的一次设计模式讲座,触发了第一篇文章的诞生。从现在来看,文 章倒是全写成了,可内容上却不能让自己满意,却又懒得动手修改(谈何容易)。“深入”二字说来容易,做到何其难,自以为这些文章的分量远远够不上;倒是 “浅出”,自以为还可以沾上点边。你可以把本系列文章看作是《 Java 与模式》的替代品,帮你叩开设计模式之门。如果你要深入研究设计模式,我劝你还是去研读《设计模式》一书吧。
posted @ 2006-08-29 17:56 Alex 阅读(218) | 评论 (0)编辑 收藏

一、引子
      
话说十年前,有一个暴发户,他家有三辆汽车 ——Benz 奔驰、 Bmw 宝马、 Audi 奥迪,还雇了司机为他开车。不过,暴发户坐车时总是怪怪的:上 Benz 车后跟司机说 开奔驰车! ,坐上 Bmw 后他说 开宝马车! ,坐上 Audi 开奥迪车! 。你一定说:这人有病!直接说开车不就行了?!
       
而当把这个暴发户的行为放到我们程序设计中来时,会发现这是一个普遍存在的现象。幸运的是,这种有病的现象在 OO (面向对象)语言中可以避免了。下面就以 Java 语言为基础来引入我们本文的主题:工厂模式。

二、分类
      
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

工厂模式在《 Java 与模式》中分为三类:
      1
)简单工厂模式( Simple Factory

2 )工厂方法模式( Factory Method

3 )抽象工厂模式( Abstract Factory
      
这三种模式从上到下逐步抽象,并且更具一般性。
       GOF
在《设计模式》一书中将工厂模式分为两类:工厂方法模式( Factory Method )与抽象工厂模式( Abstract Factory )。将简单工厂模式( Simple Factory )看为工厂方法模式的一种特例,两者归为一类。

两者皆可,在本文使用《 Java 与模式》的分类方法。下面来看看这些工厂模式是怎么来 治病 的。

 

三、简单工厂模式

简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
      
先来看看它的组成:

1)         工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在 java 中它往往由一个具体类实现。

2)         抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在 java 中由接口或者抽象类来实现。

3)         具体产品角色:工厂类所创建的对象就是此角色的实例。在 java 中由一个具体类实现。

来用类图来清晰的表示下的它们之间的关系(如果对类图不太了解,请参考我关于类图的文章):  

那么简单工厂模式怎么来使用呢?我们就以简单工厂模式来改造暴发户坐车的方式 —— 现在暴发户只需要坐在车里对司机说句: 开车 就可以了。

// 抽象产品角色

public interface Car{

public void drive();
}

 

// 具体产品角色
public class Benz implements Car{

public void drive()  {

System.out.println("Driving Benz ");

}

}

public class Bmw implements Car{

public void drive()  {

System.out.println("Driving Bmw ");

}

}
。。。(奥迪我就不写了 :P


//
工厂类角色

public class Driver{

// 工厂方法 . 注意 返回类型为抽象产品角色
       public staticCar driverCar(String s)throws Exception    {

              // 判断逻辑,返回具体的产品角色给 Client
              if(s.equalsIgnoreCase("Benz"))  

                     return new Benz();

              else if(s.equalsIgnoreCase("Bmw"))

                     return new Bmw();

                    ......   
             else throw new Exception();

       。。。


//
欢迎暴发户出场 ......

public class Magnate{

       public static void main(String[] args){

              try{
                     //
告诉司机我今天坐奔驰
              
                     Car car = Driver.driverCar("benz");
                     //
下命令:开车
                   
                     car.drive();

              。。。

    将本程序空缺的其他信息填充完整后即可运行。如果你将所有的类放在一个文件中,请不要忘记只能有一个类被声明为 public 。本程序在 jdk1.4 下运行通过。

      程序中各个类的关系表达如下:

 

这便是简单工厂模式了。怎么样,使用起来很简单吧?那么它带来了什么好处呢?
      
首先,使用了简单工厂模式后,我们的程序不在 有病 ,更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责 消费 产品(正如暴发户所为)。

       下 面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式。当暴发户增加了一辆车的时候,只要符合抽象产品制定的合同,那么只要通知工厂类知道 就可以被客户使用了。所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,因为每增加一辆车,都要在工厂类中增加相应的业务逻辑或者判断 逻辑,这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类(在我们的例子中是为司机师傅),我们称它为全能类或者上 帝类。
      
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,也累坏了我们这些程序员 :(
      
于是工厂方法模式作为救世主出现了。


四、工厂方法模式

       工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

你应该大致猜出了工厂方法模式的结构,来看下它的组成:

1)         抽象工厂角色:   这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。

2)         具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

3)         抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般有抽象类或者接口来实现。

4)         具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。

用类图来清晰的表示下的它们之间的关系:

工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的 上帝类 。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活起来 —— 当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。可以看出工厂角色的结构也是符合开闭原则的!

       我 们还是老规矩,使用一个完整的例子来看看工厂模式各个角色之间是如何来协调的。话说暴发户生意越做越大,自己的爱车也越来越多。这可苦了那位司机师傅了, 什么车它都要记得,维护,都要经过他来使用!于是暴发户同情他说:看你跟我这么多年的份上,以后你不用这么辛苦了,我给你分配几个人手,你只管管好他们就 行了!于是,工厂方法模式的管理出现了。代码如下:

// 抽象产品角色,具体产品角色与简单工厂模式类似,只是变得复杂了些,这里略。
//
抽象工厂角色
public interface Driver{
       public Car driverCar();
}
public class BenzDriver implements Driver{
       public Car driverCar(){
              return new Benz();
       }
}
public class BmwDriver implements Driver{
       public Car driverCar()   {

return new Bmw();
       }
}

// 应该和具体产品形成对应关系 ...
//
有请暴发户先生

 public class Magnate

{

              public static void main(String[] args)

              {

                     try{ 
                            Driver driver = new BenzDriver();

                            Car car = driver.driverCar();

                            car.drive();

                     }

       ……

}

可 以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。因为如果不能避免这种情况, 可以考虑使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。

 

五、小结

工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口。那我们是否一定要在代码中遍布工厂呢?大可不必。也许在下面情况下你可以考虑使用工厂方法模式:

1)         当客户程序不需要知道要使用对象的创建过程。

2)         客户程序使用的对象存在变动的可能,或者根本就不知道使用哪一个具体的对象。

 

简 单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么将判断 逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就象上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。

       面对这种情况, Java 的反射机制与配置文件的巧妙结合突破了限制 —— 这在 Spring 中完美的体现了出来。

 

六、抽象工厂模式

       先来认识下什么是产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。还是让我们用一个例子来形象地说明一下吧。

 

图中的 BmwCar BenzCar 就是两个产品树(产品层次结构);而如图所示的 BenzSportsCar BmwSportsCar 就是一个产品族。他们都可以放到跑车家族中,因此功能有所关联。同理 BmwBussinessCar BenzSportsCar 也是一个产品族。
     
回到抽象工厂模式的话题上。

可以说,抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。

抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象

而且使用抽象工厂模式还要满足一下条件:

1)         系统中有多个产品族,而系统一次只可能消费其中一族产品。

2)         同属于同一个产品族的产品以其使用。

来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):

1)         抽象工厂角色:   这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。

2)         具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在 java 中它由具体的类来实现。

3)         抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般有抽象类或者接口来实现。

4)         具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。

类图如下:

看过了前两个模式,对这个模式各个角色之间的协调情况应该心里有个数了,我就不举具体的例子了。只是一定要注意满足使用抽象工厂模式的条件哦。
posted @ 2006-08-29 17:26 Alex 阅读(466) | 评论 (0)编辑 收藏

工厂模式定义:提供创建对象的接口.

为何使用?
工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。

为什么工厂模式是如此常用?因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

我们以类Sample为例, 如果我们要创建Sample的实例对象:

Sample sample=new Sample();

可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值 查询数据库等。

首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:

Sample sample=new Sample(参数);

但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。

为什么说代码很难看,初学者可能没有这种感觉,我们分析如下,初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java 面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派“切割”成每段,将每段再 “封装”起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。

在本例中,首先,我们需要将创建实例的工作与使用实例的工作分开, 也就是说,让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。

这时我们就需要Factory工厂模式来生成对象了,不能再用上面简单new Sample(参数)。还有,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.现在Sample是接口,有两个子类MySample 和HisSample .我们要实例化他们时,如下:

Sample mysample=new MySample();
Sample hissample=new HisSample();

随着项目的深入,Sample可能还会"生出很多儿子出来", 那么我们要对这些儿子一个个实例化,更糟糕的是,可能还要对以前的代码进行修改:加入后来生出儿子的实例.这在传统程序中是无法避免的.

但如果你一开始就有意识使用了工厂模式,这些麻烦就没有了.

工厂方法
你会建立一个专门生产Sample实例的工厂:

public class Factory{

  public static Sample creator(int which){

  //getClass 产生Sample 一般可使用动态类装载装入类。
  if (which==1)
    return new SampleA();
  else if (which==2)
    return new SampleB();

  }

}

那么在你的程序中,如果要实例化Sample时.就使用

Sample sampleA=Factory.creator(1);

这样, 在整个就不涉及到Sample的具体子类,达到封装效果,也就减少错误修改的机会,这个原理可以用很通俗的话来比喻:就是具体事情做得越多,越容易范错 误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,范错误可能性就越少.好象我们从编程序中也能悟出人生道理?呵呵.

使用工厂方法 要注意几个角色,首先你要定义产品接口,如上面的Sample,产品接口下有Sample接口的实现类,如SampleA,其次要有一个factory类,用来生成产品Sample,如下图,最右边是生产的对象Sample:

进一步稍微复杂一点,就是在工厂类上进行拓展,工厂类也有继承它的实现类concreteFactory了

抽象工厂
工厂模式中有: 工厂方法(Factory Method) 抽象工厂(Abstract Factory).

这两个模式区别在于需要创建对象的复杂程度上。如果我们创建对象的方法变得复杂了,如上面工厂方法中是创建一个对象Sample,如果我们还有新的产品接口Sample2.

这里假设:Sample有两个concrete类SampleA和SamleB,而Sample2也有两个concrete类Sample2A和SampleB2

那么,我们就将上例中Factory变成抽象类,将共同部分封装在抽象类中,不同部分使用子类实现,下面就是将上例中的Factory拓展成抽象工厂:

public abstract class Factory{

  public abstract Sample creator();

  public abstract Sample2 creator(String name);

}

public class SimpleFactory extends Factory{

  public Sample creator(){
    .........
    return new SampleA

  }

  public Sample2 creator(String name){
    .........
    return new Sample2A

  }

}

public class BombFactory extends Factory{

  public Sample creator(){
    ......
    return new SampleB

  }

  public Sample2 creator(String name){
    ......
    return new Sample2B
  }

}

 

从上面看到两个工厂各自生产出一套Sample和Sample2,也许你会疑问,为什么我不可以使用两个工厂方法来分别生产Sample和Sample2?

抽 象工厂还有另外一个关键要点,是因为 SimpleFactory内,生产Sample和生产Sample2的方法之间有一定联系,所以才要将这两个方法捆绑在一个类中,这个工厂类有其本身特 征,也许制造过程是统一的,比如:制造工艺比较简单,所以名称叫SimpleFactory。

在实际应用中,工厂方法用得比较多一些,而且是和动态类装入器组合在一起应用,

举例

我们以Jive的ForumFactory为例,这个例子在前面的Singleton模式中我们讨论过,现在再讨论其工厂模式:

public abstract class ForumFactory {

  private static Object initLock = new Object();
  private static String className = "com.jivesoftware.forum.database.DbForumFactory";
  private static ForumFactory factory = null;

  public static ForumFactory getInstance(Authorization authorization) {
    //If no valid authorization passed in, return null.
    if (authorization == null) {
      return null;
    }
    //以下使用了Singleton 单态模式
    if (factory == null) {
      synchronized(initLock) {
        if (factory == null) {
            ......

          try {
              //动态转载类
              Class c = Class.forName(className);
              factory = (ForumFactory)c.newInstance();
          }
          catch (Exception e) {
              return null;
          }
        }
      }
    }

    //Now, 返回 proxy.用来限制授权对forum的访问
    return new ForumFactoryProxy(authorization, factory,
                    factory.getPermissions(authorization));
  }

  //真正创建forum的方法由继承forumfactory的子类去完成.
  public abstract Forum createForum(String name, String description)
  throws UnauthorizedException, ForumAlreadyExistsException;

  ....

}

 

 

因为现在的Jive是通过数据库系统存放论坛帖子等内容数据,如果希望更改为通过文件系统实现,这个工厂方法ForumFactory就提供了提供动态接口:

private static String className = "com.jivesoftware.forum.database.DbForumFactory";

你可以使用自己开发的创建forum的方法代替com.jivesoftware.forum.database.DbForumFactory就可以.

在上面的一段代码中一共用了三种模式,除了工厂模式外,还有Singleton单态模式,以及proxy模式,proxy模式主要用来授权用户对forum的访问,因为访问forum有两种人:一个是注册用户 一个是游客guest,那么那么相应的权限就不一样,而且这个权限是贯穿整个系统的,因此建立一个proxy,类似网关的概念,可以很好的达到这个效果.  

看看Java宠物店中的CatalogDAOFactory:

public class CatalogDAOFactory {

  /**

  * 本方法制定一个特别的子类来实现DAO模式。
  * 具体子类定义是在J2EE的部署描述器中。
  */

  public static CatalogDAO getDAO() throws CatalogDAOSysException {

    CatalogDAO catDao = null;

    try {

      InitialContext ic = new InitialContext();
      //动态装入CATALOG_DAO_CLASS
      //可以定义自己的CATALOG_DAO_CLASS,从而在无需变更太多代码
      //的前提下,完成系统的巨大变更。

      String className =(String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);

      catDao = (CatalogDAO) Class.forName(className).newInstance();

    } catch (NamingException ne) {

      throw new CatalogDAOSysException("
        CatalogDAOFactory.getDAO: NamingException while
          getting DAO type : \n" + ne.getMessage());

    } catch (Exception se) {

      throw new CatalogDAOSysException("
        CatalogDAOFactory.getDAO: Exception while getting
          DAO type : \n" + se.getMessage());

    }

    return catDao;

  }

}


CatalogDAOFactory 是典型的工厂方法,catDao是通过动态类装入器className获得CatalogDAOFactory具体实现子类,这个实现子类在Java宠物 店是用来操作catalog数据库,用户可以根据数据库的类型不同,定制自己的具体实现子类,将自己的子类名给与CATALOG_DAO_CLASS变量 就可以。

由此可见,工厂方法确实为系统结构提供了非常灵活强大的动态扩展机制,只要我们更换一下具体的工厂方法,系统其他地方无需一点变换,就有可能将系统功能进行改头换面的变化。

posted @ 2006-08-29 17:13 Alex 阅读(214) | 评论 (0)编辑 收藏

key words:正则表达式  模式匹配 javascript

摘要:收集一些常用的正则表达式。

正则表达式用于字符串处理,表单验证等场合,实用高效,但用到时总是不太把握,以致往往要上网查一番。我将一些常用的表达式收藏在这里,作备忘之用。本贴随时会更新。

匹配中文字符的正则表达式: [\u4e00-\u9fa5]

匹配双字节字符(包括汉字在内):[^\x00-\xff]

应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}

匹配空行的正则表达式:\n[\s| ]*\r

匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/

匹配首尾空格的正则表达式:(^\s*)|(\s*$)

应用:javascript中没有像vbscript那样的trim函数,我们就可以利用这个表达式来实现,如下:

String.prototype.trim = function()

{
    return this.replace(/(^\s*)|(\s*$)/g, "");
}

利用正则表达式分解和转换IP地址:

下面是利用正则表达式匹配IP地址,并将IP地址转换成对应数值的javascript程序:

function IP2V(ip)
{
 re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g  //匹配IP地址的正则表达式
if(re.test(ip))
{
return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1
}
else
{
 throw new Error("Not a valid IP address!")
}
}

不过上面的程序如果不用正则表达式,而直接用split函数来分解可能更简单,程序如下:

var ip="10.100.20.168"
ip=ip.split(".")
alert("IP值是:"+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))

匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*

匹配网址URL的正则表达式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?

得用正则表达式从URL地址中提取文件名的javascript程序,如下结果为page1

s="http://www.9499.net/page1.htm"
s=s.replace(/(.*\/){0,}([^\.]+).*/ig,"$2")
alert(s)

利用正则表达式限制网页表单里的文本框输入内容:

用正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^ \u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"

用正则表达式限制只能输入全角字符:  onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"

用正则表达式限制只能输入数字:onkeyup="value=value.replace(/[^ \d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace (/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

"^\\d+$"  //非负整数(正整数 + 0)
"^[0-9]*[1-9][0-9]*$"  //正整数
"^((-\\d+)|(0+))$"  //非正整数(负整数 + 0)
"^-[0-9]*[1-9][0-9]*$"  //负整数
"^-?\\d+$"    //整数
"^\\d+(\\.\\d+)?$"  //非负浮点数(正浮点数 + 0)
"^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$"  //正浮点数
"^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$"  //非正浮点数(负浮点数 + 0)
"^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"  //负浮点数
"^(-?\\d+)(\\.\\d+)?$"  //浮点数
"^[A-Za-z]+$"  //由26个英文字母组成的字符串
"^[A-Z]+$"  //由26个英文字母的大写组成的字符串
"^[a-z]+$"  //由26个英文字母的小写组成的字符串
"^[A-Za-z0-9]+$"  //由数字和26个英文字母组成的字符串
"^\\w+$"  //由数字、26个英文字母或者下划线组成的字符串
"^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"    //email地址
"^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$"  //url

"^[A-Za-z0-9_]*$"

posted @ 2006-08-29 15:02 Alex 阅读(576) | 评论 (1)编辑 收藏

     摘要: key words:正则表达式 深入浅出之正则表达式(一) 前言:        半年前我对正则表达式产生了兴趣,在网上查找过...  阅读全文
posted @ 2006-08-28 23:21 Alex 阅读(707) | 评论 (0)编辑 收藏

     摘要: 本程序最初是由wanghr100(灰豆宝宝.net)的checkForm基础上进行修改的,增加了很多功能,如下: 对非ie的支持 增加了内置表达式和内置提示 增加了显示方式(弹出式和页面显示式) 增加了显示一条和显示全部 进行了封装(CLASS_CHECK) 支持外接函数或表达式(应用在密码一致) ...  阅读全文
posted @ 2006-08-28 22:20 Alex 阅读(497) | 评论 (0)编辑 收藏

反向工程生成的PDM,表中NAME和CODE都一样,现在要修改NAME.
在修改一个表的NAME属性时,CODE属性也会跟着一起改变,怎么样才能让CODE不跟着NAME变呢?



Tools->General Options->Dialog->Name to Code Mirroring (去掉)
posted @ 2006-08-26 23:04 Alex 阅读(725) | 评论 (0)编辑 收藏