论坛首页 Java企业应用论坛

带给你一个全新的感觉SSH运用 - cswish struts plug-in

浏览 27002 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-12-08   最后修改:2008-12-08
写在最前:
下载地址:http://code.google.com/p/cswish/
讨论组:http://groups.google.com/group/cswish-plugin
安装介绍:http://groups.google.com/group/cswish-plugin/web/cswish---setup
暂时只有我一个人,哈哈,希望有人进去聊一下SSH方面的东东

渐入正题:
“你看到我手上拿的这个东西了吧,表面上看它是一个大哥大电话,但是你看这里有一层金属网膜,实际上,它是一个刮胡刀”
Struts without Action - 这是cswish带来的第一个特性。星爷的这句话得换成这样了:你看到这个Class了吧,表面上他是一个Service,但是你看它后面有cswish支撑,实际上,它是一个Action.

我想引入第一个话题:
一、Struts可以没有Action
问这个前,我们先看Action带来了什么,主要有:
1)页面跳转控制
2)数据校验
3)参数收集与过滤
4)VO的生命周期管理
其实,它也带来另一个副作用:
1)Action配置
2)人为引入一个不带业务含义的Action对象 (比方,HR Manager聘用一个新的staff,可以写成HrManager.hire(staff),本来很简单的东西,在SSH中变成了HrManagerAction.hire-->HrManager.hire)

大道至简,在面对这些问题时,也许他们本不是问题,只不过因为我们一些不好的设计制造出问题。这时,换位思考一下,也许我们要的不同零配置之类的概念--当Action也不存在的时候,配置也更无存在的理由了。

由此,cswish的第一个功能便产生了:struts without action,其原理是:
struts检测不到Action配置的存在,将调用cswish plugin处理,cswish调用com.googlecode.cswish.struts.Routing(注:可被再扩展或自定义,类似于ruby中的调度机制)尝试取得对应的action(第一优先级)或service(第二优先级),如果都没有,将调用内置的支持通用CRUD的Service进行处理。

将Service伪装成Action,是借助asm的字节码修改技术,将一个Service方法组装成一个Action类(对,没错,Method --> Class的转换),在转换后的Action类中,隐式的继承com.opensymphony.xwork2.ActionSupport,正如同任意一个Java class都隐式继承了Object对象一样。除此之外,为了加快OGNL对Action值的读取速度,这个新组装的Action实现Map接口,以字节码的形式组装对各个属性的读取,从而减少了部份method.invoker的性能开销。
(注,在OGNL中,他会检查Map接口,如果是Map,他会用((Map)obj).get(paramName)的方式取值,否则,他会调用method.invoke(obj))

其实,struts without action仅仅是表面现象,其实质是action被自动创建了。

题外话,很多人自已所在的项目中通常会对SSH做一些扩展,如果有涉及零配置,页面调度之类的处理,也许我这个插件中处理方式能帮得上忙(type是struts提供的endpoint(插入点?),class是自已的实现):
<bean type="com.opensymphony.xwork2.ObjectFactory" name="cswish" class="com.googlecode.cswish.struts.spring.ObjectFactory" />
有这个插入点,就有了所有对象的生杀大权,很好很强大。
<bean type="com.opensymphony.xwork2.UnknownHandler" class="com.googlecode.cswish.struts.CSwishUnknownHandler"/>
这是struts没有对应的Action的配置文件时,给出的一个插入点,我们可以对配置做出运行期的指定

二、生命周期
说到这个,就得问更好的生命周期要管理些什么。对象的创建,对象的类别,周期的类型。struts中能管的就是大对象概念了,要么都管,要么都不管,管理的方式是借助配置文件。创建一个对象也就是一个简单的new(引入spring后,稍强一点).
在cswish中,支持带条件的对象创建,是细粒度的对象生命周期,用简单的声明方式指定。

cswish通过引入了一个名叫@ParamScope的annotation,来指定对象的生命周期。它管的对象,既可以是整个对象,也可以是单个属性。
来一个代码示例(摘自com.googlecode.cswish.struts.spring.GenericJPAService):
@ParamScope
public PageInfo search(@GenericType Object model, int pageNo, boolean newSearch,Reference<QLInfo> qlInfoRef)

