Aspect在DomainModel持久化的应用,是我所知,AOP较为成功的例子之一。就目前AOP应用来说,
非对称模式要比对称模式,更容易找到应用领域。我想这主要是因为非对称模式,更接近于OO传统思路。对于非对称模式来说,基本上,扩展者和被扩展者是通过切点(扩展点)联系在一起,被扩展者相对完整。扩展者也只需要关注切点展现的对象和对象特征。
而对于对称模式,是通过对象来封装?还是通过Aspect来封装,如何划分和封装?确实缺乏有效指导。我想,这是阻碍AOP推广的重要原因。或许,需要找到一种“化学反应中催化剂”技术,AOP才能充分发挥其特有威力。
说到持久化,意味着两件事,1.把DomainObject存储到存储器上(通常是数据库);2.把DomainObject从存储器中重新构建出来。
下面来看看,AOP是如何巧妙处理DomainObject持久化的第一件事。把DomainObject的存储看作是DomainObject的一个方面,无疑是解决问题的关键。按照这种思路,DomainObject只需要关心业务逻辑,不必关心自己是如何被存储到数据库中,Aspect会来打理这一切。
如何建立该Aspect呢。首先,我们考察对DomainObject存储都涉及到哪些操作?这个问题很简单,共有增、删、改三种。各位闭上眼睛也能想清楚。接下来,看看这三者是在什么时候发生的。“增加”--意味着在业务上创建,意味着一个“生命”的开始。这和构造函数很类似,区别仅仅在于构造函数只意味着在内存中创建对象。所以我们可以考虑把切点写成,截获那些确实表示业务上创建DomainObject的构造函数返回。对于“删除”来说,确实找不到好的对应概念,所以,我们不得不添加一个什么也不做的destroy方法,来表示在业务上,该DomainObject我们不再需要,让它回归伟大的“虚无”。“更改”呢?“更改”意味着,DomainObject属性发生变化。尽管我们可以考虑监视字段变化,但通过set和add、remove等方法,似乎更加容易理解,因为对这些方法的调用就意味着DomainObject被改变了。这样我们就完成了所有操作的切点分析。借助于MartinFowler的UnitOfWork模式,完成DomainObject的Lifecycle Aspect就不是什么难事。
接下来,我们要考察,DomainObject持久化的第二件事。把DomainObject从存储器中重新构建出来。虽然DomainObject不知道持久化的具体实现,但是知道查询接口还是允许的。但仅仅是接口,没法直接使用。我们还需要把它的实现注入进来。在注入前,我们需要想好,这些接口的“居所”。如果一个DomainObject--A需要通过查找接口找到另外一个DomainObject--B,似乎我们可以把这个接口作为A的成员来完成这样的需求。考虑到,查找接口的实现有无状态的特征。进而,我们可以考虑把它作为A的静态成员,来为所有A对象实例服务。这确实比对象级别注入效率高很多。不过,我们还是看到了不少重复代码,因为DomainObject--C也需要查找到B,按照前面的设计,我们不得不在C中也静态注入B查找接口。我们需要寻找一个,单点维护的地方来锚定查找接口。确实有这样一个地方,那就是在B类上静态注入B的查询接口。这样对于任何想获取B类查找接口的请求,都可以通过B.getBFinder得到。这里其实还有一个小小的问题,“对于通过比B类高的对象D,来查找B,由于,B类不该引用比自己高的类D,但是B的查找接口绑定在B类上,就导致了B类间接引用了D”,对这个问题的思考结果,我的回答是:“通过AOP的Inter-type来完善对于B查找接口的声明。”曾经还想过其他的一些方法,最终还是认为这是最直接和优雅的一种。
典型的Finder锚定
确保Finder都正确IOC的断言
非对称模式要比对称模式,更容易找到应用领域。我想这主要是因为非对称模式,更接近于OO传统思路。对于非对称模式来说,基本上,扩展者和被扩展者是通过切点(扩展点)联系在一起,被扩展者相对完整。扩展者也只需要关注切点展现的对象和对象特征。
而对于对称模式,是通过对象来封装?还是通过Aspect来封装,如何划分和封装?确实缺乏有效指导。我想,这是阻碍AOP推广的重要原因。或许,需要找到一种“化学反应中催化剂”技术,AOP才能充分发挥其特有威力。
说到持久化,意味着两件事,1.把DomainObject存储到存储器上(通常是数据库);2.把DomainObject从存储器中重新构建出来。
下面来看看,AOP是如何巧妙处理DomainObject持久化的第一件事。把DomainObject的存储看作是DomainObject的一个方面,无疑是解决问题的关键。按照这种思路,DomainObject只需要关心业务逻辑,不必关心自己是如何被存储到数据库中,Aspect会来打理这一切。
如何建立该Aspect呢。首先,我们考察对DomainObject存储都涉及到哪些操作?这个问题很简单,共有增、删、改三种。各位闭上眼睛也能想清楚。接下来,看看这三者是在什么时候发生的。“增加”--意味着在业务上创建,意味着一个“生命”的开始。这和构造函数很类似,区别仅仅在于构造函数只意味着在内存中创建对象。所以我们可以考虑把切点写成,截获那些确实表示业务上创建DomainObject的构造函数返回。对于“删除”来说,确实找不到好的对应概念,所以,我们不得不添加一个什么也不做的destroy方法,来表示在业务上,该DomainObject我们不再需要,让它回归伟大的“虚无”。“更改”呢?“更改”意味着,DomainObject属性发生变化。尽管我们可以考虑监视字段变化,但通过set和add、remove等方法,似乎更加容易理解,因为对这些方法的调用就意味着DomainObject被改变了。这样我们就完成了所有操作的切点分析。借助于MartinFowler的UnitOfWork模式,完成DomainObject的Lifecycle Aspect就不是什么难事。
接下来,我们要考察,DomainObject持久化的第二件事。把DomainObject从存储器中重新构建出来。虽然DomainObject不知道持久化的具体实现,但是知道查询接口还是允许的。但仅仅是接口,没法直接使用。我们还需要把它的实现注入进来。在注入前,我们需要想好,这些接口的“居所”。如果一个DomainObject--A需要通过查找接口找到另外一个DomainObject--B,似乎我们可以把这个接口作为A的成员来完成这样的需求。考虑到,查找接口的实现有无状态的特征。进而,我们可以考虑把它作为A的静态成员,来为所有A对象实例服务。这确实比对象级别注入效率高很多。不过,我们还是看到了不少重复代码,因为DomainObject--C也需要查找到B,按照前面的设计,我们不得不在C中也静态注入B查找接口。我们需要寻找一个,单点维护的地方来锚定查找接口。确实有这样一个地方,那就是在B类上静态注入B的查询接口。这样对于任何想获取B类查找接口的请求,都可以通过B.getBFinder得到。这里其实还有一个小小的问题,“对于通过比B类高的对象D,来查找B,由于,B类不该引用比自己高的类D,但是B的查找接口绑定在B类上,就导致了B类间接引用了D”,对这个问题的思考结果,我的回答是:“通过AOP的Inter-type来完善对于B查找接口的声明。”曾经还想过其他的一些方法,最终还是认为这是最直接和优雅的一种。
java代码: |
public aspect Lifecycle { protected pointcut create ( ): !within (com.. data..* )&& call (IDomainObject+. new (.. ) ); protected pointcut modify (IDomainObject domainObject ): target (domainObject+ ) && (call ( void IDomainObject+. set* (.. ) ) || call ( void IDomainObject+. add* (.. ) ) || call ( void IDomainObject+. remove* (.. ) ) ) && !call ( static void IDomainObject+. set*Finder (.. ) ) && !withincode (IDomainObject+. new (.. ) ) && !within (com.. data..* ); protected pointcut destroy (IDomainObject domainObject ): target (domainObject+ )&& call ( void IDomainObject. destroy ( ) ); after ( ) returning (IDomainObject domainObject ): create ( ) { UnitOfWork. getCurrent ( ). registerCreated (domainObject ); } after (IDomainObject domainObject ): modify (domainObject ) { UnitOfWork. getCurrent ( ). registerDirty (domainObject ); } after (IDomainObject domainObject ): destroy (domainObject ) { UnitOfWork. getCurrent ( ). registerDestroyed (domainObject ); } } |
典型的Finder锚定
java代码: |
public class Customer { private static ICustomerFinder customerFinder; public static ICustomerFinder getCustomerFinder ( ) { return customerFinder; } public static void setCustomerFinder (ICustomerFinder customerFinder ) { Customer. customerFinder = customerFinder; } } |
确保Finder都正确IOC的断言
java代码: |
public aspect FinderVerification { private pointcut setXXXFinder ( ) : execution ( public static void IDomainObject+. set*Finder (.. ) ); private pointcut getXXXFinder ( ) : execution ( public static * IDomainObject+. get*Finder ( ) ); before ( ) : setXXXFinder ( ) { String methodSignature = thisJoinPoint. getSignature ( ). toShortString ( ); assert thisJoinPoint. getArgs ( ) [ 0 ] != null : methodSignature + " 参数为 null"; } Object around ( ) : getXXXFinder ( ) { Object finder = proceed ( ); String methodSignature = thisJoinPoint. getSignature ( ). toShortString ( ); assert finder != null : methodSignature + " 返回 null "; return finder; } } |