面向方面的编程

计算中,面向方面的编程AOP )是一种编程范式,旨在通过允许分离交叉切割问题来增加模块化。它是通过在修改代码本身的情况下将行为添加到现有代码(建议)的情况下来的,而是单独指定通过“尖端”规范修改哪个代码,例如“当功能的名称以'set'set '开头时,“ log log所有函数调用”” 。这允许将业务逻辑不核心(例如记录)添加到程序中,而无需将代码核心添加到功能上。

AOP包括编程方法和工具,这些方法和工具支持源代码级别的关注点模块化,而面向方面的软件开发是指整个工程学科。

面向方面的编程需要将程序逻辑分解为不同的部分(所谓的关注点,功能的凝聚力领域)。几乎所有编程范式都通过提供抽象(例如,函数,过程,模块,类,方法)来支持将问题分组和封装到独立的独立实体中,这些实体可用于实施,抽象和构成这些问题。一些担忧“跨越”程序中的多个抽象,并无视这些形式的实施形式。这些问题称为横切问题或横向问题。

日志记录体现了一个横切关注点,因为记录策略必须影响系统的每个记录部分。登录从而横切所有记录的类和方法。

所有AOP实现都有一些横切表达式,可以将每个问题封装在一个地方。实施之间的差异在于所提供结构的权力,安全性和可用性。例如,指定表达有限形式的横切形式的方法的拦截器,而无需大力支持类型安全或调试。 Factionj具有许多此类表达式,并将它们封装在一个称为“方面”的特殊类别中。例如,某个方面可以通过在量化或查询中指定的称为点刻画中指定的各种联接点(程序中)(程序中的点)上应用建议(附加行为)来改变基本代码的行为(程序的非相关部分)(附加行为)(其他行为)(这检测到给定的联接点是否匹配)。一个方面还可以对其他类别(例如增加成员或父母)进行兼容二进制的结构性更改。

历史

AOP有几个直接的先决条件A1和A2:反射元对象协议面向主题的编程,组成过滤器和自适应编程。

Xerox ParcGregor Kiczales及其同事开发了AOP的明确概念,并随之而来的是Java的AOP延伸。 IBM的研究团队通过语言设计方法采用了一种工具方法,并在2001年提出了Hyper/J有关操纵环境,这些环境没有看到广泛的用法。

本文中的示例使用aptivej。

Microsoft Transaction Server被认为是AOP的第一个主要应用程序,其次是Enterprise Javabeans

动力和基本概念

通常,一个方面被分散纠结为代码,使得很难理解和维护。它被函数(例如记录)分散在许多无关的函数上,这些功能可能使用功能,可能是在完全无关的系统中或以不同的源语言编写的功能。因此,更改记录可能需要修改所有受影响的模块。各个方面不仅与它们所表达的系统的主线功能纠缠在一起,而且彼此之间也纠缠在一起。因此,改变一个关注点需要理解所有纠结的问题或具有可以推断变化效果的某种手段。

例如,考虑具有概念上非常简单的方法,将金额从一个帐户转移到另一个帐户:

void transfer(Account fromAcc, Account toAcc, int amount) throws Exception {
  if (fromAcc.getBalance() < amount)
      throw new InsufficientFundsException();
  fromAcc.withdraw(amount);
  toAcc.deposit(amount);
}

但是,这种传输方法忽略了部署应用程序所需的某些考虑因素,例如验证当前用户有权执行此操作,封装数据库交易以防止意外数据丢失,并记录以诊断目的记录操作。

所有这些新问题的版本看起来有些类似:

void transfer(Account fromAcc, Account toAcc, int amount, User user,
    Logger logger, Database database) throws Exception {
  logger.info("Transferring money...");
  
  if (!isUserAuthorised(user, fromAcc)) {
    logger.info("User has no permission.");
    throw new UnauthorisedUserException();
  }
  
  if (fromAcc.getBalance() < amount) {
    logger.info("Insufficient funds.");
    throw new InsufficientFundsException();
  }
  fromAcc.withdraw(amount);
  toAcc.deposit(amount);
  database.commitChanges();  // Atomic operation.
  logger.info("Transaction successful.");
}

