What is AspectJ

转载网址:http://www.ibm.com/developerworks/cn/java/l-aspectJ/index.html

1. 序

Aspect Oriented Programming (AOP) 是近来一个比较热门的话题。

AspectJ 是 AOP 的 Java 语言的实现,获得了 Java 程序员的广泛关注。

关于 AspectJ 和 AOP 的具体资料,请从下列链接中查找:

http://www.eclipse.org/aspectj/
http://www.parc.com/research/csl/projects/aspectj/
http://aosd.net/

网上出现了很多讲解 AspectJ 的资料,但大多是从讲解 AspectJ 语法开始,然后讲解如何应用 AspectJ,如何分离软件开发过程的不同方面(Aspect)--Log,Session,Authentication and Authorization,Transaction,等等。

初次接触 AspectJ 的读者看到这些资料(或者语法手册),会感到 AspectJ 有些神秘。他们想知道,AspectJ 是如何做到这些的? AspectJ 是怎样工作的? AspectJ 需要特殊的运行环境吗?

本文从另一个角度讲解 AspectJ,本文从讲解 AspectJ 的设计思路、运行原理入手,回答上述问题。

本文讲解的主要内容,按照概念的重要程度,排列如下:

  1. AspectJ 是一个代码生成工具(Code Generator)。
  2. AspectJ 语法就是用来定义代码生成规则的语法。您如果使用过 Java Compiler Compiler (JavaCC),您会发现,两者的代码生成规则的理念惊人相似。
  3. AspectJ 有自己的语法编译工具,编译的结果是 Java Class 文件,运行的时候,classpath 需要包含 AspectJ 的一个 jar 文件(Runtime lib)。
  4. AspectJ 和 xDoclet 的比较。AspectJ 和 EJB Descriptor 的比较。

本文的原则是,只细讲其他资料没有讲到的东西,其他资料讲过的东西,不讲或略讲。以节省网络资源,更为了节省大家宝贵的时间。J


2 . Aspect Oriented Programming (AOP)

本节简单介绍 AOP 的概念,解释我们为什么需要 AOP。

AOP 是 Object Oriented Programming(OOP)的补充。

OOP 能够很好地解决对象的数据和封装的问题,却不能很好的解决 Aspect("方面")分离的问题。下面举例具体说明。

比如,我们有一个 Bank(银行)类。Bank 有两个方法,deposit(存钱)和 withdraw(取钱)。

类和方法的定义如下:

 Code 2.1 Bank.java 
 class Bank{ 
 public float deposit(AccountInfo account, float money){ 
  // 增加 account 账户的钱数,返回账户里当前的钱数
 } 
 public float withdraw(AccountInfo account, float money){ 
  // 减少 account 账户的钱数,返回取出的钱数
 } 
 }; 

这两个方法涉及到用户的账户资金等重要信息,必须要非常小心,所以编写完上面的商业逻辑之后,项目负责人又提出了新的要求 -- 给 Bank 类的每个重要方法加上安全认证特性。

于是,我们不得不分别在上面的两个方法中加入安全认证的代码。

类和方法的定义如下:(新增加的代码用不同的背景标出)

 Code 2.2 Bank.java 
 class Bank{ 
 public float deposit(AccountInfo account, float money){ 
  // 验证 account 是否为合法用户
  // 增加 account 账户的钱数,返回账户里当前的钱数
 } 
 public float withdraw(AccountInfo account, float money){ 
  // 验证 account 是否为合法用户
  // 减少 account 账户的钱数,返回取出的钱数
 } 
 }; 

这两个方法都需要操作数据库,为了保持数据完整性,项目负责人又提出了新的要求 -- 给 Bank 类的每个操作数据库的方法加上事务控制。

于是,我们不得不分别在上面的两个方法中加入安全认证的代码。

