AOP(面向方面的编程) 的一些入门资料

面向方面的编程

http://www.microsoft.com/china/msdn/library/langtool/vbnet/AOPArticle.mspx?pf=true

发布日期: 12/26/2005 | 更新日期: 12/26/2005

Matthew Deiters
ThoughtWorks

适用于:
Microsoft Visual Studio
Microsoft Visual Basic

摘要:从实际应用的角度考察面向方面的编程,说明如何动态扩展 Web 服务客户端应用程序中的行为。

单击此处可下载本文的代码示例。

*
本页内容
简介 简介
什么是方面? 什么是方面?
AOP:利与弊 AOP:利与弊
超越 Trace.WriteLine 超越 Trace.WriteLine
混入的后台实现 混入的后台实现
组合行为 组合行为
AOP 走了多远? AOP 走了多远?
小结 小结

简介

面向方面的编程 (AOP) 由来已久,但是直到最近才开始获得 Microsoft .NET 开发社区的青睐。任何一项新技术的采纳往往都会产生对该技术及其使用的误解,AOP 也不例外。为了澄清对 AOP 的误解,本文以及下列代码示例将举例说明一个 AOP 的实际应用程序和一些 AOP 能够解决的常见问题。以使用 Web 服务的应用程序为例,我们将扩展该 Web 服务返回的对象功能,方法是通过一个 AOP 框架对返回的对象应用新的方面。这些方面将为此功能独立生成对象模型,从而脱离 WSDL。

什么是方面?

在考虑对象及对象与其他对象的关系时,我们通常会想到继承这个术语。例如,定义某一个抽象类 — Dog 类。在标识相似的一些类但每个类又有各自的独特行为时,通常使用继承来扩展功能。举例来说,如果标识了 Poodle,则可以说一个 Poodle 是一个 Dog,即 Poodle 继承了 Dog。到此为止都似乎不错,但是如果定义另一个以后标识为 Obedient Dog 的独特行为又会怎样呢?当然,不是所有的 Dogs 都很驯服,所以 Dog 类不能包含 obedience 行为。此外,如果要创建从 Dog 继承的 Obedient Dog 类,那么 Poodle 放在这个层次结构中的哪个位置合适呢?Poodle 是一个 Dog,但是 Poodle 不一定 obedient;那么 Poodle 是继承于 Dog 还是 Obedient Dog 呢?都不是,我们可以将驯服看作一个方面,将其应用到任何一类驯服的 Dog,我们反对以不恰当的方式强制将该行为放在 Dog 层次结构中。

在软件术语中,面向方面的编程能够独立于任何继承层次结构而应用改变类或对象行为的方面。然后,在运行时或编译时应用这些方面。举一个关于 AOP 的示例,然后进行描述,说明起来比较容易。首先,定义四个关键的 AOP 术语,这很重要,因为我将反复使用它们:

接合点 (Joinpoint) — 代码中定义明确的可识别的点。

切点 (Pointcut) — 通过配置或编码指定接合点的一种方法。

通知 (Advice) — 表示需要执行交叉切割动作的一种方法

混入 (Mixin) — 通过将一个类的实例混入目标类的实例引入新行为。

为了更好地理解这些术语,可以将接合点看作程序流中定义好的一点。说明接合点的一个很好的示例是:在代码调用一个方法时,发生调用的那一点被认为是一个接合点。切点用于指定或定义希望在程序流中截获的接合点。切点还包含一个通知,该通知在到达接合点时发生。因此,如果在一个调用的特定方法上定义一个切点,那么在调用该方法或接合点时,AOP 框架将截获该切点,同时还将执行切点的通知。通知有几种类型,但是最常见的情况是将其看作要调用的另一个方法。在调用一个带有切点的方法时,要执行的通知将是另一个要调用的方法。要调用的这个通知或方法可以是对象中被截获的方法,也可以是混入的另一个对象中的方法。我们将在后面进一步解释混入。

AOP:利与弊