这里的ParamScope用了缺省值,写全的话是:@ParamScope(scope=ScopeValue.SESSION),它这里指出这个search的返回结果是session级别
另外Reference<QLInfo>被定义成
@ParamScope(visible=false, value="new com.googlecode.cswish.model.Reference()")
public class Reference<T> implements Serializable {

它表明这个参数是Session级别的,且外面用户不能修改这个值(同visible=false控制),另外你还可以看来,在cswish的对象生命周期管理中,可以把OGNL表达式放到value上,实现复杂条件的对象创建。
由上面这个例子可以看到,我们要把一个参数保留,或是传到下一页面,只需简单的在方法或Action的property上面加入@ParamScope的声明即可。

三、自动较验
在Hibernate生成的Hbm中含有大量的较验信息,像长度,类型,唯一性,约束等等,这些东西是完全可以重用的,实在是没有必要hbm生成一套,然后又在struts的validatio.xml中又写一套,cswish在这方面做了一个集成,自动的转换hbm所包含的数据较验信息到struts的validation实例中。
另外,struts在collection方面的较验支持的不好,cswish也做了一个小小的增强。其实有时在流览javaeye的其它文章时,看到有些人在问这方面的问题,我想我这里的代码也许能给出点帮助:

题外话,引入一个新的validator: com.googlecode.cswish.struts.CollectionValidator,在其validate方法中,取得要较验的collection,遍历每一个value,调用基本的struts validator较验。这个较验类中要传的参数,主要是要较验的字段名,另外就是基本较验的相关信息。
另外为了支持在client端较验,要修改一下form-close-validate的js较验语句,具体可参见:/cswish/src/main/resources/template/xhtml/form-close-validate-ex.ftl
(我这里不是用覆盖的方式,而是新写了一个theme,所以我在调用form时,用的是:<@s.form openTemplate="form-ex" template="form-close-ex" validate="true">),简单处理的话可以在form-close-validate.ftl上直接修改

四、页面模型
从第一点,到第三点,我们可以看到,一个新一通用业务过来。Action不用写了(cswish自动虚拟一个),Service不用写了(内置通用CRUD),数据较验也不用做了。那我们还要做什么呢?那就是页面。

事实上,接下来的这个功能就是让你页面也不必写了。
页面模型,顾名思义,就是以Model为原型,自动转换成相应的界面。当然,Model转成的页面模型还是一些点,那么我们需要一个面(页面风格及整体布局)来实现点面接合,所以跟着出场的就是cswish的HTML模版功能。
HTML做为模版的最大好处是,美工的规美工,开发的规开发,谁也不要改谁的文件。所以,功能改变时,界面再调整,dreamweaver直接上手去调整就是了。(通常的开发情况是,开发更改了美工设计好的界面,在其中插入程序代码,使得后期修改,美工没法直接参与)

cswish内置了一个较简单的通用模版,见:/cswish/src/main/resources/viewTemplate/input.htm,/cswish/src/main/resources/viewTemplate/list.htm,/cswish/src/main/resources/viewTemplate/_searchQuick.htm

五、OO的页面
OO的好处是对修改和扩展,cswish的页面模型在处理之初就将页面化成了点与面,因而在引入<cs.mark><cs.extends><cs.include><cs.call>几个tag后,借着类似于OO的思想,能任意的扩展和重用已有的页面。
具体的例子,可以见/cswish/src/main/resources/viewTemplate下的ftl文件,比方:
generic._search.ftl,generic.input.ftl

六、OO的查询
cswish提供了一个功能,就是查询对象,而不只是HQL。比方person.setName('feng'),那么调用search(person)方法,查得的就是name='feng'的所有person.
这里传的对象可以是任何复杂的对像,比方person下有address,有Collection<Child>等,一对一,一对多等关系。在上面的例子中扩大一点点,person.getChildren()[0].setName('feng2'),那么调用search(person)方法,查得的就是name='feng'并且有child.name='feng2'的person
   发表时间:2008-12-08   最后修改:2008-12-08
七、基于异常业务逻辑较验
要不要用异常表示业务流程,曾经引发过大讨论.其实,在有框架的帮助下,基于异常的流程其实是很高效的.用异常表示流程的好处在于:
1) 省出了那个唯一的函数返回出口
2) 可以直接跳出层层堆栈
缺点是:
1) 不可控的跳转
2) 事务完整性
幸运的是,现在的Spring事务处理也是基于RuntimeException做rollback的,跳转的控制可由struts interceptor在顶层捕获,从而可以识别异常以给出正确的错误提示.cswish在这方面做了几个小处理,一是,分析DB或Hibernate的异常代码转为相应的较验提示, 二是,识别自定义的AppException.

八、抛弃struts tag,只有一个(一种)Tag实现的复杂页面
Struts中的ui tag有很多个, cswish中引入一个叫element的tag,其实这个东西和MS中有一个组件叫"变色龙"的处理比较相似,不同的地方在于,cswish在支持参数动态换text, hidden, select等的基础上,加入根椐页面模版数据, 自动显示为text, hidden, select等类型.此外,cswish也能支持将复杂对象的显示以及对象的嵌套显示. Action本身就可被当成一个对象, 因而在cswish可以用一个element显示整个Action的对象界面
在一些业务单据处理系统中, 常见的一个功能就是自定义页面元素, 在cswish中也是较好扩展了, 主要就是将cswish依椐model自动组装的页面模版数据, 以DB的方式存储或导入即可.

九、基于spring组装带来的任意插拨与替换
这个cswish在设计时所基于的一个组装原则, 在cswish中有不少小的功能块, 每一块有一个或几个对象提供, 比方PageConverter提供页面转换, DFactory提供Acttion动态生成, Routing提供页面调度的应射,等等,这些功能都是以spring的configuratioin文件组织起来的, 每一个类都是可被替换成其它方式的实现.