在此示例中,其他兴趣已经与基本功能纠结(有时称为业务逻辑问题)。交易,安全性和日志记录所有典型的交叉切割问题

现在,考虑如果我们突然需要更改应用程序的安全考虑,将会发生什么。在该程序的当前版本中,与安全相关的操作似乎散布在许多方法中,这种更改将需要大量的努力。

AOP试图通过允许程序员在称为方面的独立模块中表达横切问题来解决此问题。各个方面可以包含建议(与程序中指定点加入的代码)和类型声明(添加到其他类中的结构成员)。例如,安全模块可以包括在访问银行帐户之前执行安全检查的建议。当人们可以访问银行帐户时,点尺将定义时间(联接点),并且建议主体中的代码定义了如何实现安全检查。这样,检查和位置都可以在一个地方维护。此外,良好的得分可以预期以后的程序更改,因此,如果另一个开发人员创建了访问银行帐户的新方法,则该建议将适用于新方法执行时。

因此,对于上面的示例,在一个方面实施日志记录:

aspect Logger {
  void Bank.transfer(Account fromAcc, Account toAcc, int amount, User user, Logger logger)  {
    logger.info("Transferring money...");
  }
  void Bank.getMoneyBack(User user, int transactionId, Logger logger)  {
    logger.info("User requested money back.");
  }
  // Other crosscutting code.
}

可以将AOP视为调试工具或用户级工具。应保留建议的建议,以使其无法更改功能(用户级别)或不想更改生产代码(调试)中的功能的情况。

加入点模型

面向方面的语言的与建议相关的组件定义了联接点模型(JPM)。 JPM定义了三件事:

  1. 当建议运行时。这些称为联接点,因为它们是运行程序中可以有效地加入其他行为的点。一名普通程序员需要可以解决联合点,才能有用。在无关紧要的程序更改中,它也应保持稳定,以维持方面稳定性。许多AOP实现支持方法执行和字段引用作为联接点。
  2. 一种指定(或量化)联接点的方法,称为点cut 。点数确定给定的联接点是否匹配。最有用的点数语言使用基本语言之类的语法(例如, sextj使用Java签名),并通过命名和组合允许重复使用。
  3. 指定代码以在联接点运行的方法。 etpectJ调用此建议,可以在联接点之前,之后和周围运行它。某些实现还支持在另一个类别上的一个方面定义方法。

可以根据公开的联接点,指定联接点的指定,联接点允许的操作以及可以表达的结构增强功能进行比较。

extackJ的联接点模型

  • AspectJ中的联接点包括方法或构造函数调用或执行,类或对象的初始化,字段读写访问以及异常处理程序。它们不包括循环,超级呼叫,投掷条款或多个语句。
  • 尖端是通过原始点盘指定器(PCD)的组合指定的。

    “友善” PCD匹配一种特定的联接点(例如,方法执行),并且倾向于将类似Java的签名作为输入。这样的点刻有一个这样的点:

     execution(* set*(*))
    

    如果方法名称以“set“而且完全有任何类型的论点。

    “动态” PCD检查运行时类型并绑定变量。例如,

      this(Point)
    

    当当前执行对像是类的实例时Point。请注意,可以通过Java的普通类型查找使用类的不合格名称。

    “范围” PCD限制了联接点的词汇范围。例如:

     within(com.company.*)
    

    该点键匹配任何类型中任何类型的联接点com.company包裹。这*是通配符的一种形式,可用于与许多签名匹配许多东西。

    可以为重复使用而组成和命名点。例如:

     pointcut set() : execution(* set*(*) ) && this(Point) && within(com.company.*);
    
    如果方法名称以“set“ 和this是类型的实例Point在里面com.company包裹。可以转介使用名称”set()".
  • 建议指定在某些代码(在方法中指定的代码)(在方法中指定的代码)(在方法中指定)。当点键匹配连接点时,AOP运行时会自动调用建议。例如:
     after() : set() {
       Display.update();
     }
    
    这有效地指定:“如果set()尖端与联接点匹配,运行代码Display.update()结合点完成后。”

