人人都爱Spring加Hibernate。
但Spring MVC+hibernate的Sample如Appfuse的代码却算不得最简洁优美好读,如果在自己的项目中继续发挥我们最擅长的依样画葫芦大法,美好愿望未必会实现。
所以,Pramatic精神不灭。这个系列就是探寻最适合自己的Spring+Hibernate模式。
I-配置文件简化 我厌倦一切配置文件繁重的框架。
最好的情况是,框架提供极端灵活复杂的配置方式,但只在你需要的时候。
Spring提供了三种可能来简化XML。随着国内用户水平的提高,这些基本的简化技巧大家都已掌握。
大家可以直接看第3,第4点--Spring 1.2, Spring 2.0的后继改进。
1.1.autowire="byName" /"byType"
假设Controller有一个属性名为customerDAO,Spring就会在配置文件里查找有没有名字为CustomerDAO的bean, 自动为Controller注入。
如果bean有两个属性,一个想默认注入,一个想自定义,只要设定了autowire,然后显式的声明那个想自定义的,就可以达到要求。这就应了需求,在需要特别配置的时候就提供配置,否则给我一个默认注入。
还有一个更懒的地方,在最最根部的<beans>节点写一句default-autovwrie="byName",可以让文件里的所有bean 都默认autowrie。
不过Rod认为开发期可以这样,但Production Server上不应该使用Autowire。而我觉得那些自定义一次的地方比如TranscationManager应该详细定义,而Dao,Controller这种大量重复定义的bean就可以偷点懒了。
1.2.<bean>节点之间抽象公共定义和 Inner Bean
这太方便懒人了,想不到两个独立的XML节点都可以玩继承和派生,子节点拥有父节点的全部属性。
最好用的地方就是那个Transtion Proxy的定义。先定义一个又长又冗的父类,然后用子类去继承它。
另外,还有一个Inner Bean的机制,可以把DAO写成Proxy的内部类。为什么要写成内部类?为了让Proxy冒名顶替它去让Controller Autowire。(详见后面的示例)
1.3. 宽松的配置, To XML or Not to XML
据说Spring比Struts的配置宽松了很多,这就给人把东西从配置文件中撤回原码中的机会。
不赞成什么都往配置文件里晒,造成了Rich Information的配置文件,修改或者查看的时候,要同时打开配置文件和原码才能清楚一切。
而我希望配置文件就集中做一些整体的配置,还有框架必须的、无需管理的冗余代码。而一些细节的变化不大的配置和逻辑,就尽量别往里塞了。因此,Success/Fail View 的配置,不建议放在里面。
2.简化后的配置文件
1.Controller只剩下一句
<bean name="customerController" class="org.springside.bookstore.web.CustomerController" autowire="byName"/>
2.DAO也只剩一句
<bean id="customerDAO" class="org.springside.bookstore.dao.CustomerDao"/>
3.Service类只剩下5行
<bean id="customerManager" parent="baseTxService">
<property name="target">
<bean class="org.springside.bookstore.service.CustomerManager"/>
</property>
</bean>
3.Spring 1.2后xml语法简化
最主要的简化是把属性值和引用bean从子节点变回了属性值,对不喜欢autowire的兄弟比较有用。
当然,如果value要CDATA的时候还是要用子节点。另外,list的值可以用空格隔开也比较实用。
<property name="myFriendList">
<list>
<value>gigix</value>
<value>wuyu</value>
</list>
</property>
简化为
<property name="myFriendList" value="gigix wuyu"/>
4.Spring 2.0来了 如果没什么外力刺激,spring xml 可能就这样不会变了。但现在xml成了过街老鼠,被ror的默认配置和JDK5的annotation逼得不行,当然就要继续求变。
比如有好事者认为,节点名必须以bean打头,附加一个属性id来表示bean名;属性值必须搞一个property子节点,子节点上有个属性name来表示属性名,是给机器看的很不直观的东西。
<bean id="customerDAO" class="org.springside...CustomerDAO">
<property name="maxCount" value="10">
</bean>
给人看的东西应该就写成
<customerDAO class="org.springside....CustomerDAO" maxCount="10"/>
Spring 2.0正用schema实现类似的语法,具体请看它的JPetStore sample。
5.使用Spring自带的DTD使编辑器Smart.
如果没有用Eclipse的Spring插件,那至少也要使用spring自带的dtd使XML编辑器smart一些,能够自动为你生成属性,判断节点/属性名称有没有拼错等。
6.还有更变态的简化配置方法
比如autoproxy,不过我觉得更简化就不可控了,所以没有采用。
因为Spring自带的sample离我们的实际项目很远,所以官方一点的model层模式展现就靠Appfuse了。
但Appfuse的model层总共有一个DAO接口、一个DAOImpl类、一个Service接口、一个ServiceImpl类、一个DataObject.....大概只有受惯了虐待的人才会欣然接受吧。
另外,Domain-Driven逢初一、十五也会被拿出来讨论一遍。
其实无论什么模式,都不过是一种人为的划分、抽象和封装。只要在团队里理解一致,自我感觉优雅就行了。
我的建议是,一开始DO和Manager一生一旦包演全场,DO作为纯数据载体,而Manager类放置商业方法,用getHibernateTemplate()直接访问数据库,不强制基于接口编程。当某天系统复杂到你直觉上需要将DAO层和Service层分开时,再分开就好了。
1.DataObject类
好听点也可以叫Domain Object。Domain Driven Development虽然诱人,但因为Java下的ORM框架都是基于Data Mapper模式的,没有Ruby On Rails中那种Active Recorder的模式。所以,还是压下了这个欲望,Data Object纯粹作一个数据载体,而把数据库访问与商业逻辑操作统一放到Manager类中。
2.Manager类
我的Manager类是Appfuse中DAO类与Service类的结合体,因为:
2.1 不想使用纯DAO
以往的DAO是为了透明不同数据库间的差异,而现在Hibernate已经做的很好。所以目前纯DAO的更大作用是为了将来可以切换到别的ORM方案比如iBatis,但一个Pragmaic的程序员显然不会无聊到为了这个机会不大的理由,现在就去做一个纯DAO层,项目又不是Appfuse那样为了demo各种ORM方案而存在。
2.2 也不使用纯的薄Service层
在JPetStore里有一个很薄的Service层,Fascade了一堆DAO类,把这些DAO类的所有方法都僵硬的重复了一遍。而我认为Fascade的意义在二:
一是Controller调用Manager甲的时候,总会伴随着调用Manager乙的某些方法。使用Fascade可以避免Controller零散的调用一堆Manager类。
二是一个商业过程里可能需要同时调用DAO甲乙丙丁的方法。
这些时候,Fascade都是合理的。但我讨厌类膨胀,所以我宁愿在甲乙丙丁中挑一个来充当Fascade的角色。有耦合的问题吗?对一个不是死搬书的Designer来说,组件边界之内的类之间的耦合并不是耦合。
3.去除不必要的基于接口编程
众所周知,Spring是提倡基于接口编程的。
但有些Manager类,比如SaleOrderManager ,只有5%的机会再有另一个Impl实现。95%时间里这两兄弟站一起,就像C++里的.h和.cpp,徒增维护的繁琐(经常要同步两个文件的函数声明),和代码浏览跳转时的不便(比如从Controler类跟踪到Service类时,只能跳转到接口类的相应函数,还要再按一次复杂的热键才跳转到实现类)
连Martin Flower都说,强制每个类都分离接口和实现是过犹不及。只在有多个独立实现,或者需要消除对实现类的依赖时,才需要分离接口。
3.1 DAO被强制用接口的原因
Spring IOC本身是不会强制基于接口的,但DAO类一般要使用Spring的声明式事务机制,而声明式的事务机制是使用Spring AOP来实现的。Spring AOP的实现机制包括动态代理和Cgilib2,其中Spring AOP默认使用的Java动态代理是必须基于接口,所以就要求基于接口了。
3.2 解决方法
那就让Spring AOP改用CGLib2,生成目标类的子类吧,我们只要指定使用声明式事务的FactoryBean使用CGLib的方式来实现AOP,就可以不基于接口编程了。
指定的方式为设置proxyTargetClass为true。如下:
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
id="baseService" abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="proxyTargetClass" value="true"/>
</bean>
又因为这些Service Bean都是单例,效率应该不受影响。
4.总结
对比Appfuse里面的5个类,我的Model层里只有VO作为纯数据载体,Manager类放商业方法。有人说这样太简单了,但一个应用,要划成几个JSP,一个Controller,一个Manager,一个VO,对我来说已经足够复杂,再要往上架墙叠屋,恕不奉陪,起码在我的项目范围里不需要。(但有很多项目是需要的,神佑世人)
后记:迫于世人的压力,SpringSide还是把DAO和Service层分开了,但依然坚持不搞那么多接口。
Struts与Webwork的扇子请跳过本篇。
MVC不就是把M、V、C分开么?至唯物朴素的做法是两个JSP一个负责View,一个负责Controller,再加一个负责Model的Java Bean,已经可以工作得很好,那时候一切都很简单。
而现在为了一些不是本质的功能,冒出这么多非标准的Web框架,实在让人一阵郁闷。像Ruby On Rails那样简捷开发,可用可不用,而且没有太多的限制需要学习的,比如Webwork这型还可以考虑。但像Struts那样越用框架越麻烦,或者像Tapestry那样有严重自闭倾向,额上凿着"高手专用玩具"的,用在团队里就是不负责任的行为了。
so,我的MVC方案是使用Spring MVC的Controller接口,写最普通的JavaBean作为Controller,本质就和当年拿JSP作Controller差不多,但拥有了Spring IOC的特性。
之所以用这么消极的选择标准,是因为觉得这一代MVC框架离重回RAD时代的标准还很远,注定了只是一段短暂的,过渡的技术,不值得投资太多精力和团队学习成本。
1. 原理 Spring MVC按植物分类学属于Martin Flower〈企业应用模式〉里的静态配置型Front Controler,使用DispatchServlet截获所有*.do的请求,按照xml文件的配置,调用对应的Command对象的handleRequest(request,response)函数,同时进行依赖对象的注入。
我们的Controller层,就是实现handleRequest(request,response)函数的普通JavaBean。
2. 优势
Spring MVC与struts相比的优势:
一是它的Controller有着从松到紧的类层次结构,用户可以选择实现只有一个HandleRequest()函数的接口,也可以使用它有很多回调函数的SimpleFormController类。
二是不需要Form Bean,也不需要Tapestry那所谓面向对象的页面对象,对于深怕类膨胀,改一个东西要动N个地方的人最适合不过。
三是不需要强XML配置文件,宣告式编程是好的,但如果强制成框架,什么都要在xml里面宣告,写的时候繁琐,看的时候也要代码配置两边看才能明白就比较麻烦了。
那Webwork呢?没有实战过,不过因为对MVC框架所求就不多,单用Spring MVC的Controller已经可以满足需求,就不多搞一套Webwork来给团队设坎,还有给日后维护,spring,ww2之间的版本升级添麻烦了。真有什么需要添加的,Spring MVC源代码量很少,很容易掌控和扩展。
3.化简
3.1. 直接implement Controller,实现handleRequest()函数
首先,simple form controller非我所好,一点都不simple。所以有时我会直接implement Controller接口。这个接口的唯一函数是供Front Controller调用的handleRequest(request,response)。
如果需要application对象,比如想用application.getRealPath()时,就要extends webApplicationObjectSupport。
3.2.每个Controler负责一组相关的action
我是坚决支持一个Controler负责多个action的,一个Controler一个action就像一个function一个类一样无聊。所以我用最传统的方式,用URL参数如msg="insert"把一组相关action交给一个Controler控制。ROR与制作中的Groovy On Rails都是这种模式,Spring也有MultiActionController支持。
以上三者都是把URL参数直接反射为Controller的函数,而
Stripes的设计可用annotation标注url action到响应函数的映射。
我的取舍很简单,反正Spring没有任何强制,我只在可能需要不重新编译而改变某些东西的时候,才把东西放在xml里动态注入。jsp路径之类的就统统收回到controller里面定义.
3.4.Data Binder
Data Binder是Controller的必有环节,对于Spring提供的DataBinder,照理完全可用,唯一不爽是对象如果有内嵌对象,如订单对象里面包含了Customer对象,Spring需要你先自行创建了Customer对象并把它赋给了Order对象,才可能实现order.customer.customer_no这样的绑定。我偷懒,又拿Jakarta BeanUtils出来自己做了一个Binder。
3.5.提取基类
最后还是忍不住提取了一个基类,负责MultiAction和其他一些简便的方法。Sprnig的MultiActionController做得太死,规定所有函数的第1,2个参数必须是request和response,不懂动态的,温柔的进行参数注入。
经过化简再化简,已经是很简单一个Java Bean ,任谁都可以轻松上手,即使某年某月技术的大潮把现在所有MVC框架都淹没了,也不至于没人识得维护。
人生像个舞台,请良家少女离开。
同样的,Freemarker和Velocity爱好者请跳过本篇。与弃用webwork而单用Spring MVC Controller接口的理由一样,
Freemarker本来是一样好东西,还跨界支持jsp 的taglib,而且得到了WebWork的全力支持,但为了它的非标准化,用户数量与IDE的缺乏,在View层我们还是使用了
保守但人人会用,IDE友好的JSP2.0 配合JSTL。
对于B/S结构的企业应用软件来说,基本的页面不外两种,一种是填Form的,一种是DataGrid 数据列表管理的,再配合一些css, js, ajax的效果,就是View层要关注的东西了。
1. JSP 2.0的EL代替<c:out>JSP2.0可以直接把EL写在html部分,而不必动用<c:out>节点后,老实说,JSP2.0+JSTL达到的页面效果,已不比Velocity相差多少了。
<p>{goods.name}</p>
代替
<p><c:out value="{goods.name}"/></p>
(除了EL里面不能调用goods的函数,sun那帮老顽固始终坚持JSTL只能用于数据显示,不能进行数据操作,所以不能调用bean的get/set外的方法)
2. 最懒的form 数据绑定
Spring少得可怜的几个tag基本上是鸡肋,完全可以不要。 而Spring开发中的那些Simple Form tag又还没有发布。Spring的Tag主要用来把VO的值绑到input框上。但是,和Struts一样,需要逐个Input框绑定,而且语法极度冗长,遇到select框还要自己进行处理.....典型的Spring Sample页面让人一阵头晕.
而jodd的form tag给了我们懒人一个懒得多的方法,只要在<form>两头用<jodd:form bean="myVO"></jodd:form>包住,里面的所有input框,select框,checkBox...统统自动被绑定了,这么简单的事情,真不明白struts,spring为什么不用,为了不必要的灵活性么?
<form>
<jodd:form bean="human">
<input type="text" name="name">
<input type="radiobox" name="sex" value="man">
<select name="age">
<option value="20">20</option>
<option value="30">30</option>
</select>
</jodd:form>
</form>
不过,jodd有个致命弱点是不能绑定内嵌对象的值。比如Order(订单)对象里有个Customer(顾客)对象,jodd就不能像 struts,spring一样用如下语法绑定:
<input name="customer.customerNo"> 这是因为它的beanUtils比Jakata Common弱,用了一个错误的思路的缘故。 动用beanUtils修改一下就可以了,修改后的源码可以在这里下载。
3. DataGrid数据列表
DisplayTag和ValueList都属于这种形式的Tag Library。但最近出现的Extreme Table是真正的killer,他本身功能强大不说,而且从一开始就想着如何让别人进行扩展重载,比如Extend Attributes机制就是DisplayTag这样的让千人一面者不会预留。
4.css, java script, ajax
天下纷扰,没有什么特别想讲想推荐的,爱谁谁吧。Buffalo, DWR, Scriptaculous, Prototype, AjaxTags, AjaxAnywhere, Rico, Dojo, JSON-RPC,看着名字就头痛。