最近同时在看《Core J2EE Patterns》跟ApacheOFBiz 源码,确实正如OFBiz官方介绍的那样,OFBiz应用了该书中的很多经典的设计模式。本篇结合OFBiz的源码试图总结一下其中用到的几个典型的Patterns。
典型的J2EE模式
业务代表模式
业务代表模式 主要目的是用于隐藏业务逻辑对于调用端的实现,消除不同层次之间的耦合,它封装了业务服务的访问。
OFBiz中对于业务代表模式的实现令人印象深刻,因为它在service跟entity engine层都实现了该模式。这里我们只讨论entity engine中的实现。
在org.ofbiz.entity下有一个Delegator接口,它就是其他层跟持久层打交道的入口。
OFBiz默认给出了一个实现:GenericDelegator:
Delegator几乎提供了对所有数据访问需求的抽象,包括:数据缓存管理、事务管理、主键管理、查找、创建、更新、删除等。这有些颠覆我们想象中的开发模式,没错,这种方式跟我们通常的开发方式有些不一样。我们通常会基于数据库的设计来构建entity,然后基于每个entity构建DAO(或者把这个任务交给ORM框架)。但OFBiz不是这么做的(想象一下,如果它这么做了,它也实现不了现在的业务代表模式),在OFBiz entity engine中,并没有针对o-r mapping构建出来的entity,取而代之的是一个通用的、泛化的实体,而且不仅仅是如此,这里包括sql语句的组成部分都是被抽象过之后形成的“语义层”,通过XML配置来完成实体、关系的定义。这才给了OFbiz实现业务代表模式的可能。
它如何实现用一个业务代表,代理了所有数据访问?
我们看到它有两个名为getEntityHelper方法的重载,它们都返回GenericHelper对象。这里我们需要来了解几个entityengine内部的辅助对象:
GenericDAO:实现了基本的数据访问操作的(CRUD)。这其中,每个方法都需要具体去跟数据库通信,通过什么?通过SQLProcessor对象
SQLProcessor:SQLProcessor负责每次SQL的执行
GenericHelper:通常一个系统里对于数据的访问/操作并不仅仅只是简单的CRUD,其中还包含有更复杂的操作(比如关系查询、条件删除等),因此OFbiz为这些相对复杂的操作抽象出了GenericHelper。
GenericHelperDAO:是对GenericHelper的实现,其内部借助于GenericDAO,来实现这些相对复杂的数据访问。
就此我们基本理清了Delegator对于数据访问的脉络。
传输对象模式
传输对象模式之前称之为ValueObject模式(值对象模式)
传输对象模式用于解决跨层次传输多种数据的问题。
在一些系统中,会有不同层次的两个组件通信(集成层与业务层或表现层与业务层)的场景,两个组件之间有可能是采用EJB实现的远程调用,如果你需要获取的不是单个实体的,而是一组数据(比如在客户端展示的信息,有可能是基于多个实体的并集)。多次调用远程对象的getter方法,很明显会增加网络负载并且降低系统性能。而传输对象就是用来解决这一问题的
在OFBiz中,可以看到对传输对象模式的应用:
这种应用非常广泛,因为在OFbiz中并没有对每种对象构建一个javabean,因而也就不存在object-relationalmapping(orm)的说法,它采用上面这几个对象表示了所有从数据库获取到的数据的表示(也就是采用了通用实体的方式),而且这几个通用对象都包含了数据访问行为(也就是说OFbiz遵循了j2eeejb 中的entitybean的设计方式,是充血模型的实体,而非只是数据库对象的载体)。
其中:
GenericEntity:是一个实现了Value Object模式(传输对象的前称)的基类;
GenericValue:可以看做从数据库获取到的任意一条数据行记录(实现了多重复合传输对象模式);
GenericPK:用于表示主键对象;
下图展示了GenericEntity的类图:
可以看到它实现了Serializable,因此它是可以串行化到客户端,被当成本地对象来使用的(而不仅仅是本地引用,因为它包含的fields集合已经作为一个Map传输到客户端来了)。
它继承了Observable,用于接收客户端对实体的更新通知,如果客户端对实体进行了更新,将会通过setFields方法进行更新(setFields方法是个粗粒度的update方法,这避免了采用频繁调用细粒度的setter方法导致的网络开销)。因此它是一个可更新的传输对象。
另外,它实现了Map<String,Object> 包装了内部的Map。事实上,它只是对真实的数据实体的一种包装(真实的数据实体是ModelEntity),它只是通过必备的ModelEntity来实例化自身,而它自身被实现为“传输对象”。
这里它的目的就是封装其内部的fields集合,而免去你对各个field不断调用getter方法。
复合实体模式
复合实体是一种粗粒度的entitybean,而不是跟数据库设计相关的实体对象(o-rmapping),它被实现为业务对象,包含一个父对象以及一些从属对象等。使用这种模式的好处:
避免基于数据库设计跟对象属性之间一一映射的关系,从而暴露数据库的设计给客户端,另外由于是粗粒度的,有可能对数据库的改动并不一定会牵扯到复合实体。这在增大可维护性的同时,还提高了网络性能,因为这可以让你获得一次获得一组数据集合,而不是频繁得调用getter方法。
在OFbiz中,复合实体的模式提现在:org.ofbiz.entity下,下面的图中体现了真实的model之间的关系:
这其中,ModelEntity用于表示从数据库获取到的真实的实体对象,而它作为一个复合对象,在其内部集成了ModelReader、ModelField的集合、ModelRelation的集合、ModelIndex的集合,另外不要忘记,事实上GenericEntity也是一个复合实体,它内部集成了ModelEntity。
传输对象组装器模式
传输对象组装器以符合传输对象的形式构建应用模型。传输对象组装器从各种不同的业务组件和业务服务中聚合多个传输对象,并且最后把符合传输对象返回给客户端。
这一模式很好得体现在GenericValue的实现中:
它提供了一系列getter接口,用于对一系列的相关实体进行访问,这其实是传输对象中的多重传输对象(它提供了一种粗粒度的接口,用于减少远程调用的网络开销)。
服务定位器模式
服务定位器模式用于实现、封装对服务/组件的寻址。服务定位器能够隐藏寻址机制的实现细节,封装这一机制对于不同实现的依赖。
其实,服务定位器在OFBiz最广泛的应用实在服务层。但在entity engine中,也有应用,那就是通过JNDI对JTA 对象的查找。
其主要实现位于org.ofbiz.entity.transaction中:
这里有一个变量名为dsCache的内部对象,它对完成对查找到的Datasource对象的缓存。在getJndiConnection方法中,会首先在dsCache中进行查找,如果不存在才从InitialContext中查找,而getJndiConnection又提供了对getConnection方法的实现。
JDNI的配置位于{basedir}/framework/entity/config/entityengine.xml定义中:- <!-- Use this one for getting the JTA objects from JNDI -->
- <!-- NOTE: to use the JndiFactory you must specify the necessary JNDI properties
- <transaction-factory class="org.ofbiz.entity.transaction.JNDIFactory">
- <user-transaction-jndi jndi-server-name="default" jndi-name="java:comp/UserTransaction"/>
- <transaction-manager-jndi jndi-server-name="default" jndi-name="java:comp/UserTransaction"/>
- </transaction-factory>
- -->
- <!--
- It is common to use UserTransaction for the TransactionManager, but if that doesn't work, try this: <transaction-manager-jndi jndi-server-name="default" jndi-name="java:comp/TransactionManager"/>
- Common UserTransaction locations:
- java:comp/UserTransaction (most servers: Resin, Orion, OC4J, etc)
- UserTransaction (RexIP)
- JBoss uses two different objects for the UserTransaction and TransactionManager interfaces;
- they are located in JNDI at: "java:comp/UserTransaction" and "java:/TransactionManager" respectively
- -->
综述
这些模式里经常有提到减小网络开销,减少网络调用,是因为OFBiz完全遵从了J2EE规范,其data access以及service是可以允许分布式访问的(而不仅仅只是提供给web层的本地调用组件那么简单)。因此它才需要引用上面的这些设计模式,来尽可能得避免RMI的性能问题。