简单,易于Mock,仅依赖Spring的Domain Model
来源:互联网 发布:cs起源武器数据 编辑:程序博客网 时间:2024/04/30 08:35
声明:该贴并不讨论Domain Model对于企业应用是否有意义,JE已经讨论过很多了,有很多优秀的帖供参考,这里仅谈实现。
马丁大叔在《企业应用架构模式中》提出Domain Model(领域模型,领域对象,Domain Object)的概念后,我们发现这才是开发企业应用更OO的模式,以前的Transaction Script简直太土了。。。按照领域模型组织起来的面向对象语言代码,简直美极了,而ROR中的ActiveRecord就是一种Domain Model的实现,其威力大家也都见识过了。但是因为Java语言的特性的原因(例如静态类型),一直没有一种好的,优美的,简单的方式去实现,主要的难题就是领域对象和数据库逻辑的依赖关系(例如和Dao的依赖),大家关于这方面的尝试从来就没有断过,目前有以下几种主流的实现方式:
1 放弃IOC,在DomainObject里直接去获得Dao的Instance,例如在Domain Object里拿到Spring上下文,直接get所需要的Bean,这样的优势就是简单,缺点就是DomainObject与Dao的实现以及上下文的耦合,领域逻辑的执行依赖数据库,无法单元测试。有的观点认为利用内存数据库,这样比真正的访问数据库要快,要方便,认为mock dao没有意义,这个观点还持保留意见 :)
2 利用一些框架的特性去实现Dao或者数据库访问能力的注入,例如利用Hibernate Interceptor,在Domain Object持久化的时候注入,这个方式有个明显的缺点就是Domain Object的生命周期依赖特定环境,也就是说这个Domain Object必须是由Hibernate构造的,其才会被注入Dao。这点其实很郁闷,我们以前有个项目就是用这种方式,自己写了Hibernate Interceptor去注入Domain Object,初看很爽很EASY,但是表现层这边,DWR将用户提交的表单数据转换成DomainObject后,那个Dao当然就是NULL了,于是自己又写了个DWR的 Convertor,去实现注入,而这个Domain Object真的是很Rich,随后又要通过web serivce传输,又要在webservice框架里加拦截器实现注入,真的是很郁闷。
3 将Dao通过方法参数传入,优点就是简单,POJOS In Action里就提到了这种方式,缺点就是会影响方法以及接口的设计,经常会出现一个方法会有6个参数之多,而5个都是Dao,真是很丑陋。
4 利用AspectJ等工具实现编译期注入,Spring 2.0刚出的时候,引入了AspectJ,其Reference中就提到了这个方式,我也尝试过,非常的麻烦,而且性能很慢,没记错的话当时大概是06年10月份,用的JDK5,现在不知道有没有改善,我当时还在JE发过新手帖询问过,某大牛说是BEA的虚拟机对其有优化,我也没条件尝试,而且编译期的手脚动太多了,感觉不是很好。
上述几种方式都有明显的缺点,我们追求的好的领域模型的实现就是,领域模型不依赖实现,例如可以注入,易于单元测试,而且其生命周期不依赖特定的环境,还要够简单!难道真是Robbin所说的JAVA不适合Rich Domain Model吗?不见得:
首先,不依赖实现,可以注入,那么要在领域模型中提供接口dao的set方法,允许被注入,这个很明确。
其次,领域模型的生命周期不依赖环境, 不依赖hibernate的构造,不被spring管理等。一般的情况,dao都是无状态的,单例的,那么把领域模型中的依赖的dao设置为static,static是class级别的,和具体的instance没关系,只要在应用初始化的时候给所有的领域模型的class注入一次,整个应用运行的时间周期,领域模型随意怎么new,在哪new,new出来的Domain Object的都可以访问一开始被注入的static的dao,实现了领域模型的生命周期不依赖环境,这样也易于测试,易于MOCK,和目前传统编程方式差别很小,仅仅把dao改为static的。
但是spring可以实现对一个class的静态peroperty注入吗?答案是不能(经bottom同学修正,spring是可以静态property注入,但是还是要在所有bean初始化之前注入才行,所以还是要扩展,我在3楼已经回复说明),但是spring是个优美扩展性强的框架,我们只要稍微扩展一下,就可以达到我们的目的。
就拿一个典型的web应用为例,普遍的方式的是在web.xml配置spring的context loader listener和spring的配置文件,这个listener会在应用启动时,创建并初始化spring的上下文,我们需要在所有bean初始化之前注入domain class,那么就在这里找切入点,通过spring的源代码可以发现类AbstractApplicationContext的refresh方法内的finishBeanFactoryInitialization有些重要的东东。
- /**
- * Finish the initialization of this context's bean factory,
- * initializing all remaining singleton beans.
- */
- protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
- // 省略
- // Instantiate all remaining (non-lazy-init) singletons.
- // 这里,会初始化所有的单例
- beanFactory.preInstantiateSingletons();
- }
/** * Finish the initialization of this context's bean factory, * initializing all remaining singleton beans.*/protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // 省略 // Instantiate all remaining (non-lazy-init) singletons. // 这里,会初始化所有的单例 beanFactory.preInstantiateSingletons();}
通过观察代码发现,初始化单例其实就是将Spring配置文件中定义的所有不是lazy init的单例初始化了,对一个Bean的初始化就是调用getBean来完成,Spring在调用getBean的时候,会判断如果已经初始化了就直接返回给你Bean的引用,否则就初始化这个Bean再返回给你,很典型的实现LAZY的方式。
我们只需要在这个初始化方法之前,主动去扫描所有的领域模型class,并且知道他们需要注入的Dao,直接getBean获得dao的实例,并且注入进Domain Model的class就完成了,那么我们只需要自己实现一个ConfigurableListableBeanFactory接口,也就是继承DefaultListableBeanFactory(其是ConfigurableListableBeanFactory接口的默认实现), 并且在preInstantiateSingletons方法的时候先注入领域模型,巨简单
- private class StaticPropertyInjectSupportListableBeanFactory extends DefaultListableBeanFactory {
- @Override//覆盖父类的方法
- public void preInstantiateSingletons() throws BeansException {
- //先注入所有的领域模型的class
- injectStaticPropertyForBasePackage();
- //再调用父类的该方法去初始化所有的非LAZY单例
- super.preInstantiateSingletons();
- }
- }
private class StaticPropertyInjectSupportListableBeanFactory extends DefaultListableBeanFactory { @Override//覆盖父类的方法 public void preInstantiateSingletons() throws BeansException { //先注入所有的领域模型的class injectStaticPropertyForBasePackage(); //再调用父类的该方法去初始化所有的非LAZY单例 super.preInstantiateSingletons(); }}
继续观察代码,finishBeanFactoryInitialization的方法的参数ConfigurableListableBeanFactory,是通过调用的createBeanFactory方法获得的,默认的实现在AbstractRefreshableApplicationContext中有这样一个方法,去创建DefaultListableBeanFactory,而这个方法是protected的,摆明着让我们去复盖扩展其功能,这也是spring易于扩展的体现,我们只要实现一个自己的ApplicaitonContext,覆盖createBeanFactory方法创建我们刚才自己实现的StaticPropertyInjectSupportListableBeanFactory就OK了,代码也巨简单
- public class StaticPropertyInjectSupportXmlWebApplicationContext extends XmlWebApplicationContext {
- @Override
- protected DefaultListableBeanFactory createBeanFactory() {
- return new StaticPropertyInjectSupportListableBeanFactory(getInternalParentBeanFactory());
- }
- }
public class StaticPropertyInjectSupportXmlWebApplicationContext extends XmlWebApplicationContext { @Override protected DefaultListableBeanFactory createBeanFactory() { return new StaticPropertyInjectSupportListableBeanFactory(getInternalParentBeanFactory()); }}
实现了自己的ApplicationContext,怎么让Spring启动的时候初始化你的上下文呢?当然可以了,spring提供了扩展的后门,只要在web.xml配置context parameter "contextClass"告诉spring,他就会用你自己的ApplicationContext去初始化了,不配置该参数默认的ApplicationContext实现是XmlWebApplicationContext。
关于自己实现静态property的注入就很简单了,在context parameter里加入个参数configurableBeanBasePackage,获得所有要注入的领域模型的父包,就可以了,如下
- <!--加入这两个参数,其余不变 -->
- <!--告诉spring用我们自己实现的支持静态bean注入的context -->
- <context-param>
- <param-name>contextClass</param-name>
- <param-value>com.norther.sps.StaticPropertyInjectSupportXmlWebApplicationContext</param-value>
- </context-param>
- <!--注入com.norther包下(包含子包)所有的领域模型的class-->
- <context-param>
- <param-name>configurableBeanBasePackage</param-name>
- <param-value>com.norther</param-value>
- </context-param>
<!--加入这两个参数,其余不变 --><!--告诉spring用我们自己实现的支持静态bean注入的context --><context-param> <param-name>contextClass</param-name><param-value>com.norther.sps.StaticPropertyInjectSupportXmlWebApplicationContext</param-value></context-param><!--注入com.norther包下(包含子包)所有的领域模型的class--><context-param> <param-name>configurableBeanBasePackage</param-name><param-value>com.norther</param-value></context-param>
这样就OK了,有人会想到这样还是有一点点点点麻烦,用PostProcessorBean或者Spring的容器初始化完毕的事件去实现注入static property不是更爽吗?这也是我最先想到的方式,但是这两种方式的注入的时间太靠后了,那个preInstantiateSingletons方法已经被调用过了,有些bean已经被初始化了,例如quartz的SchedulerFactoryBean,就有可能你部署的quartz或者其他的 job已经开始跑了,但是领域模型还没有被注入,在那些job里调用领域模型都会空指针,所以一定要在preInstantiateSingletons方法调用前去注入。
那么如何辨别哪些是领域模型呢?就利用了spring的Configurable这个annotation去标识这个是需要静态注入的领域模型,然后自己实现了两种自动装配,BY_TYPE和BY_NAME,以及JSR-250的Resource annotation,例子如下
- @Configurable
- public class Student {
- private Long id;
- @Resource //注入field的名字所代表的bean,
- private static StudentDao studentDao;
- public static void setStudentDao(StudentDao studentDao) {
- Student.studentDao = studentDao;
- }
@Configurablepublic class Student { private Long id; @Resource //注入field的名字所代表的bean, private static StudentDao studentDao; public static void setStudentDao(StudentDao studentDao) { Student.studentDao = studentDao; }
- @Configurable
- public class Assistant {
- private static StudentDao studentDao;
- @Resource(name = "studentDao") //注入name为studentDao的bean
- public static void setDao(StudentDao studentDao) {
- Assistant.studentDao = studentDao;
- }
@Configurablepublic class Assistant { private static StudentDao studentDao; @Resource(name = "studentDao") //注入name为studentDao的bean public static void setDao(StudentDao studentDao) { Assistant.studentDao = studentDao; }
- @Configurable(autowire = Autowire.BY_TYPE)//自动装配
- public class SchoolMaster {
- private static StudentDao studentDao;
- // by type
- public static void setDao(StudentDao studentDao) {
- SchoolMaster.studentDao = studentDao;
- }
@Configurable(autowire = Autowire.BY_TYPE)//自动装配public class SchoolMaster { private static StudentDao studentDao; // by type public static void setDao(StudentDao studentDao) { SchoolMaster.studentDao = studentDao; }
单元测试以及Mock
- @Configurable(autowire = Autowire.BY_NAME)//自动装配
- public class Teacher {
- private static StudentDao studentDao;
- public static void setStudentDao(StudentDao studentDao) {
- Teacher.studentDao = studentDao;
- }
- public List<Students> getStudents() {
- return studentDao.getByClassId(this.getClassId());
- }
@Configurable(autowire = Autowire.BY_NAME)//自动装配public class Teacher { private static StudentDao studentDao; public static void setStudentDao(StudentDao studentDao) { Teacher.studentDao = studentDao; } public List<Students> getStudents() { return studentDao.getByClassId(this.getClassId()); }
- StudentDao studentDao = EasyMock.createMock(StudentDao.class);
- EasyMock.expected(studentDao.getByClassId(998877)).andReturn(students);
- Teacher.setStudentDao(studentDao);
- Teacher zhang3 = new Teacher();
- zhang3.setClassId(998877);//设置班级ID
- List<Student> actual = zhang3.getStudents();
- .......
StudentDao studentDao = EasyMock.createMock(StudentDao.class); EasyMock.expected(studentDao.getByClassId(998877)).andReturn(students); Teacher.setStudentDao(studentDao); Teacher zhang3 = new Teacher(); zhang3.setClassId(998877);//设置班级ID List<Student> actual = zhang3.getStudents(); .......
后半部分说了这么多其实都是实现static property的注入,原理很简单,代码也很简单,就4个类,但是我认为这么简单的东西spring之类的框架可以更容易并且实现的更好,也就是说目前的java可以简单的解决领域对象和dao依赖的问题从而实现rich domain model。
当然这个代码只是demo性质的,实现的比较简单,只有web方式的实现方法,希望和大家共同讨论。
- 简单,易于Mock,仅依赖Spring的Domain Model
- Domain Model
- JAVA 贫血的 Domain Model 的心得
- Domain Model:业务对象的进一步设计
- mock简单的json返回
- 一个简单的mock例子
- 简单的Spring依赖注入例子~~
- 简单的Spring依赖注入例子~~
- Spring简单的依赖注入例子
- 简单的Spring依赖注入例子~~
- java简单模拟Spring的依赖注入
- spring依赖注入原理的简单模拟
- Spring Mock--用于Spring 的单元测试
- Spring-Mock--用于Spring 的单元测试
- 简单易于理解的C#事件代理例子
- 利用Spring的mock进行单元测试
- 利用spring的mock类进行单元测试
- 利用spring的mock类进行单元测试
- J2ME平台体系结构
- 看完> 同行们有什么想法??
- NSGA2代码分析——rank.c
- 常用数据库连接
- 转贴-有关TinyXML使用的简单总结
- 简单,易于Mock,仅依赖Spring的Domain Model
- js中cookie的使用
- 智能指针CComPtr 和 CComQIPtr
- 原来cvMinMaxLoc的roi是这样的
- ActiveMQ (5)
- 网页制作技巧及常用代码集锦
- 总结必须学习的10项.NET技术
- netbean反编译插件NBJAD
- VC 提供的数据库访问技术