<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>Norther</title>
    <description></description>
    <link>http://norther.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>简单，易于Mock，仅依赖Spring的Domain Model</title>
        <author>Norther</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://norther.javaeye.com">Norther</a>&nbsp;
          链接：<a href="http://norther.javaeye.com/blog/193496" style="color:red;">http://norther.javaeye.com/blog/193496</a>&nbsp;
          发表时间: 2008年05月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>声明：该贴并不讨论Domain Model对于企业应用是否有意义，JE已经讨论过很多了，有很多优秀的帖供参考，这里仅谈实现。<br /><br />马丁大叔在《企业应用架构模式中》提出Domain Model（领域模型，领域对象，Domain Object）的概念后，我们发现这才是开发企业应用更OO的模式，以前的Transaction Script简直太土了。。。按照领域模型组织起来的面向对象语言代码，简直美极了，而ROR中的ActiveRecord就是一种Domain Model的实现，其威力大家也都见识过了。但是因为Java语言的特性的原因（例如静态类型），一直没有一种好的，优美的，简单的方式去实现，主要的难题就是领域对象和数据库逻辑的依赖关系（例如和Dao的依赖），大家关于这方面的尝试从来就没有断过，目前有以下几种主流的实现方式：<br /><br />1 放弃IOC，在DomainObject里直接去获得Dao的Instance，例如在Domain Object里拿到Spring上下文，直接get所需要的Bean，这样的优势就是简单，缺点就是DomainObject与Dao的实现以及上下文的耦合，领域逻辑的执行依赖数据库，无法单元测试。有的观点认为利用内存数据库，这样比真正的访问数据库要快，要方便，认为mock dao没有意义，这个观点还持保留意见 ：）<br /><br />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框架里加拦截器实现注入，真的是很郁闷。<br /><br />3 将Dao通过方法参数传入，优点就是简单，POJOS In Action里就提到了这种方式，缺点就是会影响方法以及接口的设计，经常会出现一个方法会有6个参数之多，而5个都是Dao，真是很丑陋。<br /><br />4 利用AspectJ等工具实现编译期注入，Spring 2.0刚出的时候，引入了AspectJ，其Reference中就提到了这个方式，我也尝试过，非常的麻烦，而且性能很慢，没记错的话当时大概是06年10月份，用的JDK5，现在不知道有没有改善，我当时还在JE发过新手帖询问过，某大牛说是BEA的虚拟机对其有优化，我也没条件尝试，而且编译期的手脚动太多了，感觉不是很好。<br /><br />上述几种方式都有明显的缺点，我们追求的好的领域模型的实现就是，领域模型不依赖实现，例如可以注入，易于单元测试，而且其生命周期不依赖特定的环境，还要够简单！难道真是Robbin所说的JAVA不适合Rich Domain Model吗？不见得：<br /><br />首先，不依赖实现，可以注入，那么要在领域模型中提供接口dao的set方法，允许被注入，这个很明确。</p>