一种常见的误解是认为 AOP 是截获,事实并非如此。但是,它确实运用了截获来应用通知以及组合行为。有一些 .NET 代码示例通过 ContextBoundObject 以一种 AOP 翻版风格说明截获。可是用 ContextBoundObject 来说明截获并不合适,因为使用这种方法的先决条件是所有需要进行截获的类都必须从 ContextBoundObject 继承。像 ContextBoundObject 这样带有先决条件的 AOP 方法会带来需求产生的负面影响,所以在 AOP 中被视为重方法,应该避免使用。重方法在系统中遗留的大量“足迹”会潜在地影响每个类,阻碍将来更改或修改系统的功能。

我创建了一个名为 Encase 的轻量型框架。用“轻量型”这个术语的意义是整体上对系统没有影响。系统的不同部分仍然受 AOP 影响,但是选择轻量型框架并应用良好的编程实践可以减轻大部分负面问题。Encase 框架的用途是简化切点、混入和方面组合。开发人员能够通过代码在 Encase 中应用方面,从而代替大多数其他轻量型 AOP 框架使用的配置文件(例如 XML)。

重量型框架阻碍了 AOP 的应用,但是防碍 AOP 广泛应用的罪魁祸首是目前可用的 AOP 示例几乎都都包含以下内容:执行方法前先截获,并应用执行 Trace.WriteLine("Method entered.") 的方面。与普遍看法相反,除了日志记录、安全、规范以及这类性质的事情外,AOP 对于解决其他问题也很有用。

超越 Trace.WriteLine

为了说明更实用的使用 AOP 的方法,我们将创建一个应用程序,从名为 ContactService.Service 的 Web 服务接收 people 对象的集合。目前,在 .NET 开发中使用 Web 服务的最常见方法是调用返回 XML 的 Web 服务,该服务通过框架自动反序列化为一个对象。这些对象仅包含数据而不包含任何行为。在 .NET Framework 2.0 中,通过使用 partial 关键字并创建行为,能够对这些自动代码生成的对象添加功能。但是在一些 Web 服务或代理对象之间重用某个特定行为时仍然存在一个问题。如前所述,多数情况下,共享的公共行为将包含在一个抽象类中,其他所有类从该类继承。但是,我们不能使 Web 服务对象继承功能。借此良机,通过这个问题说明 AOP 功能如何强大。

我们的应用程序用于显示联系人信息。最初它的用途是显示信息,但是现在需要添加某些行为。为了查看代码示例,我们需要创建一个称为 TheAgileDeveloper.ContactService 的虚拟目录。该目录必须指向 TheAgileDeveloper.ContactService 项目在本地计算机上的位置。

通过 http://localhost/TheAgileDeveloper.ContactService 可以访问此项目,这一点很重要。

AOP(面向方面的编程) 的一些入门资料

1. 应用程序屏幕快照。

应用程序有一个视图,它是一个名为 MainForm 的 WinForm,用于显示左侧 ListView 中 Web 服务返回的联系人对象。选定一个联系人时,名字、姓氏和 Web 页将显示在右侧的文本框中。载入 MainForm 时,它调用 ServiceManager 类来获取联系人信息。下列 ServiceManager 类乍看起来似乎没有添加任何值,只不过在窗体和 Web 服务之间添加了另一层。但是,它的价值就在于提供了一个在 Web 服务中添加新功能的位置,而不用重复代码。另一个优点是,它将 Web 服务的“足迹”抽象出来,并从整个应用程序中移除出去。

Public Class ServiceManager

    Public Shared Function GetAllContacts() As ContactService.Contact()
        Dim service As ContactService.Service = New ContactService.Service
        Dim contacts() As ContactService.Contact = service.GetAllContacts
        Return contacts
    End Function

    Public Shared Sub SaveContact(ByVal contact As ContactService.Contact)
        Dim service As ContactService.Service = New ContactService.Service
        service.SaveContact(contact)
    End Sub

End Class

请查看 TheAgileDeveloper.Client 项目中的 Reference.vb 文件。它是在导入 ContactService 的 Web 引用时通过 wsdl.exe 创建的。它从 WSDL 自动生成以下 Contact 类。