类和方法的定义如下:(新增加的代码用不同的背景标出)

 Code 2.3 Bank.java 
 class Bank{ 
 public float deposit(AccountInfo account, float money){ 
  // 验证 account 是否为合法用户
  // Begin Transaction 
  // 增加 account 账户的钱数,返回账户里当前的钱数
  // End Transaction 
 } 
 public float withdraw(AccountInfo account, float money){ 
  // 验证 account 是否为合法用户
  // Begin Transaction 
  // 减少 account 账户的钱数,返回取出的钱数
  // End Transaction 
 } 
 }; 

我们看到,这些与商业逻辑无关的重复代码遍布在整个程序中。实际的工程项目中涉及到的类和函数,远远不止两个。如何解决这种问题?

我们首先来看看 OOP 能否解决这个问题。

我们利用 Design Pattern 的 Template Pattern,可以抽出一个框架,改变上面的例子的整个设计结构。

类和方法的定义如下:

 Code 2.4 Base.java 
 abstract class Base{ 
 public float importantMethod(AccountInfo account, float money){ 
  // 验证 account 是否为合法用户
  // Begin Transaction 
  
  float result = yourBusiness(account, money) 
  // End Transaction 
  return result; 	
 } 
 protected abstract float yourBusiness(AccountInfo account, float money); 
 }; 
 Code 2.5 BankDeposit.java 
 class BankDeposit extends Base{ 
 protected float yourBusiness(AccountInfo account, float money){ 
  // 增加 account 账户的钱数,返回账户里当前的钱数
 } 
 }; 
 Code 2.6 BankWithdraw.java 
 class BankWithdraw extends Base{ 
 protected float yourBusiness(AccountInfo account, float money){ 
  // 减少 account 账户的钱数,返回取出的钱数
 } 
 }; 

这里我们用一种很勉强的方法实现了认证和事务代码的重用。而且,有心的读者可能会注意到,这种方法的前提是,强制所有的方法都遵守同样的 signature。

如果有一个转账方法 transfer(AccountInfo giver, AccountInfo receiver, float money),由于 transfer 方法的 signature 不同于 yourBusiness 的 signature,这个方法无法使用上面的框架。

这个例子中提到的认证,事务等方面,就是 AOP 所关心的 Aspect。

AOP 就是为了解决这种问题而出现的。AOP 的目的就是 --Separation of Aspects (or Separation of Concerns).

下面的章节,解释 EJB Descriptor,AspectJ,xDoclet 等工具如何解决 Separation of Aspects 的问题。


3 . EJB Descriptor

如果我们使用 EJB 实现上面的例子,Bank 类可以作为一个 Stateless Session Bean 实现。

在 Bank 的代码中只用考虑商业逻辑,不用考虑认证和事务等方面。

认证和事务等方面在 EJB Descriptor 中定义,由 EJB Container 提供这些方面的实现。

我们来看一下,如何使用 EJB Descriptor 描述上面的例子。

EJB Descriptor 包括一个 ejb-jar.xml 文件。ejb-jar.xml 文件包含两大部分,enterprise-beans 和 assembly-descriptor 部分。enterprise-beans 部分包含 EJB 的定义 --JNDI Name,EJB Home, Interface, Bean Class Path 等;assembly-descriptor 部分包括配置信息的定义 -- 安全角色,事务控制等等。

下面给出上面例子对应的模拟 EJB Descriptor。

 <ejb-jar> 
 <enterprise-beans> 
  <session> 
     <ejb-name>Bank</ejb-name> 
     …
     <ejb-class>example.Bank</ejb-class> 
     <session-type>Stateless</session-type> 
     <transaction-type>Container</transaction-type> 
 <security-role-ref> 
 <role-name>bank-account</role-name> 
 </security-role-ref> 
  </session> 
 </enterprise-beans> 
 <assembly-descriptor> 
  <security-role> 
    <role-name>bank-account</role-name> 
  </security-role> 
 <method-permission> 
 <role-name>employee</role-name> 
 <method> 
 <ejb-name>Bank</ejb-name> 
 <method-name>deposit</method-name> 
 </method> 
 <method> 
 <ejb-name>Bank</ejb-name> 
 <method-name>withdraw</method-name> 
 </method> 
 </method-permission> 
 <container-transaction> 
 <method> 
 <ejb-name>Bank</ejb-name> 
 <method-name>deposit</method-name> 
 </method> 
 <method> 
 <ejb-name>Bank</ejb-name> 
 <method-name>withdraw</method-name> 
 </method> 
 <trans-attribute>Required</trans-attribute> 
 </container-transaction> 
 </assembly-descriptor> 
 </ejb-jar> 

