基于代理的过滤器的美妙之处是转移机制本身。每当我们调用一个目标对象(流程)时,拦截过滤器将一个接一个的被代理机制所调用。这是自动的,而且在每个活动必须被调用的情况下工作得相当好。但是实际并不总是这种情况。早先我们在问题定义中描述之一是:“已经被实现的流程目标的那部分,表示已完成的事实,它被用来协调剩余活动的执行”——这意味着一个活动完成并不是必须转移到另一个活动。在实际的流程中,转移必须严格地基于前一个活动已完成和/或未完成的事实。这些事实必须向共享数据占位符注册,这样它们就可以被审核。
完成这一点可以简单到在我们的拦截过滤器中放一个IF语句:
if (fact(s) exists){
invoke activity
}
}
但是这将产生多个问题。在我们研究是些什么问题之前,让我们先搞清楚一件事:在当前的结构中,每个拦截过滤器紧密地与相应的POJO活动耦合在一起。正因为如此,我们可以很容易把所有活动逻辑保持在拦截过滤器本身中。唯一能够阻止我们这样做的是我们希望活动保持为POJO,这意味着在拦截过滤器中的代码将简单地委派给活动回调(Activity callback)。
这意味着如果我们把转移求值逻辑放在活动中,我们将从根儿上把这两个关注点耦合在一起(活动转移罗辑和活动业务罗辑),这将违背分离关注点的基本架构原则并将导致关注点/代码耦合(concern/code coupling)。另一个问题则涉及到所有拦截过滤器会重复同一转移逻辑。我们称之为关注点/代码扩散(concern/code scattering)。转移逻辑横贯所有拦截过滤器,你可能已经猜到了,AOP再次成为所选技术。
我们所需做的一切就是写一个around advice,它将使我们能够拦截对实际过滤器类目标方法的调用,对输入求值,并作出转移决策,要么允许,要么不允许目标方法执行。唯一要告诫的是我们的目标类本身恰恰就是拦截过滤器。因此本质上我们正在试图拦截拦截器。不幸的是Spring AOP不能帮上忙,因为它是基于代理的,因此我们的拦截过滤器已经是代理基础架构的一部分了,我们不能代理这个代理(proxy the proxy)。
但是AOP最好的特性之一就是它可以有多种不同的风格和实现(例如,基于代理的,字节码编织等等……)。尽管我们不能使用基于代理的AOP来代理另一个代理,但是我们使用字节码编织AOP,谁也拦不住,它将通过编织(编译时或装载时)我们的转移求值逻辑,来形成我们代理的拦截过滤器,这样就可以保持转移和业务逻辑分离。使用像AspectJ这样的框架很容易做到这一点。这样做,我们就给我们的架构引入了第二个AOP层,这是非常有意思的实现。我们使用Spring AOP来解决功能关注点,比如用活动编排流程;同时,我们又使用AspectJ来解决非功能关注点,比如活动导航和转移。
下图记录了被显示为两个AOP层的流程流的最终结构,其中功能AOP层(Functional AOP Layer)负责从有序的拦截过滤器集合中装配流程,而非功能AOP层(Non-Functional AOP Layer)则解决转移控制的问题。
为证明这一架构的工作情况,我们将实现一个简单的用例——购买物品(Purchase Item),它定义了一个简单的流程流。