'<remarks/>
    <System.Xml.Serialization.XmlTypeAttribute(_  
[Namespace]:=http://tempuri.org/TheAgileDeveloper.ContactService/Service1 _ )>  _
    Public Class Contact
        
        '<remarks/>
        Public Id As Integer
        
        '<remarks/>
        Public FirstName As String
        
        '<remarks/>
        Public LastName As String
        
        '<remarks/>
        Public WebSite As String
    End Class

注意,Contact 对象目前只处理数据,而且我们不想以任何方式编辑该代码,因为 wsdl.exe 会为我们自动生成,所以下一次生成时更改将丢失。我想引入行为,这样就能够通过调用名为 Save 的方法保存对象,这很容易通过一个混入 来完成。混入 是多继承的翻版,只是它有局限性,例如只能混入接口实现。我们使用的 Encase 框架包含一个 Encaser 类,它负责接收并包装一个对象。包装对象的行为实际上意味着创建新的对象,在本例中就是新的 Contact 对象,它包含配置的混入和切点。

为了创建允许在 Contact 对象上调用 Save 方法的混入,需要指定一个接口,我称之为 ISavable。实际混入对象的就是 ISavable 接口。我们需要在另一个称为 ContactSave 的新类中实现该接口。

Public Interface ISaveable
    Sub Save()
End Interface

Public Class ContactSave
    Implements ISavable

    Public Contact As ContactService.Contact

    Public Sub Save() Implements ISavable.Save
        ServiceManager.SaveContact(Me.Contact)
    End Sub

End Class

在我们的应用程序中,混入 Contact 对象中 ContactSave 实现的适当位置是 ServiceManager。我们能够混入这个行为,但是不更改任何客户端代码(即,MainForm),因为应用混入后,结合 ContactContactSave 的新 Contact 对象仍然保持为最初的 Contact 类型。以下代码是经过更改的 ServiceManager 的 GetAllContacts 方法,它处理混入行为。

Public Shared Function GetAllContacts() As ContactService.Contact()
        Dim service As ContactService.Service = New ContactService.Service
        Dim contacts() As ContactService.Contact = service.GetAllContacts

        '//Wrap each contact object
        For i As Integer = 0 To contacts.Length-1
            '//Create a new instance of the 
'//encaser responsible for wrapping our object
            Dim encaser As encaser = New encaser

            '//Add mixin instance of ContactSave
            Dim saver As ContactSave = New ContactSave
            encaser.AddMixin(saver)

            '//Creates a new object with 
'//Contact and ContactSave implementations
            Dim wrappedObject As Object = encaser.Wrap(contacts(i))

            '//Assign our new wrapped contact object 
'//to the previous contact object
contacts(i) = DirectCast(wrappedObject, _  
ContactService.Contact) 
'//Notice the wrapped object is still the same type

            '//Assign the new wrapped Contact object to 
'//target field of the ContactSave mixed in
            saver.Target = contacts(i)
        Next

        Return contacts
    End Function

混入的后台实现

每个框架应用切点、通知或方面的方法都是独特的,但是其目的和概念是相同的。在本文示例中,Encaser 包装一个对象时真正进行的操作是,通过 System.Reflection.Emit 命名空间中的类产生 MSIL 代码,从而随时创建新的 Contact 类型。新 Contact 类型派生于 Contact 类,它仍然共享类型,但是新包装的对象还持有对 ContactSave 对象的引用,后者是我们混入的。ISavable.Save 方法在新的 Contact 对象上实现,因此在调用 Save 时,它实际上将调用委托给混入的 ContactSave 对象。这样做的优点是能够将新的 Contact 对象转换为在任何混入对象上实现的任何接口。

AOP(面向方面的编程) 的一些入门资料

2. 包装对象的 UML 图表。

您或许在想,通过 .NET Framework 2.0 的部分类语言功能,可以在另一个 partial 类中添加 Save 行为。这是可能实现的,但是本文没有采用这种方法,这是为了使代码与 .NET Framework 1.x 的其他版本向后兼容。既然有部分语言功能,那么在正常情况下,前面的示例也就不需要使用混入 了。但是混入 仍然很有价值,因为通过它,开发人员可以混入可重用的对象行为,这些对象可以源自其他不相关的对象层次结构,它实现的功能比 partial 类更多。在使用 partial 关键字时,是在同一个类或类型中添加代码,只不过物理位置不同。下一个混入示例说明的添加行为不只特定于 Contact 类,而是一个名为 FieldUndoer 的可重用类。FieldUndoer 实现了 IUndoable 接口,允许已修改的对象恢复为原来的状态。

    Public Interface IUndoable
        ReadOnly Property HasChanges() As Boolean
        Sub Undo()
        Sub AcceptChanges()
    End Interface

HasChanges 属性表示,如果发生了更改,Undo 将对象恢复为原来的状态,AcceptChanges 接收对象的当前更改,因此任何时候再调用 Undo 时都会恢复为上一次接收更改的状态。如果该接口是在一个部分类中实现的,那么在每个希望包含该行为的类中,都必须不厌其烦地重复实现这三个方法。作为一个实用主义编程人员,我尝试坚持“一次且仅一次代码”原则,所以我永远不想重复任何代码,复制和粘贴越少越好。通过使用混入,我能够重用实现 IUndoableFieldUndoer 对象。在 ServiceManager 中我又混入了这个新功能。所有客户端代码仍然不知道新的混入,而且也不需要更改,除非需要使用 IUndoable 接口。更改 MainForm 中的 Contact 对象,然后单击“撤消”,测试这个行为。

Public Shared Function GetAllContacts() As ContactService.Contact()
        Dim service As ContactService.Service = New ContactService.Service
        Dim contacts() As ContactService.Contact = service.GetAllContacts

        '//Wrap each contact object
        For i As Integer = 0 To contacts.Length-1
            '//Create a new instance of the encaser 
'//responsible for wrapping our object
            Dim encaser As encaser = New encaser

            '//Add mixin instance of ContactSave
            Dim saver As ContactSave = New ContactSave
            encaser.AddMixin(saver)

            '//Add mixin instance of FieldUndoer
            Dim undoer As FieldUndoer = New FieldUndoer
            encaser.AddMixin(undoer)

            '//Creates a new object with Contact 
'//and ContactSave implementations
            Dim wrappedObject As Object = encaser.Wrap(contacts(i))

            '//Assign our new wrapped contact object 
'//to the previous contact object
contacts(i) = DirectCast(wrappedObject, _ 
ContactService.Contact) 
'//Notice the wrapped object is still the same type

            '//Assign the new wrapped Contact object to target fields 
            saver.Target = contacts(i)
            undoer.Target = contacts(i)
        Next

        Return contacts
End Function

组合行为

混入还只是冰山一角。真正让 AOP 声名鹊起的功能是组合混入行为。以使用新 Contact 对象为例,在调用 ISavable.Save 方法时,客户端代码还需要调用 IUndoable.AcceptChanges 方法,以便在下一次调用 IUndoable.Undo 时恢复到所保存的上一次更改。在这个小的 MainForm 中浏览和添加该对象很容易,但是在任何比用户界面大得多的系统中对该规则编码将是一项繁重的任务。您需要查找所有调用 Save 方法的情况,然后添加另一个对 AcceptChanges 的调用。而且在创建新代码的过程中,开发人员也需要牢记,在每次调用 Save 时都添加这个新功能。这很快就会产生级联效应,很容易会破坏系统稳定姓,引入一些难于跟踪的 bug。而使用面向方面的编程则能够组合这些方法。指定一个切点和通知,在调用 Save 方法时,Contact 对象将自动调用后台的 AcceptChanges

为了在应用程序中实现组合,需要在 ServiceManager 中再添加一行代码。我们在加入 FieldUndoer 混入后添加这行代码。

'//Specify join point save, execute the AcceptChanges method
encaser.AddPointcut("Save", "AcceptChanges") 

AddPointcut 方法通过几个不同的签名进行重载,这为指定切点提供了更大的灵活性。我们调用的 AddPointcut 接收了一个字符串类型的接合点名,它表示为 Save 方法,然后又接收了一个名为 AcceptChanges 的方法作为执行的通知。要查看这是否起作用,可以分别在 FieldUndoer.AcceptChanges 方法和 ContactSave.Save 方法前设置一个断点。单击 MainForm 上的 Save 按钮将截获接合点,您首先将中断至通知 — 即 AcceptChanges 方法。通知执行后将执行 Save 方法。

这个简单的示例说明如何添加贯穿整个应用程序的新行为,其功能强大无比。尽管有此功能,但它不仅仅是添加功能的一种很好的新方法。在众多优点中,只有几个涉及代码重用,以及通过简化新需求带来的系统进化来改进系统的可维护性。与此同时,误用 AOP 会对系统的可维护性造成显著的负面效应,因此了解使用 AOP 的时机和方法很重要。

AOP 走了多远?

将 AOP 用于多数大型系统或关键的生产系统还不完全成熟,但是随着语言支持的提高,AOP 的应用将更容易。另外,提高支持也是新的软件开发范例,例如利用面向方面的编程的软件工厂。目前在 .NET 领域中有几种可用的 AOP 框架,每个框架都有其自己的方法、正面属性和负面属性。

Encase — 本代码示例中的 Encase 框架只是一个工具,帮助您快速了解并运行 AOP,以及理解 AOP 背后的概念。Encase 在运行时期间应用能够单独添加到对象的方面。

Aspect# — 一个针对 CLI 的 AOP 联合兼容框架,提供声明和配置方面的内置语言。

RAIL — RAIL 框架在虚拟机 JIT 类时应用方面。

Spring.NET — 流行的 Java Spring 框架的一个 .NET 版本。在下一个版本中将实现 AOP。

Eos — 用于 C# 的一个面向方面的扩展。

小结

本文的目的是说明一种比常规日志记录或安全实例更实用的应用 AOP 的新方法。正确应用使用 AOP 会带来很多优点,甚至能够帮助您完成常规编程选项所不能完成的成果任务。我强烈推荐您在 internet 上搜寻大量可用资源,以指导应用 AOP 的方法和场景时机。

关于作者

Matthew Deiters 对于软件开发工作充满热情,他是 ThoughtWorks 的一名咨询人员。他曾协助通过 .NET Framework 开发一些针对金融和保险行业的企业级系统。他看重 XP 编程和 TTD 方法论,认为大多数人为问题能够通过设计模式和/或良好的单元测试解决。您可以通过 Matthew 的个人 Web 空间与他联系:

--------------------------------------------

面向方面编程和JBoss之一

http://www.matrix.org.cn/resource/article/0/439.html

chris 发表于 2003-08-26 10:07:00
作者:neo 评论数:0 点击数:2,404 投票总得分:0 投票总人次:0
关键字:
<!-- end of div title --> <!-- end of summary line -->
概述
面向方面编程(Aspect-Oriented Programming, AOP)是一个令人兴奋的新模式。就开发软件系统而言,它的影响力将会和有15到20年的面向对象一样。面向方面编程和面向对象编程不但不是互相竞争的技术而且是可以很好的互补。面向对象编程主要用于为同一对象层次的公用行为建模。它的弱点是将公共行为应用于多个无关对象模型之间。而这恰恰是AOP适合的地方。AOP允许定义交叉的关系,那些关系应用于跨国分开的,非常不同的对象模型。AOP允许你层次化功能性而不是嵌入功能性,那使得代码有更好的可度性和易于维护性。我喜欢认为OOP是自上而下的软件开发,而AOP是自左而右的软件开发,它们是完全直交的技术,并且互相很好的补充。
在OOP的工具里是继承,封装和多态,而AOP的组件是通知/拦截器,导言,元数据和pintcuts.让我们看一下这些定义。

通知/拦截器
一个通知是一个逻辑,这个逻辑有特定的事件触发。它是行为,这个行为能够被插入在调用者和被调用者之间,在一个方法调用者和实际的方法之间。通知是AOP真正的关键。通知允许你去透明的应用一些事物,像日志和记录到一个存在的对象模型。
在 JBoss AOP中,我们用拦截器是实现了通知。你能够定义拦截器,它拦截方法调用,构造器调用和域访问。后面,我们将阐明怎样应用这些拦截器到一个存在的对象模型。

导言
导言是一个增加方法或者域到一个存在的类中的途径。它们甚至允许你改变当前存在的类是显的接口,并且引入一个混合的类,这个类是实现了新的接口。导言允许你带入多继承到一般的Java类。导言一个主要的用例是当你有一个方面,你想让这个方面有一个运行时间借口时。你想应用你的方面跨越不同的对象层次,但是你仍然要应用开发者去能够调用特定方面的APIs.
Apple apple= new Apple();
LoggingAPI logging = (LoggingAPI)apple;
Apple.setLoggingLevel(VERBOSE);
导言能够是一个方法,它将一个新的API绑定到一个存在的对象模型。

元数据
元数据是能够绑定到一个类的附加信息,在静态或者运行时间。元数据更加有力力量的是,你能够动态绑定元数据到一个给定的对象实例。元数据非常强大的,当你真正编写应用于任何对象的一般方面,而逻辑需要知道制定类的信息时。在使用的一个好的元数据类比就是EJB规范。在EJB的XML发布描述符中,你需要定义基于每一个方法的事务属性。应用服务器指导什么时候,什么地方开始,挂起或者提交一个事务,因为你在BEAN的XML的配置文件中的元数据内已经定义如方法:Required,RequiresNew,Support等等,它们绑定在你的EJB类和事务管理之间。
C#把元数据成为了这个语言的组成部分。XDoclet是另一个动作的元数据的例子。如果你曾经用过XDoclet生成过EJB文件和发布描述符,你就会知道元数据的力量。在JDK1.5 中,当元数据被加入java语言中,JCP一致同意。(见JSR175)。尽管直到JSR175成为了事实,一个好的AOP框架也应该提供一种机制去定义在运行时间有效的类级元数据。

Pointcuts
如果拦截器,导言和元数据是AOP的特征,那么pointcuts就是粘合剂。Pointcuts告诉AOP框架,那些拦截器绑定到那些类, 什么原数据将应用于那些类或者那一个导言将被传入那些类。Pointcuts定义各种AOP特征将怎样应用于你应用中的类。

在动作中的AOP
例1.使用拦截器
JBoss 4.0带了一个AOP框架。这个框架和JBoss应用服务器紧密地结合,但是你也能够在你的应用中,单独的运行它。直到你看了动作中看到它,你才会完全的理解这个概念,所以让我们用一个来自于JBoss AOP的例子,来说明这个模块所有的部分是如何一起工作的。在这章余下的部分,我们将建立一个例子来跟踪使用AOP的框架。

定义一个拦截器
为了实现我们对于框架的跟踪,我们必须作的第一件事是定义一个拦截器,它将作实际的工作。在JBOSS AOP中,所有的拦截器必须实现org.jboss.aop.Interceptor 接口。
public interface Interceptor
{
public String getName();
public InvocationResponse invoke(Invocation invocation) throws Throwable;
}

在JBoss  AOP中,被拦截的所有域,构造器和方法被转成一般的invoke调用。方法的参数被填入一个Invocation对象,并且方法的返回值,域的存取或者构造器被填入一个InvocationResponse对象。这个Invocation对象也驱动这个拦截链。为了清楚地说明这个,让我们看一下,在这个例子中,所有的对象是如何配合到一起的。
import org.jboss.aop.*;
import java.lang.reflect.*;

public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String message = null;

if (invocation.getType() == InvocationType.METHOD)
{
Method method = MethodInvocation.getMethod(invocation);
message = method:+ method.getName();
}
else if (invocation.getType() == InvocationType.CONSTRUCTOR)
{
Constructor c = ConstructorInvocation.getConstructor(invocation);
message = constructor:+ c.toString();
}
else
{
// Do nothing for fields.Just too verbose.
//对于域什么也不做。太繁琐。
return invocation.invokeNext();
}

System.out.println(Entering+ message);

// Continue on.Invoke the real method or constructor.
// 继续。调用真正的方法或者构造器
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving+ message);
return rsp;
}
}