十、从迷失的分层中回来,重归面向OO的编程方式
最终篇,一到九都只是雾里看花的表面现象,其实质只是为一个目的而来————重回OO. 一到九,左一点右一点,可能是花可能是草,其实这一切杂七杂八功能的主要目的,只是为了业务的OO.
在这里,我用了迷失一词,为什么我称之为迷失,因为写代码到现在这种时代,已经和OO远离.还记得大学中初学C++时,写代码的感觉吗?
回到上面那个HR Manager想聘用一个新人的例子,以OO的思想,我们就会很自然的说,我要写一个HrManager的class,class中有一个方法叫hire,hire的参数目标中有一个person的对象.而在现在的web开发中,简单的问题,忽然很复杂很别扭了,更要命的是,仿佛大家接受了这种别扭为理所当然.我们要写input页面,要写各种配置,要写Action保持person对象,要用Action调用这一HR Manager,要写专门的DAO去save hire的结果.我想,这真是太复杂了.
在一个Web应用中,这些功能是必要的,但代码的形式不应如现在这般.它应用由SSH之类的框架以无入侵的方式提供进来.正如一个大家熟知的原则:虽然这个Object是一个JavaBean,但它其实也是一个EJB.不同行为取决于它运行的不同容器.(好像又回到了最开篇引入星爷的那一句搞笑台词)
在cswish提供的功能中,它围绕的是:虽然这个Object看似是一个JavaBean,但它其实也是一组Action(一个Method对应到一个Action),它其实还是多组页面(输入和输出).
所以, 在引入cswish的SSH结构中, 它对整个Busines Object应是无入侵的,无配置,无Action,无DAO,无页面, Business Object就是那个简简单单的对象(甚至它也是无), 平时,它就是一普通的class, 丢到SSH中, 它就自动成了一个可调用的Web服务.调用的语法可以是:
hrManager.hire.action?person.name=abc&person.sex=male,也可以引入URL Mapping,以形于person_name/abc/person_sex/male/hrManager.hire的形式调用(虽然我个人不认为他们有本质的区别)
这就是我希望的OO编程.也是我写下这个struts plugin的原动力

说些无聊的话。。。
其实写这个plugin,持续了较长时间了,主要平时工作的的事情也多,时不是10点后下班的修修补补,或是周末的敲敲打打,进展不是那么快。

其实还有一些自觉良好的idea,没时间一一验证或实现,深深体会到做时方知难啊。也不知道这plugin有多少没发现的bug,又或是可能自觉不错的东西其实啥都不是。希望多多指点和交流了。。。

多谢各位javaeye的兄弟们捧场,另附一些demo出的效果图。其实这个基于cswish plugin的demo几乎是零代码了。

主要包括用hibernate tools自动生成的model和hbm, 然后就是一个基于appfuse搭建的demo,当然,我只留了他的css, js, layout之类,他的server,action什么的,我全拿掉了
  • 大小: 66.1 KB
  • 大小: 67.7 KB
  • 大小: 331.1 KB
  • 大小: 104.3 KB
  • 大小: 349.9 KB
  • 大小: 130.1 KB
  • 大小: 51.7 KB
2 请登录后投票
   发表时间:2008-12-08  
个人觉得页面模板就算了....以前用struts基本都是用simple theme,最近找到办法,终于把struts tag彻底搞掉了。
0 请登录后投票
   发表时间:2008-12-08  
我们是这样做的,当然没有LZ这么强,但是也不错:
1.使用*/*配置,例如name="*/*/*",这样几乎可以匹配任何action了。
2.Action的method一律返回null,将数据用json-lib转换为json写入response.
3.页面用ajax,比如ext,来渲染数据。

这样好处是:
1.几乎是零配置。
2.真正的数据和表现的分离。

0 请登录后投票
   发表时间:2008-12-08  
支持,脑袋不灵活现在还看不出有什么好处,性能上有没有提升
0 请登录后投票
   发表时间:2008-12-08  
我觉得Struts需要一个适合写wizard的plug-in,这样可以把spring web-flow丢垃圾桶里去。
0 请登录后投票
   发表时间:2008-12-08  
好像很强大,先看一下
0 请登录后投票
   发表时间:2008-12-08  
没看出来有什么全新的东西。OO的查询,和 Hibernate 的 Example 查询的思想是一致的,Hibernate 论坛上也有一个 Enhanced Example 的帖子(Javaeye 上也有相关的帖子),所以看不出有什么新意。而且 Example 查询限制比较多,只适合个别情况,而且查询条件不够清晰。

由于 Struts 本身的限制,再怎么在上面搭东西也不会有大变化。LZ 的项目可能比较适合公司内部使用,对于那些在SSH上有很大投入的公司比较适用。但技术是发展的,优秀的轮子已经非常多了。
0 请登录后投票
   发表时间:2008-12-08  
学习ing
0 请登录后投票
   发表时间:2008-12-08  
先观察一下
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics