你的位置:首页 > Java教程

[Java教程]Java 面向切面编程 AOP


本文内容

  • 实例
    • 引入
    • 原始方法
    • 装饰者模式
  • JDK 动态代理和 cglib 代理
  • 直接使用 AOP 框架

下载 demo

实例


引入

package com.cap.aop;
 
public interface ICalculator {
  public double add(double num1, double num2) throws Exception;
 
  public double sub(double num1, double num2) throws Exception;
 
  public double div(double num1, double num2) throws Exception;
 
  public double mul(double num1, double num2) throws Exception;
}


package com.cap.aop;
 
/**
 * 加减乘除
 * */
public class Calculator implements ICalculator {
  @Override
  public double add(double num1, double num2) {
    double result = num1 + num2;
    return result;
  }
 
  @Override
  public double sub(double num1, double num2) {
    double result = num1 - num2;
    return result;
  }
 
  @Override
  public double div(double num1, double num2) {
    double result = num1 / num2;
    return result;
  }
 
  @Override
  public double mul(double num1, double num2) {
    double result = num1 * num2;
    return result;
  }
}


定义 ICalculator 接口和类 Calculator,并且 Calculator 继承 ICalculator

若要为这个类添加“日志”功能该怎么办?日志在实际项目中很有必要,比如数据库日志,业务日志等等,通过日志就能知道数据库和业务存在的问题,这要比调试程序容易多了,此外还有性能统计,安全控制,事务处理,异常处理等等都是类似的问题。

我们最可能想到的是,在类的每个方法内都写日志相关的代码,或是在该类的基类中写,在其子类中继承。

原始方法

package com.cap.aop;
 
/**
 * 加减乘除,原始方式
 * */
public class CalculatorOriginalWay implements ICalculator {
  @Override
  public double add(double num1, double num2) {
    System.out.println("the method [add()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = num1 + num2;
 
    System.out.println("the method [add()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double sub(double num1, double num2) {
    System.out.println("the method [sub()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = num1 - num2;
 
    System.out.println("the method [sub()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double div(double num1, double num2) {
    System.out.println("the method [div()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = num1 / num2;
 
    System.out.println("the method [div()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double mul(double num1, double num2) {
    System.out.println("the method [mul()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = num1 * num2;
 
    System.out.println("the method [mul()]" + "end with result (" + result
        + ")");
 
    return result;
  }
}


这样做得缺点显而易见,重复代码太多,耦合也不好。要是该类只针对正数运算呢,代码如下所示:

package com.cap.aop;
 
/**
 * 加减乘除,只对正数
 * */
public class CalculatorForPositiveNumber implements ICalculator {
  @Override
  public double add(double num1, double num2) throws Exception {
    this.argsValidatior(num1);
    this.argsValidatior(num2);
 
    System.out.println("the method [add()]" + "begin with args (" + num1
        + "," + num2 + ")");
    double result = num1 + num2;
    System.out.println("the method [add()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double sub(double num1, double num2) throws Exception {
    this.argsValidatior(num1);
    this.argsValidatior(num2);
 
    System.out.println("the method [sub()]" + "begin with args (" + num1
        + "," + num2 + ")");
    double result = num1 - num2;
    System.out.println("the method [sub()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double div(double num1, double num2) throws Exception {
    this.argsValidatior(num1);
    this.argsValidatior(num2);
 
    System.out.println("the method [div()]" + "begin with args (" + num1
        + "," + num2 + ")");
    double result = num1 / num2;
    System.out.println("the method [div()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double mul(double num1, double num2) throws Exception {
    this.argsValidatior(num1);
    this.argsValidatior(num2);
 
    System.out.println("the method [mul()]" + "begin with args (" + num1
        + "," + num2 + ")");
    double result = num1 * num2;
    System.out.println("the method [mul()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  private void argsValidatior(double arg) throws Exception {
    if (arg < 0)
      throw new Exception("参数不能为负数");
  }
}


这也仅仅是一个类而已,实际项目中那么多类,要是都这么干,显然不现实,那么如何改进?

设计模式“装饰者模式”,在不必改变原类文件和继承的情况下,动态地扩展一个对象的功能。

装饰者方法

package com.cap.aop;
 
/**
 * 加减乘除,装饰者模式
 */
public class CalculatorDecorator implements ICalculator {
  private ICalculator cal;
 
  public CalculatorDecorator(ICalculator iCalculator) {
    cal = iCalculator;
  }
 
  @Override
  public double add(double num1, double num2) throws Exception {
    System.out.println("the method [add()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = cal.add(num1, num2);
 
    System.out.println("the method [add()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double sub(double num1, double num2) throws Exception {
    System.out.println("the method [sub()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = cal.sub(num1, num2);
 
    System.out.println("the method [sub()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double div(double num1, double num2) throws Exception {
    System.out.println("the method [div()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = cal.div(num1, num2);
 
    System.out.println("the method [div()]" + "end with result (" + result
        + ")");
 
    return result;
  }
 
  @Override
  public double mul(double num1, double num2) throws Exception {
    System.out.println("the method [mul()]" + "begin with args (" + num1
        + "," + num2 + ")");
 
    double result = cal.mul(num1, num2);
 
    System.out.println("the method [mul()]" + "end with result (" + result
        + ")");
 
    return result;
  }
}


这个方法比上面的强点,但也有弱点,需要为每个类都应用“装饰者模式”,工作量也不小。如何解决?——代理。

 

JDK 动态代理和 cglib 代理


JDK 从 1.3 版本开始,引入了动态代理。JDK 动态代理非常简单,但动态代理的对象必须是一个或多个接口。

若想代理类,就需要使用 cglib 包。cglib 是一个强大的、高性能的代码生成包,cglib 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类,cglib 被许多 AOP 框架使用,例如 Spring AOP;Hibernate 使用 cglib 来代理单端 single-ended(多对一和一对一)关联;EasyMock 通过使用模仿(moke)对象来测试 java 包……它们都通过 cglib 来为那些没有接口的类创建模仿(moke)对象。

JDK 动态代理

package com.cap.aop;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
 
/**
 * 加减乘除,JDK 代理<br/>
 * 只能代理接口,不能代理类
 * 
 * */
public class CalculatorInvocationHandler implements InvocationHandler {
  // 动态代理只有在运行时才知道代理谁,所以定义为Object类型
  private Object target = null;
 
  /**
   * 通过构造函数传入原对象
   * 
   * @param target
   *      要代理的对象
   */
  public CalculatorInvocationHandler(Object target) {
    this.target = target;
  }
 
  /**
   * InvocationHandler 接口的 invoke 方法,调用代理的方法
   * 
   * @param proxy在其上调用方法的代理实例
   * @param method拦截的方法
   * @param args拦截的参数
   * */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    System.out.println("JDK proxy...");
    // 日志开始
    System.out.println("the method [" + method.getName() + "]"
        + "begin with args (" + Arrays.toString(args) + ")");
 
    Object result = method.invoke(this.target, args);
 
    // 日志结束
    System.out.println("the method [" + method.getName() + "]"
        + "end with result (" + result + ")");
 
    return result;
  }
 
  /**
   * 获取代理类
   */
  public Object getProxy() {
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new CalculatorInvocationHandler(target));
  }
}


cglib 代理

package com.cap.aop;
 
import java.lang.reflect.Method;
import java.util.Arrays;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
/**
 * 加减乘除,cglib 代理<br/>
 * 能代理接口和类,不能代理final类
 */
public class CalculatorInterceptor implements MethodInterceptor {
  private Object target;
 
  public CalculatorInterceptor(Object target) {
    this.target = target;
  }
 
  @Override
  public Object intercept(Object proxy, Method method, Object[] args,
      MethodProxy invocation) throws Throwable {
    System.out.println("cglib proxy...");
    // 日志开始
    System.out.println("the method [" + method.getName() + "]"
        + "begin with args (" + Arrays.toString(args) + ")");
 
    Object result = invocation.invoke(target, args);
    // 日志结束
    System.out.println("the method [" + method.getName() + "]"
        + "end with result (" + result + ")");
 
    return result;
  }
 
  public Object proxy() {
    return Enhancer.create(target.getClass(), new CalculatorInterceptor(
        target));
  }
}


主程序如下所示:
package com.cap.aop;
 
public class Client {
  public static void main(String[] args) throws Exception {
    ICalculator calculatorProxy = (ICalculator) new CalculatorInvocationHandler(
        new Calculator()).getProxy();
    calculatorProxy.add(10, 10);
 
    Calculator calculator = (Calculator) new CalculatorInterceptor(
        new Calculator()).proxy();
    calculator.add(20, 20);
  }
}


运行结果:

JDK proxy...
the method [add]begin with args ([10.0, 10.0])
the method [add]end with result (20.0)
cglib proxy...
the method [add]begin with args ([20.0, 20.0])
the method [add]end with result (40.0)


 

直接使用 AOP 框架——AspectWerkz


利用 AOP 框架,你只需要利用注释和 Aspect 就可以完成上面工作。

AOP,Aspect Oriented Programming,称为“面向切面编程”,是一种通过预编译和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。在 Spring 中,通过分离应用的业务逻辑与系统级服务,应用对象只完成业务逻辑而已,并不负责(甚至是意识)其它的系统级的关注点,例如日志、事务、审计等等。AOP 是 GoF 的延续,GoF 孜孜不倦的追求是,调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP 的目标也是一样。

AOP 主要应用在日志记录,性能统计,安全控制,事务处理,异常处理等等,将它们从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP 与 OOP/OOD

AOP(面向切面编程)与 OOP(面向对象编程)字面上虽然类似,但却是面向不同领域的两种设计思想。

OOP 针对业务中的实体及其属性和行为进行抽象封装,以便划分逻辑单元。而 AOP 则是针对业务中的“切面”进行提取,它面对的是处理过程中的某个步骤或阶段,以获得各部分之间低耦合性的隔离效果。因此,这两种设计思想有着本质的差异。

简单来说,对“雇员”这个业务实体进行封装,是 OOP 的任务,我们可以建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用 AOP 对“雇员”进行封装将无从谈起;权限检查也是如此,它是 AOP 的领域。

换而言之,OOD/OOP 面向名词领域,AOP 面向动词领域。

很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 接口也能,我想这个观点是值得商榷的。AOP 和定义良好的 OOP 可以说都是用来解决并且实现需求中的横切问题。但对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法回避的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会,你只需要修改相应的 Aspect,再重新 weave(编织)即可。 AOP 也绝对不会取代 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,扬长避短。

AOP 涉及的概念

这些概念,在 AspectWerkz 的注释中都有所体现。

  • Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

Java 的 AOP 框架

  • AspectWerkz 是简单、动态、轻量级、强大的 AOP 框架,更容易的集成 AOP 的项目中。
  • JBoss AOP 是 JBoss 4.0 带了一个 AOP 框架,但也能够在你的应用中单独的运行它。
  • Nanning。Nanning Aspects is a simple yet scaleable aspect-oriented framework for Java. Nanning is also nice "little" town in Guanxi province in southern China. It's about 1-2 million inhabitants which barely qualifies it as a town by chinese standards. It is not to be confused with the much larger Nanking/Nanjing.
  • JAC,Java Aspect Components 是一个应用服务器。它为Java2平台、用于Java开发的企业开发环境(J2EE)、和基于Web的分布式应用,提供开放式资源 的又一个选择(在GNU次常规公共许可证下发布)。JAC包括统一模型语言(UML)IDE,该UML IDE模块化应用商业逻辑并且自动生成和编译纯商业逻辑Java类。这些类,在JAC容器内执行,可从一组技术和/或商业的横切关系 (crosscutting concerns)[1] 如数据持久性、认证、配置文件管理、访问权限检测、演示、和负载平衡中无缝地受益。基于面向方面编程技术(AOP)的JAC将这些关系( concerns)[2]从应用程序的核心商业逻辑中分离出来。
  • DynamicAspects。This project is no longer maintained! DynamicAspects enables you to do aspect-oriented programming in pure Java. Using the "instrumentation" and "agent" features introduced with Sun JDK 1.5, aspects can be installed and deinstalled during runtime!
  • CAESAR。CaesarJ is a new Java based programming language, which facilitates better modularity and development of reusable components. The components are collaborations of classes, but they can modularize crosscutting features or non-functional concerns. Caesar language features help to implement, abstract and integrate such components. Caesar can be used in combination with plain Java. Tool support is available in the form of an Eclipse plugin.
  • PROSE 是一个动态编排(weaving)工具(允许在运行期插入或抽取aspects)。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。
  • FastAOP。FastAOP is an very high performant AOP (Aspect Oriented Programming) framework for java. The framework was initially
    developped to support performance profiling and monitoring for large J2EE applications with nearly no runntime overhad.

 

.Net 的 AOP 框架

  • Encase 是C#编写开发的为.NET平台提供的AOP框架。Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式。这种部署方面(aspects)的方法帮助缺少经验的开发人员提高开发效率。
  • NKalore 是一款编程语言,它扩展了C#允许在.net平台使用AOP。NKalore的语法简单、直观,它的编译器是基于Mono C#编译器(MCS)。NKalore目前只能在命令行或#Develop内部使用。NKalore兼容公共语言规范CLS(Common Language Specification),它可以在任何.NET开发环境中使用,包括微软的Visual Studio .NET。
  • PostSharp 读取 .NET 字节模块,转换成对象模型。让插件分析和转换这个模型并写回到MSIL。PostSharp使开发程序分析应用程序容易得像分析代码规则和设计模式,它使程序开发的思想变革为面向方面软件开发(AOSD/AOD)思想。
  • AspectDNG 的目标是为.NET开发人员提供简单而功能强大的AOP-GAOP实现。它效仿java下的开源工具AspectJ 和 Spoon,成熟程度也很接近它们。
  • RAIL(Runtime Assembly Instrumentation Library) 开源项目可以在C#程序集加载和运行前进行处理控制调整和重新构建。C#在CLR中,我们已经能够动态加载程序集并且获得程序集中的类和方法,RAIL(Runtime Assembly Instrumentation Library)的出现填补了CLR处理过程中的一些空白。
  • SetPoint是一款.NET框架下的全功能(full-featured)AOP引擎.它着重为称为语义切点(semantic pointcuts)的定义依赖RDF/OWL的使用.它的功能为一个IL-level,highly dynamic weaver&LENDL,一个引人注目的定义语言 DotNetAOP 为 CLR language提供AOP 框架基础属性。
  • NAop 是一个 DotNet 下的 AOP 框架。
  • AspectSharp 是 DotNet 下的免费 AOP框架,它以 Dynamic Proxies 和

 

下载 demo