上面的拦截器将拦截所有的对一个域,构造器或方法的调用。如果调用的类型是一个方法或者构造器,一个带有方法或构造器签名的消息将输出到控制平台。
绑定拦截器
好了,这样我们就定义了拦截器。但是怎么绑定这个拦截器到实际的类?为了做这个,我们需要定义一个pointcut。对于JBoss AOP, pointcuts 是在一个XML文件中定义的。让我们看一下这看起来象什么。

<?xml version="1.0" encoding="UTF-8">
<aop>
<interceptor-pointcut class="POJO">
<interceptors>
<interceptor class="TracingInterceptor" />
</interceptors>
</interceptor-pointcut>
</aop>

上面的pointcut绑定TracingInterceptor到一个叫做POJO的类。这看起来有一点麻烦;我们不得不为每一个想跟踪的类创建一个 pointcut吗?幸运的是,interceptor-pointcut的类属性可以用任何的正规表达式。所以如果你想跟踪由JVM载入的类,类表达式将变为 .*。如果你仅仅想跟踪一个特定的包,那么表达式将是com.acme.mypackge.*。
当单独运行JBoss AOP时,任何符合 META-INF/jboss-aop.xml模式的XML文件将被JBoss AOP 运行时间所载入。如果相关的路径被包含在任何JAR或你的CLASSPATH的目录中,那个特定的XML文件将在启动时,由JBoss AOP 运行时间所载入。

