简化Spring

     人人都爱Spring加Hibernate。
    但Spring MVC+hibernate的Sample如Appfuse的代码却远算不得最简洁优美好读,如果在自己的项目中继续发挥我们最擅长的依样画葫芦大法,美好愿望未必会实现。
     所以,Pramatic精神不灭。这个系列就是探寻最适合自己的Spring+Hibernate模式。

 I-配置文件简化

     我厌倦一切配置文件繁重的框架。
     最好的情况是,框架提供极端灵活复杂的配置方式,但只在你需要的时候
 
     Spring提供了三种可能来简化XML。随着国内用户水平的提高,这些基本的简化技巧大家都已掌握,从Spring 1.2开始又有了小小的改进,<Spring XML: way simpler since 1.2>作了集大成的归纳

1.1.autowire="byName" /"byType"

     假设Controller有一个属性名为customerDAO,Spring就会在配置文件里查找有没有名字为CustomerDAO的bean, 自动为Controller注入。
      如果bean有两个属性,一个想默认注入,一个想自定义,只要设定了autowire,然后显式的声明那个想自定义的,就可以达到要求。这就应了需求,在需要特别配置的时候就提供配置,否则给我一个默认注入可以了。

     还有一个更懒的地方,在最最根部的<beans>节点写一句default-autovwrie="byName",可以让所有bean 都默认autowrie。
    不过Rod认为开发期可以这样,但Production Server上不应该使用Autowire,大家自己来决定了。

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 id="saleOrderController" class="com.itorgan.web.SaleOrderController" />

2.DAO只剩下5行

<bean id="saleOrderDAO" parent="baseDAOService" >
 
<property name="target">
   
<bean class="com.itorgan.lherp.dao.impl.SaleOrderDAOImpl" /> 
 
</property>
</bean>
3.Spring 1.2后xml语法简化

 
最主要的简化是把属性值和引用bean从子节点变回了属性值,对不喜欢autowire的兄弟比较有用。
 当然,如果value要CDATA的时候还是要用子节点。
1.属性值

  <property name="foo">
    
<value>fooValue</value>
  </property>
  简化为
  <property name="foo" value="fooValue"/>

2.引用 bean
<property name="foo">
  
<ref bean="fooBean">
</property>
简化为
<property name="foo" value="fooBean"/>


3. list如果只有一个值时,可以直接用value表示
 
<property name="myFriendList">
 
<list>
    
<value>wuyu</value>
 
</list>
</property>
简化为
<property name="myFriendList" value="wuyu"/>

   
  因为只有一个值时,List就退化为Object类型嘛,多体贴。

4.使用Spring自带的DTD使编辑器Smart.

    如果没有用Eclipse的Spring插件,那至少也要使用spring自带的dtd使XML编辑器smart一些,能够自动为你生成属性,判断节点/属性名称有没有拼错等。

5.还有更变态的简化配置方法
    比如autoproxy,不过我觉得更简化就不可控了,所以没有采用。

II-Model层简化

    因为Spring自带的sample离我们的实际项目很远,所以官方一点的model层模式展现就靠Appfuse了。
    但Appfuse的model层总共有一个DAO接口、一个DAOImpl类、一个Service接口、一个ServiceImpl类、一个DataObject.....大概只有受惯了虐待的人才会欣然接受吧。
    另外,Domain-Driven逢初一、十五也会被拿出来讨论一遍。
    其实无论什么模式,都不过是一种人为的划分、抽象和封装。只要在团队里理解一致,自我感觉优雅就行了。在我的Model层里,DO和Manager一生一旦包演全场,VO作为纯数据载体,而Manager类放置商业方法,用getHibernateTemplate()直接访问数据库,不强制基于接口编程......

    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类。
    二是一个商业过程里可能需要同时调用Manager甲乙丙丁的方法。 

      这些时候,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,对我来说已经足够复杂,再要往上架墙叠屋,恕不奉陪,起码在我的项目范围里不需要。 (但有很多项目是需要的,神佑世人)

III-Controller层简化

    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到响应函数的映射。
       3.3.xml宣告式编程的取舍 
    我的取舍很简单,反正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框架都淹没了,也不至于没人识得维护。

IV-View层简化

    人生像个舞台,请良家少女离开。
    同样的,Freemarker和Velocity爱好者请跳过本篇。与弃用webwork而单用Spring MVC Controller接口的理由一样,Freemarker本来是一样好东西,还跨界支持jsp 的taglib,但为了它的非标准化,用户数量与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, javascript, ajax
天下纷扰,没有什么特别想讲想推荐的,爱谁谁吧。

阅读更多
换一批

没有更多推荐了,返回首页