其他潜在的联接点模型

还有其他类型的JPM。所有建议语言都可以根据其JPM来定义。例如, UML的假设方面语言可能具有以下JPM:

  • 联接点都是模型元素。
  • 尖端是结合模型元素的一些布尔表达
  • 在这些点上的情感手段是对所有匹配的联接点的可视化。

式声明

类型的声明提供了一种表达影响模块结构的横切问题的方法。这也称为开放类扩展方法,使程序员能够在另一个班级的一个地方成员或父母声明,通常可以将与关注点有关的所有代码组合在一起。例如,如果程序员使用访问者实现了横切显示更高的关注,则使用访问者模式的Inter-type声明可能会在extackJ中看起来像这样。

  aspect DisplayUpdate {
    void Point.acceptVisitor(Visitor v) {
      v.visit(this);
    }
    // other crosscutting code...
  }

此代码段添加了acceptVisitor方法Point班级。

任何结构性添加都必须与原始类兼容,以便现有类的客户继续运行,除非AOP实施期望始终控制所有客户。

执行

AOP程序可以通过两种不同的方式影响其他程序,具体取决于基本语言和环境:

  1. 制作了一个组合的程序,在原始语言中有效,并且与普通程序与最终解释器无法区分
  2. 最终的解释器或环境已更新以了解和实施AOP功能。

不断变化的环境的困难意味着大多数实施方式通过一种称为编织的程序转换产生兼容的组合程序。 Weaver的一个方面读取面向方面的代码,并与集成的方面生成适当的面向对象的代码。可以通过各种编织方法实现相同的AOP语言,因此,不应从编织实施方面理解语言的语义。只有实施速度及其易于部署的速度受使用的组合方法的影响。

系统可以使用预处理器(AS C ++最初在CFRONT中实现)来实现源级编织,以需要访问程序源文件。但是,Java定义明确的二进制形式使字节编织器可以使用.class-File Form的任何Java程序使用。可以在构建过程中部署字节码编织器,或者如果编织模型为每类,则可以在上课加载过程中进行部署。 AxpectJ始于2001年的源级编织,于2002年提供了人均字体编织编织,并在2005年将ExtiveWerkz集成后提供了高级的加载时间支持。

在运行时结合程序的任何解决方案都必须提供正确分离它们的视图,以维护程序员的隔离模型。 Java对多个源文件的字节码支持使任何调试器都可以逐步介绍源编辑器中正确编织的.class文件。但是,一些第三方分解器无法处理编织代码,因为它们期望Javac生成的代码,而不是所有受支持的字节码表格(另请参见下面的批评)。

部署时间编织提供了另一种方法。这基本上意味着后处理,但是这种编织方法不是修补生成的代码,而是现有类属于现有类,以便通过跨越方法进行修改。即使在运行时,现有的类也没有受到影响,并且在开发过程中可以使用所有现有工具,例如辩论者和探险家。在实施许多Java EE应用程序服务器(例如IBMWebSphere)中,已经证明了一种类似的方法。

术语

在面向方面的编程中使用的标准术语可能包括:

交叉切割问题
即使OO模型中的大多数类都会执行单个特定功能,但它们通常与其他类别共享常见的次要要求。例如,每当线程输入或退出方法时,我们可能希望将日志记录添加到数据访问层中的类,以及UI层中的类。进一步的问题可能与安全控制信息流控制等安全性有关。即使每个类具有非常不同的主要功能,执行次级功能所需的代码通常是相同的。
建议
这是要应用于现有模型的附加代码。在我们的示例中,这是每当线程输入或退出方法时要应用的记录代码。
尖端
这是在需要应用交叉切割问题的应用程序中给出执行点的术语。在我们的示例中,当线程输入方法时,将达到一个尖端,当线程退出方法时,将达到另一个尖端。
方面
尖端和建议的组合被称为一个方面。在上面的示例中,我们通过定义点键并提供正确的建议来为应用程序添加一个记录方面。