运行这个例子
我们将用上面定义的pointcut去运行例子。POJO类看起来如下:

public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
}
}

TracingInterceptor将拦截对main(),POJO()和helloWorld()的调用。输出看起来如下:
Entering method: main
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Leaving method: main


你能够在 这里
下载JBoss AOP和离子代码。编译和执行:

$ cd oreilly-aop/example1
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO

JBoss AOP 对绑定的拦截器做字节码操作。因为没有编译步骤,AOP运行时间必须有ClassLoader的总控。如果你正运行在非JBoss应用服务器,你必须用JBoss制定的一个类载入器覆盖系统的类载入器。
TraceingInterceptor 不跟踪域访问,因为它有一点繁琐。对于开发者,实现get()和set()方法去封装域访问是一个一般的实践。如果TracingInterceptor 能够过滤出,并且不跟踪这些方法,那是非常好的。这个例子显示你能够用JBoss AOP 元数据去实现基于任一方法的过滤。一般,元数据用于更复杂的事情,如定义事务属性,每个方法的安全角色或者持久性映射,但是这个例子应该足够说明元数据能够怎样用在 AOP使能的应用中。

定义类的元数据
为了增加这个过滤功能,我们将提供一个标志,你能够用这个标着去关闭跟踪。我们将回到我们的AOP的XML文件去定义标签,那将删除对get()和set()方法的跟踪。事实上,对于main()函数的跟踪毫无意义,所以我们也过滤出它。