本文后面会讲到如何用 AspectJ 实现上例中的 Separation of Aspects。

读者可以比较一下 AspectJ 语法和 EJB Descriptor 定义之间的对应关系。

两者都提供了类名、方法名的匹配规则,能够把类的方法映射到认证,事务等 Aspect(方面)。


4 . AspectJ

这一节我们来看看 AspectJ 如何实现上例中的 Separation of Aspects。

使用 AspectJ,我们不用对原有的代码做任何修改,就可以为代码提供不同的 Aspect(方面)-- 比如,认证,事务等。

我们只需要提供两个不同的 Aspect-- 认证 Aspect 和事务 Aspect。

 Code 4.1 AuthAspect.java 
 aspect AuthAspect{ 
  pointcut bankMethods() : execution (* Bank.deposit( … )) || 
    execution (* Bank. withdraw ( … )); 
  Object around(): bankMethods(){ 
  // 验证 account 是否为合法用户
  return proceed(); 
  } 
 }; 
 Code 4.2 TransactionAspect.java 
 aspect TransactionAspect{ 
  pointcut bankMethods() : execution(* Bank.deposit( … )) || 
    execution (* Bank. withdraw ( … )); 
  Object around(): bankMethods(){ 
  // Begin Transaction 
  Object result = proceed(); 
  // End Transaction 
  return result; 
  } 
 }; 

如果您暂时不能理解这段代码,没有关系,后面会讲到,这些 aspect 的定义,不过是定义了一些代码生成规则。

我们用 AspectJ 编译器编译 Bank 文件和含有 aspect 的这个文件,出来的结果就是带有安全认证和事务处理的 Bank 类。编译出来的这个 Bank 类调用了 AspectJ Runtime Lib,所以,如果你要运行这个 Bank 类,你需要把 AspectJ Runtime Lib 设置在你的 classpath 里面。

我们来看看,AspectJ 编译器为我们做了什么事情。

  1. 首先,AspectJ 从文件列表里取出所有的文件名,然后读取这些文件,进行分析。
  2. AspectJ 发现一些文件含有 aspect 的定义,在这个例子里,就是 AuthAspect 和 TransactionAspect 的定义;这些 aspect 就是代码生成规则。
  3. AspectJ 根据这些 aspect 代码生成规则,修改添加你的源代码。在这个例子里,就是修改添加 Bank 文件。
  4. AspectJ 读取 AuthAspect 的定义,发现了一个 pointcut--bankMethods();这个 pointcut 的定义是 execution(* Bank.deposit( … )) || execution(* Bank. withdraw ( … )),表示所有对 Bank 类的 deposit 和 withdraw 方法的执行点。
  5. AspectJ 继续读取 AuthAspect 的定义,发现了一个 around(),这在 AspectJ 中叫做 Advice,我不明白为什么叫这个名字,不过没关系,我们只要知道它是干什么的就行了。Advice 允许你在某个类的方法的调用之前或调用之后,加入另外的代码。Code 4.1 所示代码中的 around() 的" // 验证 account 是否为合法用户"部分,就是要加入的代码。这段代码要加在哪里呢? around() 后面跟了一个 pointcut--bankMethods()。根据这个 pointcut,AspectJ 会把这段代码加入到 Bank.deposit 和 Bank.withdraw 两个方法的执行之前。达到的效果就如同 Code 2.2 所示。
  6. AspectJ 读取 TransactionAspect 的定义,象第(4)步一样,发现了发现了一个 pointcut--bankMethods()。
  7. AspectJ 继续读取 AuthAspect 的定义,发现了一个 around()。这次 AspectJ 把"Begin Transaction"和"End Transaction"两段代码加在 Bank.deposit 和 Bank. withdraw 两个方法的执行前后。达到的效果就如同 Code 2.3 所示。