<p>其次，领域模型的生命周期不依赖环境， 不依赖hibernate的构造，不被spring管理等。一般的情况，dao都是无状态的，单例的，<span style="color: red;">那么把领域模型中的依赖的dao设置为static</span>，static是class级别的，和具体的instance没关系，只要在应用初始化的时候给所有的领域模型的class注入一次，整个应用运行的时间周期，领域模型随意怎么new，在哪new，new出来的Domain Object的都可以访问一开始被注入的static的dao，实现了领域模型的生命周期不依赖环境，这样也易于测试，易于MOCK，和目前传统编程方式差别很小，仅仅把dao改为static的。</p>
<p>&nbsp;</p>
<p><span style="color: #000000;">但是spring可以实现对一个class的静态peroperty注入吗？答案是不能（经</span>bottom<span style="color: #000000;">同学修正，spring是可以静态property注入，但是还是要在所有bean初始化之前注入才行，所以还是要扩展，我在3楼已经回复说明）</span>，但是spring是个优美扩展性强的框架，我们只要稍微扩展一下，就可以达到我们的目的。<br /><br />就拿一个典型的web应用为例，普遍的方式的是在web.xml配置spring的context loader listener和spring的配置文件，这个listener会在应用启动时，创建并初始化spring的上下文，我们需要在所有bean初始化之前注入domain class，那么就在这里找切入点，通过spring的源代码可以发现类AbstractApplicationContext的refresh方法内的finishBeanFactoryInitialization有些重要的东东。</p>
<pre name="code" class="java">/**
 * 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();
}
</pre>
<p><br />通过观察代码发现，初始化单例其实就是将Spring配置文件中定义的所有不是lazy init的单例初始化了，对一个Bean的初始化就是调用getBean来完成，Spring在调用getBean的时候，会判断如果已经初始化了就直接返回给你Bean的引用，否则就初始化这个Bean再返回给你，很典型的实现LAZY的方式。<br /><br />我们只需要在这个初始化方法之前，主动去扫描所有的领域模型class，并且知道他们需要注入的Dao，直接getBean获得dao的实例，并且注入进Domain Model的class就完成了，那么我们只需要自己实现一个ConfigurableListableBeanFactory接口，也就是继承DefaultListableBeanFactory（其是ConfigurableListableBeanFactory接口的默认实现）， 并且在preInstantiateSingletons方法的时候先注入领域模型，巨简单</p>
<pre name="code" class="java">private class StaticPropertyInjectSupportListableBeanFactory extends DefaultListableBeanFactory {
       @Override//覆盖父类的方法
       public void preInstantiateSingletons() throws BeansException {
              //先注入所有的领域模型的class
              injectStaticPropertyForBasePackage();
              //再调用父类的该方法去初始化所有的非LAZY单例
              super.preInstantiateSingletons();
       }
}
</pre>
<p>继续观察代码，finishBeanFactoryInitialization的方法的参数ConfigurableListableBeanFactory，是通过调用的createBeanFactory方法获得的，默认的实现在AbstractRefreshableApplicationContext中有这样一个方法，去创建DefaultListableBeanFactory，而这个方法是protected的，摆明着让我们去复盖扩展其功能，这也是spring易于扩展的体现，我们只要实现一个自己的ApplicaitonContext，覆盖createBeanFactory方法创建我们刚才自己实现的StaticPropertyInjectSupportListableBeanFactory就OK了，代码也巨简单</p>
<pre name="code" class="java">public class StaticPropertyInjectSupportXmlWebApplicationContext extends XmlWebApplicationContext {
       @Override
       protected DefaultListableBeanFactory createBeanFactory() {
              return new StaticPropertyInjectSupportListableBeanFactory(getInternalParentBeanFactory());
       }
}
</pre>
<p><br />实现了自己的ApplicationContext，怎么让Spring启动的时候初始化你的上下文呢？当然可以了，spring提供了扩展的后门，只要在web.xml配置context parameter "contextClass"告诉spring，他就会用你自己的ApplicationContext去初始化了，不配置该参数默认的ApplicationContext实现是XmlWebApplicationContext。<br /><br />关于自己实现静态property的注入就很简单了，在context parameter里加入个参数configurableBeanBasePackage，获得所有要注入的领域模型的父包，就可以了，如下</p>
<pre name="code" class="xml">&lt;!--加入这两个参数，其余不变 --&gt;
&lt;!--告诉spring用我们自己实现的支持静态bean注入的context --&gt;
&lt;context-param&gt;
        &lt;param-name&gt;contextClass&lt;/param-name&gt;
	&lt;param-value&gt;com.norther.sps.StaticPropertyInjectSupportXmlWebApplicationContext&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;!--注入com.norther包下（包含子包）所有的领域模型的class--&gt;
&lt;context-param&gt;  
	&lt;param-name&gt;configurableBeanBasePackage&lt;/param-name&gt;
	&lt;param-value&gt;com.norther&lt;/param-value&gt;
&lt;/context-param&gt;
</pre>
<p><br />这样就OK了，有人会想到这样还是有一点点点点麻烦，用PostProcessorBean或者Spring的容器初始化完毕的事件去实现注入static
property不是更爽吗？这也是我最先想到的方式，但是这两种方式的注入的时间太靠后了，那个preInstantiateSingletons方法
已经被调用过了，有些bean已经被初始化了，例如quartz的SchedulerFactoryBean，就有可能你部署的quartz或者其他的
job已经开始跑了，但是领域模型还没有被注入，在那些job里调用领域模型都会空指针，所以一定要在preInstantiateSingletons方法调用前去注入。</p>
<p>&nbsp;</p>
<p>那么如何辨别哪些是领域模型呢？就利用了spring的Configurable这个annotation去标识这个是需要静态注入的领域模型，然后自己实现了两种自动装配，BY_TYPE和BY_NAME，以及JSR-250的Resource annotation，例子如下</p>
<pre name="code" class="java">@Configurable
public class Student {
       private Long id;

       @Resource //注入field的名字所代表的bean,
       private static StudentDao studentDao;

       public static void setStudentDao(StudentDao studentDao) {
              Student.studentDao = studentDao;
       }
</pre>
<p>&nbsp;</p>
<pre name="code" class="java">@Configurable
public class Assistant {
       private static StudentDao studentDao;

       @Resource(name = "studentDao") //注入name为studentDao的bean 
       public static void setDao(StudentDao studentDao) {
              Assistant.studentDao = studentDao;
       }
</pre>
<pre name="code" class="java">@Configurable(autowire = Autowire.BY_TYPE)//自动装配
public class SchoolMaster {
       private static StudentDao studentDao;

       // by type
       public static void setDao(StudentDao studentDao) {
              SchoolMaster.studentDao = studentDao;
       }
</pre>
<p>
单元测试以及Mock</p>
<pre name="code" class="java">@Configurable(autowire = Autowire.BY_NAME)//自动装配
public class Teacher {
       private static StudentDao studentDao;

       public static void setStudentDao(StudentDao studentDao) {
              Teacher.studentDao = studentDao;
       }

       public List&lt;Students&gt; getStudents() {
              return studentDao.getByClassId(this.getClassId());
       }
</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<pre name="code" class="java"> 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&lt;Student&gt; actual = zhang3.getStudents();
 .......

</pre>
<p><br />后半部分说了这么多其实都是实现static property的注入，原理很简单，代码也很简单，就4个类，但是我认为这么简单的东西spring之类的框架可以更容易并且实现的更好，也就是说目前的java可以简单的解决领域对象和dao依赖的问题从而实现rich domain model。</p>
<p>&nbsp;</p>
<p>当然这个代码只是demo性质的，实现的比较简单，只有web方式的实现方法，希望和大家共同讨论。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://norther.javaeye.com/blog/193496#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 16 May 2008 00:09:17 +0800</pubDate>
        <link>http://norther.javaeye.com/blog/193496</link>
        <guid>http://norther.javaeye.com/blog/193496</guid>
      </item>
      <item>
        <title>模仿Warp Dynamic Finder的Hibernate Dynamic Dao</title>
        <author>Norther</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://norther.javaeye.com">Norther</a>&nbsp;
          链接：<a href="http://norther.javaeye.com/blog/172265" style="color:red;">http://norther.javaeye.com/blog/172265</a>&nbsp;
          发表时间: 2008年03月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          看了Robbin前两天发的那帖，Warp framework - 一个相当有前途的Java轻量级Web开发框架(<a href="http://www.javaeye.com/topic/168780" target="_blank">http://www.javaeye.com/topic/168780</a>)，让人眼前一亮，特别是基于annotation的warp-dynamic-finder部分给人印象非常深刻，利用它，80%情况下Dao的实现不用去写了，只要定义个interface，加几个annotation轻松搞定，自己就着手实现了一个，基于spring的HibernateDaoSupport，其用法也很简单，如下：<br /><br /><pre name="code" class="java">
public interface StudentDao {
	@Save
	Long save(Student student);

	@Delete
	void delete(Student student);

	@Get
	Student get(Long id);

	@Query("from Student as s where s.name = ? and s.age = ?")
	Student getByNameAndAge(String name, Integer age);//根据参数出现的次序绑定

	@Query("from Student as s where s.age > :age")
	List&lt;Student> getStudentsAgeMoreThan(
			@Parameter("age") Integer age);//name绑定

	@Query("from Student as s where s.name like ?")
	Student getStudentNameLike(
			@Like(matchMode = MatchMode.START) String name);//支持like

	@Query(value = "delete Student where name = ?", executeUpdate = true)
	int deleteStudentByName(String name);//批量更新

	@Query("from Student as s order by s.id desc")
	@Conditions({ 
		@Condition("s.name = ?"), //动态条件添加，如果第一个参数不为null，该条件会被插入query string
		@Condition("s.age = ?") 
	})
	Student query(String name, Integer age);

}

</pre><br /><br />这样一个Dao就定义OK了，非常容易，有两种使用方式，一种是利用AutoInjectDynamicDaoBeanPostProcessor，在spring中的bean初始化好之后，找到有InjectDao标注的方法就，利用动态代理生成StudentDao的代理实现并注入：<br /><br /><pre name="code" class="xml">
&lt;bean class="com.norther.dynamic.dao.AutoInjectDynamicDaoBeanPostProcessor">
	&lt;property name="sessionFactory" ref="sessionFactory" />
&lt;/bean>

&lt;bean id="studentService " class="com.norther.dynamic.dao.test.StudentService" />

</pre><br /><pre name="code" class="java">
public class StudentService {

	private StudentDao studentDao;

	@InjectDao//会被注入StudentDao动态代理实现
	public void setStudentDao(StudentDao studentDao) {
		this.studentDao = studentDao;
	}
</pre><br /><br />还有另一种方式是基于FactoryBean的DynamicDaoProxy，并自己指定要实现的Dao接口，如下：<br /><pre name="code" class="xml">
&lt;bean id="studentDao " class="com.norther.dynamic.dao.DynamicDaoProxy">
	&lt;property name="dao" value="com.norther.dynamic.dao.test.StudentDao" />
&lt;/bean>
&lt;bean id="teacherDao " class="com.norther.dynamic.dao.DynamicDaoProxy">
	&lt;property name="dao" value="com.norther.dynamic.dao.test.TeacherDao" />
&lt;/bean>

</pre><br /><br />非常的简单，也很初级，有什么不足，请大家多多指教，呵呵。
          <br/>
          <span style="color:red;">
            <a href="http://norther.javaeye.com/blog/172265#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 16 Mar 2008 01:21:21 +0800</pubDate>
        <link>http://norther.javaeye.com/blog/172265</link>
        <guid>http://norther.javaeye.com/blog/172265</guid>
      </item>
  </channel>
</rss>