<?xml version="1.0" encoding="UTF-8">
<aop>
<class-metadata group="tracing" class="POJO">
<method name="(get.*)|(set.*)">
<filter>true</filter>
</method>
<method name="main">
<filter>true</filter>
</method>
</class-metadata>
</aop>

上面的XML定义了一组叫做tracing的属性。这个过滤属性将绑定到每一个以get或者set开始的方法上。正则表达式格式用JDK-1.4定义的表达式。元数据通过Invocation对象,在TracingInterceptor内访问。


matrix开源技术onjava授权翻译并发布.
如果你对此文章有任何看法或建议,请到 Matrix论坛发表您的意见.
注明: 如果对matrix的翻译文章系列感兴趣,请点击 oreilly和javaworld文章翻译计划查看详细情况
您也可以点击- neo 查看翻译作者的详细信息.

翻译-面向方面编程和JBoss之二

http://www.matrix.org.cn/resource/article/0/440.html

chris 发表于 2003-08-26 10:26:00
作者:neo 评论数:0 点击数:1,695 投票总得分:0 投票总人次:0
关键字:
<!-- end of div title --> <!-- end of summary line -->
访问Metadata
为了用元数据,它在运行时间必须是可达的。类的元数据是通过Invocation对象可达的。为了在我们的例子使用它,TracingInterceptor必须要修改一点点。