与其他编程范式进行比较

面向对象的编程计算反射出现了方面。 AOP语言具有类似于元对象协议的功能,但受到限制。方面与主题混音代表团等编程概念密切相关。使用面向方面的编程范式的其他方法包括构图过滤器Hypresliperes方法。至少从1970年代开始,开发人员一直在使用类似于AOP的实现方法的拦截和调度捕获形式,但是这些语义从未具有交叉弯曲规范在一个地方书写的语义。

设计师已经考虑了实现代码分离的替代方法,例如C#的部分类型,但是这种方法缺乏量化机制,允许使用一个声明性陈述来达到代码的几个联接点。

尽管看起来似乎无关,但是在测试中,使用模拟或存根需要使用AOP技术,例如围绕建议。在这里,协作对像是为了进行测试,这是一个交叉切割问题。因此,各种模拟对象框架提供了这些功能。例如,一个过程调用服务以获得余额。在该过程的测试中,金额来自的位置并不重要,但只有该过程根据要求使用余额。

收养问题

程序员需要能够读取和理解代码以防止错误。即使接受适当的教育,如果不适当地支持静态结构和程序的动态流,也很难理解横切问题。从2002年开始,Jeptj开始提供IDE插件,以支持可视化横切问题的可视化。这些功能以及方面代码辅助和重构现在很常见。

鉴于AOP的力量,在表达横切方面犯了合乎逻辑的错误可能会导致广泛的程序故障。相反,另一个程序员可以通过作者没有预期的方面和不可预见的后果来更改程序中的联合点,例如通过重命名或移动方法来更改程序。模块化横切问题的一个优点是使一个程序员能够轻松地影响整个系统。结果,此类问题表现为两个或多个开发人员之间在给定失败中的责任的冲突。 AOP可以加快解决这些问题的速度,因为只有方面才能更改。没有AOP,相应的问题可能会更加分散。

批评

对AOP效果的最基本批评是控制流程被模糊不清,它不仅比备受关注的Goto陈述更糟,而且与笑话非常相似。应用程序的遗忘性是对AOP的许多定义至关重要的(所讨论的代码没有迹象表明将应用建议,而不是在点曲中指定的建议),这意味着与明确的建议不可见。方法调用。例如,比较来自程序:

 5 INPUT X
10 PRINT 'Result is :'
15 PRINT X
20 COME FROM 10
25      X = X * X
30 RETURN

具有类似语义的AOP片段:

main() {
    input x
    print(result(x))
}
input result(int x) { return x }
around(int x): call(result(int)) && args(x) {
    int temp = proceed(x)
    return temp * temp
}

实际上,点列可能取决于运行时条件,因此在静态上是确定性的。这可以缓解,但不能通过静态分析和IDE支持来解决哪些建议可能匹配。

普遍的批评是,AOP声称改善“模块化和代码结构”,但有些人反对它破坏了这些目标并阻碍了“独立的发展和计划的独立发展和理解力”。具体而言,通过尖端进行量化破坏了模块化:“通常,必须具有全面图知识来推理有关面向方面的程序的动态执行。”此外,尽管其目标(模块化的横切问题)尚不清楚,但其实际定义尚不清楚,并且与其他完善的技术没有明确区分。跨切割问题可能会互相交叉,需要一些分辨率机制,例如订购。确实,方面可以适用于自己,导致骗子悖论等问题。

技术批评包括量化点(定义执行建议的位置)“对程序的变化极为敏感”,这被称为脆弱的点数问题。尖端的问题被认为是棘手的。如果用明确的注释替换了点cut的量化,则可以将其获得面向属性的编程,这只是一个显式的子例程呼叫,并且遭受了AOP设计用于解决的相同散射问题。

实施

以下编程语言在语言中或作为外部库中实现了AOP:

也可以看看