如何验证这一点?您可以到 http://www.eclipse.org/aspectj/下载安装 AspectJ,编译里面的 Sample,把编译结果反编译一下,就可以看到 AspetJ 自动生成的代码。

我们看到,AspectJ 是一种代码自动生成工具。你编写一段通用的代码,比如认证方面的代码,事务方面的代码,然后根据 AspectJ 语法定义一套代码生成规则(aspect 定义),AspectJ 就会帮助你自动把这段通用代码分布到对应的代码里面去,简单快捷,算无遗策。

无独有偶,一个著名的编译器生成工具 --Java Compiler Compiler (JavaCC),也采用了非常相似的代码生成机制。JavaCC 允许你在语法定义规则文件中,加入你自己的 Java 代码,用来处理读入的各种语法元素。

AspectJ 令你的代码更精简,结构更良好。AspectJ 的好处,我就不多说了,网上很多精彩的文章探讨 AspectJ 的各种用途。

下面介绍一个著名的代码自动生成器 --xDoclet,和 EJB Descriptor,AspectJ 之间的联系和比较。


5 . xDoclet

我们知道,Doclet 用来生成 Javadoc,xDoclet 是 Doclet 的扩展,不仅仅能生成 Javadoc,还能够生成源代码和配置信息等。

Doclet 和 xDoclet 的工作原理,就是处理源代码中的注释中的 tag,生成相应的信息。这些 tag 都以 @ 开头,你可以自己定义 tag 和对 tag 的处理,生成自定义的信息。

(这里提一下 Apache Maven Project。Maven 是一种 Project Build 工具。用 Maven 进行管理的项目,能够同时生成 Javadoc 和 XRef。XRef 是 Source Code Cross Reference。)

JBoss 就利用 xDoclet 为 EJB 自动生成 EJB Home 和 EJB Object Interface 源文件,和 EJB Descriptor 文件。

在 Sourceforge.net 上看到一个叫做 Barter 的开源项目,利用 xDoclet 为类方法生成 AspectJ 代码。

请注意,EJB Descriptor 和 AspectJ 都是把方方面面的 Aspects 集中在一处进行管理,而 xDoclet 的思想是处理散布在源代码中的各种 tag。

xDoclet 在生成 EJB Descriptor 和 AspectJ 等方面的应用,正应了中国的一句古话 -- 分久必合,合久必分。


6 .总结

开源项目的出现,打破了软件技术领域的众多壁垒,推动软件技术进程的日新月异。

同时,一些新名词,新概念也层出不穷,令人眼花缭乱,无所适从。其实,很多东西都是换汤不换药,我们理解应用这些新技术的时候,要抓住本质,要破除迷信,破除任何人为的神秘感。

举个例子,现在炒作的很热的一些概念,"Web Service",还有"Grid Computation"(网格计算),都是基于原有的各种技术发展出来的。媒体和技术文章不应该人为地制造任何神秘感。

互联网时代的权威,不是说出来的,而是做出来的。

另外,围绕着一些有前途的新技术,总会出现大量的"快速入门手册",有些简直就是对该技术帮助文档的翻译,而且,有难度的地方没有翻译出来,大家都明白的地方翻译得非常详尽,详尽到了没有必要的地步。这种因为市场需求而产生的应景时文,大量地出现在技术文章领域。

笔者对本文的期望是,决不迷信,决不重复。并试图引入一种洁净的,毫无废话的文风。笔者期待一针见血的驳斥和批评。

Enjoy it. J
Thanks.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值