public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String filter = (String)invocation.getMetaData(tracing, filter);
if (filter != null && filter.equals(true))
return invocation.invokeNext();

String message = null;

if (invocation.getType() == InvocationType.METHOD)
{
Method method = MethodInvocation.getMethod(invocation);
message = method:+ method.getName();
}
else if (invocation.getType() == InvocationType.CONSTRUCTOR)
{
Constructor c = ConstructorInvocation.getConstructor(invocation);
message = constructor:+ c.toString();
}
else
{
// Do nothing for fields.Just too verbose.
return invocation.invokeNext();
}

System.out.println(Entering+ message);

// Continue on.Invoke the real method or constructor.
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving+ message);
return rsp;
}
}

运行例子2


POJO类将扩展一点,增加get()和set()方法。
public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }

private int counter = 0;

public int getCounter() { return counter; }
public void setCounter(int val) { counter = val; }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
pojo.setCounter(32);
System.out.println(counter is:+ pojo.getCounter());
}
}
TracingInterceptor将拦截对main(),POJO()和helloWorld()调用。输出应该看起来如下:
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld


你能够在 这里下载JBoss AOP和离子代码。编译和执行:
$ cd oreilly-aop/example2
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO

例子3.使用导言
如果我们能够为特定的实例关闭和打开,那将很酷。JBoss AOP有一个API,他绑定元数据到一个对象实例,但是让我们伪装一个实际的跟踪API是一个更好的方案。在这例子中,我们通过用一个导言,将改变 POJO类的本身的定义。我们将强制POJO类去实现一个跟踪借口和提供混合类,这个混合类处理新的跟踪API。这将是跟踪借口:

