阅读更多
为AOP注入团队活力
作者:cleverpig
前言:
在
EclipseCon2007的看到了一篇理论性十足的
OT/J的提议搞, 引起了我强烈的兴趣,因为在文中仿佛听到了AOP车轮加速的声音,看到了AOP列车正在徐徐地奔向未来之路。为牵引机车提供强劲动力是其内部刚刚安装上的 ObjectTeams引擎组,它为AOP注入了空前强大的团队活力。它使我感觉自己发现了一个全新世界,找到了新的视角。
一、专用语:
Core concern:字面上译为“核心关系”,指程序中 的主要关系之一。例如,编写处理病历卡记录的应用时,记录数据以及这类记录排序是一个核心关系,然而对记录数据库或者用户数据库的更改进行历史记录的日志 系统,或者认证系统都将被称为横切关系,因为它们触及到了程序的多个部分。这些核心关系的总和是某个程序的业务逻辑,然而此程序的所有方面 (Aspect)在功能上都是需要,但这些Aspect并非实际业务逻辑的组成部分。
Cross-cutting关系:横切关系是指在一个程序中影响 (横切)其它关系的Aspect(横切其它核心关系的部分)。这些关系通常在设计和实现时都不能和系统的剩余部分被清晰地分解出来,从而导致发散的、混乱 的或者既发散又混乱的程序。例如编写处理病历卡记录的应用时,记录数据以及这类记录排序是一个核心关系,然而对记录数据库或者用户数据库的更改进行历史记 录的日志系统,或者认证系统都将被称为横切关系,因为它们触及到了程序的多个部分。
Aspect: 字面上翻译为“方面”或者“切面”,指程序中 横切核心关系的那部分。例如,日志代码能横切多个模块,然而日志的切面应该从它所横切的功能关系中分离出来。从业务逻辑中,对例如日志和持久化这样的切面 进行隔离是出于面向切面软件开发的目的,这也常常作在面向Aspect编程中的范例。
Aspect-oriented programming:包括面向Aspect的编程(
AOP),和用于帮助编程者解决处理关系分离(特别是横切关系)的面向Aspect软件开发(
AOSD)。 AOP通过使用主要的语言变化来实现,然而AOSD则使用多种语言的结合体、环境、关系的方法分离(把某个程序分解为尽可能小的部分)。所有的编程方法论 (包括过程编程和面向对象编程)都支持把关系的分离、封装为多个单一的实体。比如:过程、包、类和方法都帮助编程者将关系封装为多个单一的实体。但是某些 关系并不服从这样的封装形式。软件工程师提供了一些横切关系,因为他们在程序中对一些模块进行了横向切分。日志(Logging)提供了一个横切关系的实 例:因为一个日志策略可以影响到系统中每个使用日志功能的部分,所以日志横向切分了那些使用日志功能的类和方法。
AspectJ:是一个面向Aspect的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
CaesarJ:是基于java的一种新的编程语言,它具有更好的模块化和
重用组件开发能力。这些组件是类的集合体,但它们能够模块化横切特性和非功能的关系。此语言帮助开发者实现、抽象和继承这样的组件,并且可与java结合在一起使用。
ObjectTeams/Java:缩写为OT/J,是ObjectTeam的java语言实现。
team:表示对一组类的包裹,同时每个team具有类特征:继承、实例化,每个team实例作为定义在其中的对象的容器,提供了这些对象协作(collaboration)的环境(context)。
Subteam:指某个team的子类。
Superteam:指某个team的父类。
role:包含在team中的类具有能修饰其它类的
role(角色)。每个role通过一种
“被扮演”的关系绑定在某个类(base类)上面。
Subrole:指某个role的子类。
Superrole:指某个role的父类。
base类:一个被role修饰的类被称为“
base”,一个base对象只能拥有一个role实例。
base系统/应用:应用team的具体系统/应用。也可认为是使用base类的具体系统/应用。
join point:字面上翻译为“接入点”。在ObjectTeams/Java中,一个接入点被定义为程序的一个元素,这里的意思类似指程序功能调用点。
二、Object Teams的AOP血统:
另类的家族聚餐合影
面向Aspect软件开发为模块化软件提供了新的含义,其目的在于提高应用的可理解性、重用性、扩展性、适配性和进化能力。然而此领域的纯粹学院派研究成果用于实际的项目却很稀少。所以,
联合研究项目“TOPPrax”应 运而上,其致力于通过工业化个案研究证明面向Aspect软件开发已经达到了应用于复杂现代软件系统的成熟级别。基于对面向Aspect和面向对象软件开 发的直接比较研究,TOPPrax提供了一种评估机制。为了使实践性的发布更加真实,概念、工具和方法论都已经被并行地完成。
面向Aspect实现——
Object Teams和
Caesar已经超越了
AspectJ,它们通过继承、多态、通过封装和对联合体的改进以一种丰富的内部结构支持方面(aspects)的特性将Aspect视为“头等公民”。
三、Object Teams案例:
TOPPrax个案研究(case studies)的主要焦点是在重用性和扩展性上。这里提供一些使用Object Teams语言的个案,这些个案的主题是如何为系统提供安全性功能。它们不仅用于研究使用Object Teams编写的应用是否易于扩展成为安全组件,而且也可以证明一种面向Aspect的安全组件是否能被设计成为具有重用性的。
同时TOPPrax提供了这些个案的测试案例,它实现了一个部属组件(disposition component)的两种变体:一种面向对象实现、一种面向Aspect实现。它是一个监控库存和生成订货建议(order proposals)的ERP(企业资源计划)系统。我们可以基于这个部属组件,对使用Object Teams实现可重用Aspect的可用性研究这一疑问进行进一步的求证。此案例所产生影响之一就是帮助巩固Object Teams语言和工具,同时它已可以作为使用
JAsCo语言的比较实例,但本文并不涉及这比较。
这个安全组件不仅对部属组件的两种变体可用,而且对其它的ERP系统来讲也是可用的。不同系统和它们的结构暗示了对安全组件的不同需求。 所以,这个安全组件应该把通用“核心部分”作为重点,并按照不同的需求进行适配。因此,我们就必须采用面向Aspect技术去设计和实现一个针对这个安全 组件的框架。其中“通用核心”应该实现为使用Aspect的通用方式,并可以使用面向Aspect技术进一步改进。这个面向Aspect框架不仅使用 Aspect作为框架内部结构,而且这些Aspect在应用中扮演了一种核心角色。贯穿框架的开发过程,我们将使用模式技术来保证Aspect模块的重用 性。
四、Object Teams编程模式概要介绍:
Object Teams是一种面向Aspect的编程模式,它结合了其它编程范例和技术中的许多概念,并引入了一种新的
team概念。team表示对一组类的包裹,同时每个team具有类特征:继承、实例化,每个team实例作为定义在其中的对象的容器,提供了这些对象协作(collaboration)的环境(context)。包含在team中的类具有能修饰其它类的
role(角色)。每个role通过一种
“被扮演”的关系绑定在某个类上面。一个被修饰的类被称为“
base”,一个base对象只能拥有一个role实例。
图-1 Object Team实例
一个role和它的base对象交互的两种方式:
1.role类提供了
callin方法绑定,因此,role实例可以中途拦截对它所绑定的base对象的方法调用,进而执行它的role方法。callin绑定具有before、after、replace三种类型,分别表示在base对象的方法执行之前、之后执行,或者取代base对象的方法。
2.role类还提供了
callout方法绑定。它允许role实例转发方法调用到base方法上。base类方法和字段都可以通过callout绑定被访问到。
绑定可以访问base对象的任何方法和属性,其中包括私有类成员。role和包含它们的team结合在一起完成对此行为的封装——利用绑 定所产生的周围环境信息去适配base类集合。team概念可以升级为两种关系:第一,每个team的role本身可以是一个team;第二,不仅普通类 能被某个role绑定,而且team类和role类也可以如是这样。
适配行为可在运行时被控制。每个team实例能被激活或者冻结。如果某个team被激活,那么所有的callin绑定都将生效;而倘若这 个team被冻结,那么所有的callin绑定将全部失效。冻结team实例并不影响其状态和其包含的role的状态,因为这些状态在team实例的生命 周期中被进行了持久化。如果多个callin作用于同一个接入点(join point),那么它们的执行顺序由team激活顺序和每个team中的优先声明共同决定。
Object Teams支持对某个base类的被列出的属性进行绑定。将role方法绑定到一个接入点集合时,这个接入点集合可以通过集成在Object Teams的工具套件中的高级查询机制进行筛选。
五、安全案例研究:
这个安全组件需要通用设计,并实现为能被改进以匹配其它系统需求的组件。在详细讲解安全组件之前,首先介绍一下它将被应用于其中的“部属组件”。
部署组件概貌:
面向Aspect和面向对象的部署组件都具有相同的需求:监控库存、支持手工和自动建立订货建议、评估订货建议、执行订货。这些组件所依赖的问题域模式(domain model)涵盖了库存货物的属性和货物供应商的一些交货条件。
两种实现共享用户接口和使用面向对象技术实现的持久化层。用户接口用来生成GUI模式框架。而部属组件的持久化层则采用持久化框架实现。这两个框架都是支持模式驱动开发的商用框架TREND的一部分。
对于此案例的面向Aspect部分,其所需要的功能采用ObjectTeams/Java实现。Teams被用在封装
工作流(workflow)、封装持久化层和数据库事务上。系统设计遵从
mvc(model-view-controller)框架,凭借teams和roles的面向Aspect能力优秀地完成了对这三部分(model、view和controller)的分离。
请注意,这些部署组件都没有安全层设计。
安全需求:
安全组件用于通过用户认证和授权提供访问控制。这里假设采用存储登录名和密码的简单用户管理。授权和认证的功能需求将在下面进行描述。授权依赖于认证,如果只需要认证,被认证的用户将具有对应用的完全控制。
对于认证的最小化需求是login特性:如何执行登录,比如通过用户接口或是操作系统用户识别的方式实现。login特性并不意味着需要 logout。在最简化的环境下的logout就是关闭应用程序。而一个可选的logout特性是支持明确地从系统登出。在登出后,一个新用户或是同一用 户可以登入。timeout也可作为自动logout的一种方式:在一个可配置的时间段后没有用户交互的自动登出。logout和timeout都需要 login特性,而logout和timeout彼此并不需要,但如果缺少logout,那么意味着使用timeout。如果没有明确的logout, timeout将关闭应用。但如果login采用操作系统的用户识别技术,则可能它无法为logout和timeout提供支持。
授权对不同用户和不同访问权限进行区分。它作用于业务数据对象和工作流。
安全设计问题:
图-2定义安全组件环境的Teams
此案例认证部分的通用解决方案由四个模块组成,每个模块采用team概念进行封装:
1.LoginManagerTeam:是其它team的基础。它管理对登陆的处理。
2.LogoutOptionTeam、TimeoutOptionTeam:是采用team实现的两个可选特性。
3.AuthorizationTeam:实现了授权功能。
这些team是抽象的,它们只实现了安全组件的基本功能。它们能相互独立地去适配具体系统。
LogoutOptionTeam和TimeoutOptionTeam依赖于LoginManagerTeam,比如它们需要 login信息。LoginManagerTeam则独立于这两个team,而且不必将它的内部结构公开。通过“playedBy”关系, LogoutOptionTeam和TimeoutOptionTeam能访问LoginManagerTeam的那些没有明确可见的特性,并在需要时去 适配LoginManagerTeam的行为。LoginManagerTeam被设计为与其它team无关。相同的, AuthorizationTeam也依赖LoginManagerTeam,但并非反之亦然。
这些可选项(logout和timeout)之间是独立的,如果它们中的一个没有相应的subteam(指这个team的子类)或是相应 的subteam没有激活,那么这个可选team就是无效的。team概念甚至允许在运行时激活或者冻结可选项:比如,一个依赖用户的(user- dependent)timeout通过冻结或激活某个用户的timeout的方式轻松实现。
这些需求导致了安全组件的抽象解决方案成为了一种类似框架的设计。抽象实现应该是足够灵活以适合不同的系统。而实现具体层次和适应底层组 件的策略在系统之间的差异万千。最后,我们针对应用在部署组件的安全解决方案实现了team的三步改进:通用解决方案、具体部署解决方案、依赖变体(面向 对象或者面向Aspect两种方式的实现)的解决方案。在改进抽象安全解决方案的过程中,我们已经发现了一套
设计模式,它们可以在不同的具体team层次上进行灵活的改进。
重用Aspect的模式:
在本章节将提供一些在实现安全案例研究时的模式。而另一个模式将在实现可行性研究时介绍。所有这些模式构成了一个通用解决方案:实现一个 抽象team、一个从通用解决方案继承和改进而来的特定解决方案。在下面,我们在抽象层调用这个通用解决方案,在具体层继承和改进这个方案。
依赖性激活:
图-3 依赖性激活
不同的team模块在它们被初始化和激活之前,在它们所需要建立的环境(context)的过程中存在着不同之处:某些模块在程序启动时被激活,而另一些team这需要预先执行适当的初始化。
于是,针对这种状况,一种能灵活支持多种依赖的解决方案现身了:
在这个安全案例研究中,上述问题发生在实现安全管理器(security manager)时。这个安全管理器作为一个team,激活所有安全相关的team使它们为部署组件提供安全。在被分别被两位工程师开发的情况下,每个部 署的变体都有自己的启动机制。我们希望针对这两个变体(由开发者确定何时激活被安全管理器所管理的team)在抽象层上制定用统一、通用的解决方案。
这个问题的解决方案展示在图中。在抽象层,初始化方法:activateTeams——被引入到team中。另外,一个role:AppAdapter——具有一个调用初始化方法的方法。注意:我们的安全管理器是一个team,而并非Java安全管理器。
在具体实现层,我们有两种改进superteam的方法:
1.直接实现初始化方法,图中显示在AOSecurityManager team中。就像在例子中那样,通常情况下,这个初始化方法将被team的构造方法所调用。这个team的构造方法将作为“钩子”被钩放到应用的启动机制中。
2.AppAdapter role被解放出来。在本例中,OOSecurityManager team将其所包含的role绑定到base系统的一个类,使初始化方法通过callin绑定的方式被间接调用。
3.在本例中,AppAdapter role被绑定到base类OODisposition,activateTeams方法通过拦截base类的方法start的方式调用 initialize方法。这样,OOSecurityManager的实例在应用启动时被构造并激活。
特性选择:
图-4 特性选择
第二个模式类似设计
代码库的状况:一个模块实现了一套丰富功能,而应用仅能使用其中一部分。但是,在这个重用Aspect的案例中没有提供通过明确方法调用来选择特性的主程序。
在我们的安全案例研究中,我们在为了支持某个logout操作而适配某个部署GUI时发现这一情况:不同元素(比如菜单项或者logout按钮)需要被添加到GUI中。这里存在着两种可能的实现:在抽象层实现和在具体的logout team实现。
这个问题在Object Team中很容易被解决。抽象team实现所有的功能:增加按钮和菜单项。而subteam:LogoutOptionTeam可以自由地选择绑定该使用 的方法。例如:callin绑定将只导致按钮为添加到GUI。因为addMenuItem方法是未经绑定的,所以它将不被调用。
这里存在着部分绑定role行为所带来的好处:所有的行为在抽象层实现,实现对在bese系统进行具体适配的抽象类的使用可以通过简单地 绑定方法或者不绑定方法来进行功能选择。如果使用全部功能,则role方法将被同一的base方法绑定(本例中的add方法被initializeGUI 方法绑定)。
统一role访问:
图-5 统一role访问
这便是依靠实现抽象接口的面向对象风格与明确引用接口的实现相比之下的更妙之处。使用Object Teams,可以通过两种不同方式提供在抽象接口后面的实现:通过直接实现功能或者使用callout绑定方式委托调用给被适配的base对象。基于直接 实现和委托抽象role方法这两种选择的比较,后者不但能将如何实现恰当的行为进行抽象,而且将此行为被实现的位置进行抽象。在这种抽象role的可以进 行是完全自由地选择或者结合这两种技术来实现其行为。
上述两种行为能够以一种统一的方式被访问到。图-5展现了我们在安全案例中是如何统一访问role行为的。 LogoutOptionTeam负责logout选项。ViewAdapter作为这个team的role,负责为指定的部署视图添加按钮。为了实现添 加按钮的功能,一套helper方法被抽象地声明在superrole中,并且在subroles中被改进。例子中展示了这些方法中的两个: getRootPane方法返回部署视图的root pane,getRessources方法返回被用到的资源包。这两个方法被定义在同一个role中,并且能在role实例中被调用,只是其行为不同。
第一个方法getRootPane是被委托到返回root pane的被适配的视图对象。第二个方法则完全在role中实现,不需要来自base对象的信息。
统一role的访问将role方法的调用者从如何提供实现的设计烦恼中解放了出来。具体层的开发者能决定是否在role中实现接口还是委托到base对象。这两种实现接口的方式也可以被混合使用。
提醒者roles(Reminder roles):
当我们实现了抽象层,我们不得不决定如何处理那些可能访问某个指定team的环境,但却没有功能或者没有在抽象层的参考。如果我们不给出 在抽象team中的一个提示,那么具体层的开发者将忘记或者对role产生困惑并相信这种role是不重要的。或者,我们能实现这样的空白role,但在 本例中,我们不得不解释一下:这种role没有行为和依赖。
我们建议选择后者,我们称这些空白的role为“提醒者roles”。它们的目的是提醒开发者:这里存在一些需要在具体层实现的 role。提醒者role是普通的、空白的role。如果我们想在subteam中强制实现它们,那么就需要声明它们为抽象并且尽可能地具体化提醒者 role,使得subteam中无需再实现。这里我们发现提醒者role并不是一种技术而是一种有方法论的解决方案。
六、重用的可行性研究:
同时,一个与安全案例相独立的、用于测试Aspect重用可行性的比较性案例研究正在进行着。这个研究分享了一些安全案例中的基本推断。 比如:使用TOPPrax的部署系统作为研究的base应用、着眼于安全Aspect。但与本文中的安全案例不同的是它的范围和目标。作为一种可用性研 究,其着眼点更偏重于探索而不是如何交付产品。通过可行性研究,我们评估概念性和技术性的成熟度,尤其是在base应用中的Aspect重用。一些在语言 和工具上的提升已经通过这个研究被模拟出来。在这一研究的过程中,一套抽象的Aspect被设计和实现。这一问题域包括:如何在没有传播用户周围环境信息 功能的base应用中提供此信息、在base应用中实现统一访问的会话Aspect。这个Aspect通过基本认证和授权的Aspect来完成。
控制roles:
在设计可用性研究的Aspect集合时,需求导致允许Aspect被描述为两个不同方向:
1.在相互协作的Aspect之间的耦合。
2.对具体base应用的适配。
为了到达初始状态的解耦,Aspect进行协作,并没有削弱它们被部署到具体环境中的能力,这样一种基于模式的设计——我们称之为“控制 roles”(controller roles)被引入。控制role是绑定到抽象控制器类(controller class)上的role类,这个控制器类声明了反映team基本特性的方法。
图-6 控制role行为示意图
控制role包含绑定到控制器类方法的方法,这些方法托管了到相应team方法的调用。注意这里没有直接在多个实体之间的连接,从而去除 了在程序启动时对交换引用(reference)的需要。同时这也意味着在Control Marker的子类实例中所有被继承方法都将被激活的team实例所拦截。这对于多个team来讲可要小心。这样,使用具体的Control Marker子类和Aspect进行相互联接,而将team的子类留给在具体的base应用中用来部署Aspect的做法成为了可能。
控制role模式是安全Aspect的可行性设计和实现的核心。当这些Aspect不是被明确地初始耦合在一起时,此时设计被允许在重用 模块中实现Aspect之间的通讯。这样的系统是被限制的而不能看作是框架,但它展现了Aspect在问题域上的相互