概述
这并不是一个具体的项目,而是在开发具体项目过程中,逐渐系统归纳并渐成体系的一套快速的开发框架。其充分应用了Spring的依赖倒转特性,结合前端显示的开源框架,可以快速的开发对数据库表的增删改查功能,使程序员从繁琐的SQL和大量重复的代码中解脱出来,专注于业务逻辑的实现。这个项目也是实现OO设计的一个典范。
SSH指Hibernbert+Spring+Webwork。这里用Webwork替换了Struts,其实对于展示层来说,用何种开源框架都是类似的。且Webwork比Struts更加规范,Webwork实际上也是Struts2的一种平滑过渡。所以这里依然命名为基于SSH的快速开发框架。
这个框架的初衷源于做SSH项目时的两个问题:
作为一个重要的功能,对数据库的查询,如何能做到快速和标准化的实现?我们知道程序员需要善于写SQL来进行查询,但是在Java的持久化层,大量复杂的SQL调用是否合理,是值得商榷的。Hibernate作为最广泛使用的开源框架,其HQL足够强大,而我个人认为,Criteria的设计更加契合Java的面向对象开发理念。如果任由程序员使用HQL构造系统,则持久化层要写很多重复代码,且无法做到很好的对象化,容易失控。
中间层(业务层)的作用体现在哪里?从纯JSP的最初开发阶段,我们习惯于将业务逻辑写在page上,极其头痛的维护迫使人们考虑分离显示层,利用Action(Struts和Webwork等)的方式将逻辑挪到Java的层面上,使得page上只有那些标签。而我们知道在Action中可以直接构造查询条件,并调用持久层以获取结果,那么如何体现以Spring为代表的中间层的作用呢?
出于解决这两个问题的目标,这个框架实现了如下的特点:
- 封装查询条件。为了解决第一个问题,该框架在持久化层做了一些对象化的工作。其前提是尽量使用Criteria的设计。所谓封装,即将每一个查询条件设计为一个对象,其中包含了查询条件的值,以及需要的关系(大于,小于,为空等等);
- 充分利用Spring的依赖倒转。为了解决第二个问题,在Action的方法体中,通过调用各种Manager类来实现业务逻辑,而Manager全部使用Spring的依赖倒转在运行时进行注入,由容器控制。这样做的好处是真正实现了三个层次的分离,各司其职,同时便于扩展。该框架设计的比较早,对于Spring的依赖注入还使用的是配置文件的方式,工作量也是比较大的。最近研究JSF的实现,发现使用Annotation是一个潮流,相信Spring的新版本也会在这方面有些改进;
- 模板方法模式的应用。在显示层和业务层,核心处都使用了模板方法模式,即由抽象类实现算法步骤,需要由具体类具体行为的地方,用抽象方法代替。这样具体类就简化为实现这些具体行为(抽象方法),具体类看起来更加像一个”对象“,而非一种”过程“;
- 符合OO设计的原则。基于接口编程,而非基于具体实现编程,是OO设计的基本原则。该框架的设计完全符合这些OO的原则。在三个层次上,接口,抽象类,具体类形成了完美的交互关系,相互支撑,最大限度的减少了冗余代码,也便于扩展。
由于有了以上的特点,利用这个框架开发比较简单的SSH应用,有如下的优点:
- 标准化。该框架很好的封装了持久化层,规范了显示层和业务层。开发工作趋于标准化,程序员只需要集中在业务逻辑的实现,不需要关心底层的实现,也不想写很多重复的代码;
- 快速开发。由于有了标准化的优点,对于新功能的加入,可以做到快速的实现。特别是针对那些简单的功能,甚至不需要写代码,只需要在Spring的配置文件中添加配置语句即可;
- 易于扩展。优秀的OO设计目标就是易于扩展,不论是持久化层更新框架,还是对原有业务逻辑进行升级,在该框架的支持下,都可以快速的实现。
持久化层
持久化层对条件的封装,是这个框架的核心变化,所以最先进行描述。我们考虑这样一个场景,对某个表进行综合查询,并把结果返回给用户。这个表包含了各种类型的字段,行数也很多,我们的查询也许会比较复杂,程序员会很自然的写出"select ... from XXX where ... and ... order by ..."。针对这么一个基本需求,我们会首先考虑对where子句的内容进行改造和封装。这就是PojoValue接口,PojoValueSupport抽象类以及具体的PojoValue类的工作。
这种封装的机制是这样的:任何一个where条件都有一个字段名称(variable),点值或极值(current, min, max)以及他们之间的关系(大于,小于,等于,为空等等)。那么一个具体的PojoValue类会存储这些信息,相当于把一个where条件进行了对象化。其中,PV接口可以返回这个条件封装类的查询条件(HQL形式或Criterion形式),而PVS抽象类包含了字段名称,和一些boolean值的关系,因为这两个都是不随具体类而变化的。继承PVS的具体类,则包含了不同对象的点值和极值,例如IntegerPojoValue包含了Integer类型的current, max and min;DatePojoValue包含了Date类型的current, max and min。为了方便构造,我们会将这些具体的PojoValue类设计成单例模式,后面会看到调用的实例。
其次,考虑对整条查询语句进行对象化,我们就得到PojoWrapper接口和PojoWrapperSupport抽象类。前者定义了获取整个查询语句的方法(依然是HQL形式或DetachedCriteria形式),后者包含了必要的一些元素,例如表名(tableName),排序关系(orders)和映射字段(projections)。对于实现该抽象类的具体类,应该根据要实现的查询条件,置入一定的PojoValue具体类。例如一个对整型的判断,就需要一个IntegerPojoValue,一个对日期型的判断,就需要一个DatePojoValue。
至此,整个查询语句就对象化完成了,这个对象是一个实现了PojoWrapper的具体类,其功能就是返回DetachedCriteria对象或者代表HQL的字符串对象。最后,我们需要DAO类来获取真正的数据,这是DaoSupportWrapper接口的工作。它通过一个方法接收DetachedCriteria对象(或String对象,代表HQL),返回结果集(List)。至于具体的实现,我们使用Hibernate来做,所以自然的以一个HibernateDaoSupportWrapper来定义,并且由于该类继承了HibernateDaoSupport,后者为Spring提供的对Hibernate的支持,所以具体实现非常的简单。
业务层
持久化层结束于Dao的设计,对其调用,根据业界的常规做法,是业务层提供的Manager接口的工作。ManagerSupportWrapper接口是极其重要的,它连接了持久化层。一方面,由于其实现类会内置一个对应的DAO类,所以连接了持久化层,并获得查询结果;另一方面,它通过createWrapper这个重要的方法,将显示层接收到的用户输入,以FormBean的方式整体输入,并在这里转化为PojoWrapper(持久化层对查询条件的封装)。
WrapperBehavior是一个抽象类,部分实现了ManagerSupportWrapper接口。它通过内置的Dao去获取查询结果,而createWrapper方法则使用模板方法模式给出最基本的算法步骤,只在添加真正的PojoValue和其它条件的地方,留给具体类实现。
显示层
显示层最重要的是各种Action的设计。这里两个问题,一个是各种Action的继承或实现关系,二是对Manager类的注入和使用。就Webwork来说,所有的Action应该都继承自ActionSupport类,我们定义了一个Action类,并继承自Webwork的系统类,以实现和页面的通信。此外Action类还支持异常出现后的处理,特别是发送到页面的提示信息,这部分基本也对象化了。ModelAction是Action的一级子类,其实现Webwork的ModelDriven接口,以接受页面信息。以QueryAction为典型的二级子类,过载execute方法,实现各自的业务逻辑和转向功能。
第二个问题是通过Spring来实现的。QueryAction中会有一个ManagerSupportWrapper对象,Spring会在运行期根据配置注入不同的具体Manager。而QueryAction会在其execute方法中,调用Manager的createWrapper,将FormBean对象转换成Wrapper对象,并再次使用Manager进行实际结果的调用。
显示层还会处理结果显示,异常提示等和页面更紧密的通用功能,对这部分也尽量使用OO的思想予以对象化的设计。由于显示层和Webwork联系较为紧密,这里就不再赘述了。