public interface Tracing
{
public void enableTracing();
public void disableTracing();
}

定义一个混合的类
Tracing接口将在混合类中实现。当一个POJO是实例时,一个混合对象混合类将绑定到POJO类。下面是实现:

import org.jboss.aop.Advised;

public class TracingMixin implements Tracing
{
Advised advised;

Public TracingMixin(Object obj)
{
this.advised = (Advised)obj;
}

public void enableTracing()
{
advised._getInstanceAdvisor().getMetaData().addMetaData(
"tracing", "filter", true);
}

public void disableTracing()
{
advised._getInstanceAdvisor().getMetaData().addMetaData(
"tracing", "filter", false);
}
}

enableTracing()方法绑定filter属性到对象实例。在disableTracing()方法作同样的事,但是制定filter属性为false。这两个方法是元数据能够怎么样用于超过一个类级别。元数据也能够实例级的应用。元数据应用在实例级别。

绑定一个导言
好了,所以我们定义跟踪接口,并且实现这个混合类。下一步是应用导言到POJO类。像拦截器,我们必须在XML中定义一个ponitcut。让我们看一下这项什么。

<?xml version="1.0" encoding="UTF-8">
<aop>
<introduction-pointcut class="POJO">
<mixin>
<interfaces>Tracing</interfaces>
<class>TracingMixin</class>
<construction>new TracingMixin(this)</construction>
</mixin>
</introduction-pointcut>
</aop>

上面的pointcuts将强制POJO类实现Tracing接口。现在,当一个POJO实例被初始化,一个TracingMixin也将被实例化。 TracingMixin被初始化的途径被定义在<contstruction>标签中。你能够把想要的任一行Java代码放入在< contstruction>标签中。

运行例子3
POJO类为了显示TracingAPI怎么被访问,它已经被扩展了一点。TracingInterceptor仍然和例子2一样。

public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }

public static void main(String[] args)
{
POJO pojo = new POJO();
Tracing trace = (Tracing)this;
pojo.helloWorld();

System.out.println("Turn off tracing.");

trace.disableTracing();
pojo.helloWorld();

System.out.println("Turn on tracing.");

trace.enableTracing();
pojo.helloWorld();
}
}

注意我们转换POJO到Tracing接口。输出应该看起来这样:

Entering constructor: POJO()
Leaving constructor: POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Turn off tracing.
Entering method: disableTracing
Leaving method: disableTracing
Hello World!
Turn on tracing.
Entering method: helloWorld
Hello World!
Leaving method: helloWorld


注意被增加到TracingInterceptor 中的interceptor-pointcut也应用到那些通过Tracing 导言导入的方法中。
为了编译和运行这个例子:

$ cd oreilly-aop/example3
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO


结论
面向方面编程对于软件开发是一个强有力的新工具。为了使你的软件开发过程更加动态和流畅,用JBoss4.0,你能够实现你自己的拦截器,元数据和导言。更详细的文档参见我们的站点 www.jboss.org。那会有一些惊奇等着你,象我们已经在我们新的框架上实现了一套服务。拥有它并恰当的使用。
Bill Burke JBOssGroup首席架构师,领导JBoss4.0
Adrian Brock是JBossGroup的技术主管

matrix开源技术onjava授权翻译并发布.
如果你对此文章有任何看法或建议,请到 Matrix论坛发表您的意见.
注明: 如果对matrix的翻译文章系列感兴趣,请点击 oreilly和javaworld文章翻译计划查看详细情况
您也可以点击- neo 查看翻译作者的详细信息.

你可能感兴趣的:(AOP)