`
frank-liu
  • 浏览: 1665574 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

AOP技术基础

    博客分类:
  • aop
阅读更多

简介

    我们在使用一些OOP相关的编程语言的时候,就会受到很重的OOA/D思想影响。关于各种职责的划分、各种设计模式的思想和变化等等。只为了能够更加轻松灵活的实现各种业务和需求变化。 当然,在一定程度上,当我们使用OOP的时候,会发现处理某些特定的问题单纯用这些思想还是有点不足。一个典型的场景就是AOP(Aspect Oriented Programming)。在我之前的一些文章里有讨论过AOP结合一些框架的应用,但是对于它的一些比较原生的思想却讨论的比较少。这里想从一些更基础的层面去探讨AOP的思想和实现技术。

 

OOP和AOP

    正如我们在OOP学习里所了解到的,OOP的目的是为了提供一个更加灵活可扩展可重用的方案来解决实际应用的问题。但是在某些实际应用的场景下,光用OOP不一定能找到一个合适的方法。这些场景更多的是牵涉到一些业务的切入。比如在很多应用的具体实现里,我们需要对很多具体的行为进行权限验证。这样,在几乎大多数牵涉到一些权限限制下的操作,我们都需要做这么一个检查。在一些应用里,我们需要记录它们执行的日志信息,有的是在它们某个执行点之前,有的在之后。在某些情况下,我们需要记录跟踪应用的执行情况。这些都是在原有实现的基础上引入的新的需求和变化。从下图中,我们可以看到,我们要引入的变化和现有业务的实现的关系。

 

    那么,为了适应这些变化,我们该怎么做呢?一种办法就是针对每种需要应用到具体业务的场景都来加入具体调用相关的代码。这种方式有一个不足的地方就是,它需要在多个地方重复。从追求软件尽量可重用的基础上,这部分代码其实和原有的代码在业务逻辑上很可能不是一个层面的,这样的实现就显得有点丑陋。另外,这种方式也不够灵活,如果有什么变化的话,我们还是要修改原来的这块代码。哪怕是改方法调用什么的。所以,最理想的方式就是我们原来的代码什么都不用动,我们可以通过某种方式将我们需要添加的内容织入到原有的代码中。

   现在,我们就先来探讨一下没有框架支持的情况下,有哪些思路可以达到这个目的。 

 

几种实现AOP的思路

简单代码重复

   这种方法最简单直接,只要在每个需要重复的地方重复代码就可以了。当然,既然我们希望能寻找更好的方式,这种方式是不可接受的。

 

Decorator模式

    在我之前的一篇文章里有讨论过这个模式。如果我们希望在不破坏使用者对目标对象方法签名的基础上,一种可行的办法就是通过decorator pattern。它的类模式如下图:

 

   在这种实现里,我们的原有业务对象就相当于是类ConcreteComponent的对象。它必须继承一个抽象类或者实现一个接口。同时,我们需要有一个Decorator类,它也继承自同一个父类,但是它本身也有一个指向抽象父类的引用。同时,它本身并不实现任何具体的细节。它只是保持了一个和父类同样的方法签名以保持兼容,而具体的实现在实现它的子类里。

    我们结合一个实例来讨论这种方式的应用。假设我们有一个类Hello:

 

package com.wrox.Hello;

public class Hello implements HelloInterface {

    @Override
    public void sayHello() {
        System.out.println("hello");        
    }

}

 它实现了接口HelloInterface:

 

package com.wrox.Hello;

public interface HelloInterface {
    void sayHello();
}

   这就相当于前面uml图里的ConcreteComponent和Component。

    当然,为了能够有一个可以实现增强效果的地方,我们需要定义一个抽象类Decorator:

package com.wrox.Hello;

public abstract class Decorator implements HelloInterface{
    protected HelloInterface hello;
    
    public Decorator(HelloInterface hello) {
        this.hello = hello;
    }
}

     它的主要职责就是保留一个指向抽象接口的引用,并定义一个增强效果的构造函数。当然,这个效果是怎么增强的,在详细的代码里我们就会看到了。

    有了这个Decorator之后,如果我们希望在Hello的方法执行之前做一些操作,我们就需要定义一个扩展的类BeforeHello:

 

package com.wrox.Hello;

public class BeforeHello extends Decorator {
    public BeforeHello(HelloInterface hello) {
        super(hello);
    }
    
    @Override
    public void sayHello() {
        System.out.println("Something happens before sayHello!");
        hello.sayHello();
    }
}

    它的实现比较直接,就是在调用自己引用的hello对象之前,先执行一个自定义的输出。

    同样的,为了在Hello的方法之后执行一些任务,我们也定义了一个AfterHello:

package com.wrox.Hello;

public class AfterHello extends Decorator {
    public AfterHello(HelloInterface hello) {
        super(hello);
    }

    @Override
    public void sayHello() {
        hello.sayHello();
        System.out.println("After hello triggered!");
    }
}

     从使用的角度来说,现在我们有了两个可以增强原有属性的类了,一个BeforeHello, 一个AfterHello。现在,假设我们希望在Hello的方法执行之前和之后都执行给定的行为,我们需要在具体的代码里如下操作:

 

package com.wrox.Hello;

public class App {
    public static void main( String[] args ) {
        HelloInterface hello = new AfterHello(new BeforeHello(new Hello()));
        hello.sayHello();
    }
}

     可以看到,我们最开始定义的Hello对象就相当于一个被包裹的核,我们通过一层层的构造函数来包装它,最终达到了增强原有特性的效果。如果执行上述代码,我们将看到如下的输出:

 

Something happens before sayHello!
hello
After hello triggered!

     综合来看,这种使用Decorator pattern的方式还是太费劲了。一个是它需要对我们增强效果的目标对象定义一个实现的接口。或者我们将这个目标对象作为父类来处理。另外一个,我们需要定义Decorator类和具体的属性扩展类。还要包含有相关的增强功能的构造函数。

 

Proxy和InvocationHandler

   还有一种方法就是我们使用Proxy和InvocationHandler来实现这个效果。假设在原有Hello和HelloInterface接口的基础上,我们要增强它的效果。我们可以定义一个如下的类EnhancedHandler:

 

package com.wrox.Hello;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class EnhancedHandler implements InvocationHandler {

    private Object obj;
    
    public EnhancedHandler(Object obj) {
        this.obj = obj;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if(methodName.equals("sayHello")) {
            System.out.println("Something happends before sayHello!");
            Object result = method.invoke(obj, args);
            System.out.println("After hello triggered!");
            
            return result;
        }
        
        return method.invoke(obj, args);
    }

}

   在这个类里,我们实现了接口InvocationHandler。然后,我们通过反射的方式,通过判断方法名的方式,在程序里调用我们增强的这部分代码。

    具体使用这部分的代码如下:

package com.wrox.Hello;

import java.lang.reflect.Proxy;

public class App {
    public static void main( String[] args ) {
        HelloInterface h = new Hello();
        HelloInterface proxyH = (HelloInterface)Proxy.newProxyInstance(EnhancedHandler.class.getClassLoader(), 
                new Class<?>[] {HelloInterface.class}, new EnhancedHandler(h));
        proxyH.sayHello();
    }
}

    这样,我们将得到前面同样的输出。当然,我们通过这种方式也达到了增强原有代码的效果。只是这里依然有同样的限制,比如说我们要对接口进行代理。这就限制了原有的类里头必须只能是实现某个接口的,单独的类就不行。另外,在增强的部分也不灵活,我们需要判断自己想要增强的方法名,如果想要引入新的方法的时候,这里的变动影响比较大。

   那么,针对这个只能对接口的代理,有没有什么更好的办法呢?我们可以看看下面这种方式。

 

CGLIB

    使用CGLIB的话,我们需要在程序里引入对这个类库的依赖:

 

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.6</version>
</dependency>

    我们首先要定义一个类HelloProxy:

 

package com.wrox.Hello;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class HelloProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("sayHello")) {
            System.out.println("Something happends before sayHello!");
            proxy.invokeSuper(object, objects);
            System.out.println("After hello triggered!");
            
            return object;
        }
        
        proxy.invokeSuper(object, objects);
        return object;
    }

}

    这里需要实现接口MethodInterceptor。这里的方法也很类似,它是通过反射,根据方法的名字来判断。然后我们再调用相应的增强。

   而使用这部分的代码如下:

package com.wrox.Hello;

import net.sf.cglib.proxy.Enhancer;

public class App {
    public static void main( String[] args ) {
        HelloProxy proxy = new HelloProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FlatHello.class);
        enhancer.setCallback(proxy);
        FlatHello hello = (FlatHello)enhancer.create();
        hello.sayHello();
    }
}

     因为这里是对普通的类进行增强的,我们可以用一个通用的类来尝试。这里我用了另外一个类FlatHello。它和Hello的差别就在于它没有实现任何接口,仅仅是定义了一个同样的方法实现。

 

AspectJ

    我们从前面几种实现功能增强的方法里都可以看到,它们是通过在运行时动态的反射来达到这个增强效果。而这里采用aspectJ却是另外一种方式,它是在编译的时候生成代码到现有的class文件里。这种方式可以实现切入内容和原有实现更加松的耦合。具体它的实现增强我们在后面会详细讨论。

    要实现使用AspectJ的效果,我们需要下载AspectJ这个工具。它的下载地址如下(www.eclipse.org/aspectj) 。下载到本地之后,执行java -jar aspectj-1.x.x.jar 命令后会进行安装。在我这边的本地环境里,aspectJ安装的目录是~/aspectj1.8。我们为什么要下载aspectJ这个工具呢?因为我们前面提到过,它是通过编译的方式将内容织入到目标对象里,所以它提供了一个工具来实现对源代码的编译。

    现在,我们先来定义一个简单的对象Hello:

 

public class Hello {
    public void sayHello() {
        System.out.println("Hello AspectJ!");
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        h.sayHello();
    }
}

    这部分的代码非常简单,就是输出一个字符串。

    然后我们再创建一个文件TxAspect.aj

    

public aspect TxAspect {
    void around():call(void Hello.sayHello()) {
        System.out.println("Start transaction...");
        proceed();
        System.out.println("End transaction...");
    }
}

     需要注意的是,这里保存的文件是后缀名为.aj的,而不是.java的。因为这里是利用了aspectJ里自定义的一些语法和文件类型。

    上面的代码里有几个特殊的地方,一个是它用aspect来表示它是一个切面。而around方法表示在方法的执行前后来执行我们目前的内容。call方法的参数部分指明了是Hello对象里的sayHello方法。proceed()方法表示执行原来的sayHello方法。

    现在我们来编译这些代码:

 

ajc -cp ~/aspectj1.8/lib/aspectjrt.jar Hello.java TxAspect.aj

 

    需要注意一点,因为我们是用命令行来编译代码。这里程序编译的时候依赖一个类库aspectjrt.jar。我们可以在环境变量里配置它,也可以在命令行里指定它。而ajc相当于是对java默认编译器javac的增强。所以我们要用它来编译带.aj后缀的文件。

    在运行程序代码的时候我们也需要指定它的类库。

 

java -cp ~/aspectj1.8/lib/aspectjrt.jar:. Hello

     这时候,程序的输出如下:

 

Start transaction...
Hello AspectJ!
End transaction...

     这个方法看起来比较神奇,我们没有对原有的代码做任何的修改,只需要在外面定义一个aspect文件,里面描述好需要切入的方法和执行的内容就可以了。那么,既然前面我们提到过,我们采用aspectJ的时候是将这些切入的内容给编译到class文件里了。我们也可以进一步看看这些内容是怎么给织入进去的。

    在前面编译好的文件里,我们会发现有Hello.class和TxAspect.class两个文件。我们用java的反编译工具打开这些class文件看看。有一个比较简单的java反编译工具可以试一下jd(jd.benow.ca)。我们选择最简单的jar包来运行这个程序:

java -jar jd-gui-1.4.0.jar

    我们将Hello.class文件打开,会发现它对应如下的内容:

 

import java.io.PrintStream;
import org.aspectj.runtime.internal.AroundClosure;

public class Hello
{
  public void sayHello()
  {
    System.out.println("Hello AspectJ!");
  }
  
  private static final void sayHello_aroundBody1$advice(Hello target, TxAspect ajc$aspectInstance, AroundClosure ajc$aroundClosure)
  {
    System.out.println("Start transaction...");
    AroundClosure localAroundClosure = ajc$aroundClosure;sayHello_aroundBody0(target);
    System.out.println("End transaction...");
  }
  
  public static void main(String[] args)
  {
    Hello h = new Hello();
    Hello localHello1 = h;sayHello_aroundBody1$advice(localHello1, TxAspect.aspectOf(), null);
  }
  
  private static final void sayHello_aroundBody0(Hello paramHello)
  {
    paramHello.sayHello();
  }
}

     这里面多了一个sayHello_aroundBody1$advice方法,在main方法里最后执行的就是这个方法。可见,我们新切入的内容就被aspectj的编译器给加进去了。

 

总结 

    这样,我们在这里就探讨了要实现AOP技术可以采用的技术手段。从实现的角度来说,我们可以采用decorator pattern的方法,这种方式需要对目标对象做一些修改和扩展。这样带来的限制还是比较多的,而且很不灵活。如果采用InvocationHandler的方式来实现的话,它需要目标对象实现某个接口。然后在它的实现方法里通过反射来判断目标对象的切入方法。而CGLIB的方式则也是需要实现一个methodInterceptor接口,但是它不要求增强的目标对象实现接口。只是在它的具体实现里,也是通过反射来查找目标方法再进行处理。前面的这几种方法都是在运行时对目标对象进行增强。所以相对来说他们的性能方面会稍微差一些。

    除了以上的方法,还有一种就是采用AspectJ的方式。它是通过采用一套aspectJ专门的语法来描述切入的点和执行内容。同时,它通过对javac的增强ajc来对目标代码进行编译。这些代码被直接编译到class文件中。所以它的性能相对来说会高一点。

    以上是从底层实现的角度来看待AOP。当然,从宏观的角度来考虑AOP的话,无非就是以下几个点:1. 我们要切入什么?很多时候无非就是一些日志、安全验证、应用执行跟踪等需求。2. 在哪个地方切入?我们一般是在目标方法之前、之后或者中间。具体的应用里还包含一些其他场景。3. 怎么定义和关联我们需要切入的内容和切入点?一种就是前面的采用硬编码判断,一种就是采用特定的语法来解析处理。当然,在结合spring框架的时候,它里面已经做了很多的增强。在后续的文章里我们会对这些增强做一个进一步的分析讨论。

 

参考材料

https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/index.html

http://www.cnblogs.com/xrq730/p/7003082.html

AspectJ in Action

分享到:
评论

相关推荐

    AOP JAVA .NET

    AOP技术基础 Java平台AOP技术研究 .Net平台AOP技术研究

    AOP技术及其在J2EE中动态代理实现

    随着软件技术的发展及需求的增加,OOP逐渐表现出其不足之处,AOP在继承OOP基础之上很好地解决了00P ...理,说明了其可行性,体现了AOP技术应用价值和发展前景。 关键词:AOP;动态代理;横切关注点

    论文研究-基于AOP技术的基础服务构件的设计和实现 .pdf

    基于AOP技术的基础服务构件的设计和实现,麦通,邓中亮,随着软件规模的不断扩大、结构日益复杂,针对通用服务代码和核心功能代码交织缠绕的现象和问题,AOP作为一种全新的变编程思想,从

    生产实习概要

    微波技术基础总结 电磁场靶向调控技术技术优势 卅三先生的工程电磁场讲座.EEm04——电势能001 卅三先生的工程电磁场讲座.EEm04——电势能002 AOP技术基础 Spring IoC(控制反转)【IoC的技术基础】

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.zip

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    开发者突击·精通AOP整合应用开发 源码

    精通AOP整合应用开发(AspectWerkz+Aspectl+Spring)》以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后再详细讲解AspectWerkz、AspectJ、Spdng框架的AOP应用开发技术。...

    Spring AOP源码深度解析:掌握Java高级编程核心技术

    动态代理是实现AOP的基础,它通过JDK动态代理或CGLIB代理生成被代理对象的子类。通知是织入到目标对象连接点上的一段程序,例如@Before、@After等。 切点定义了通知应该在哪些连接点上触发。而切面则是通知和切点的...

    spring-aop.zip

    Spring以JVM的动态代理技术为基础,设计出了一系列的AOP横切实现,比如前置通知、返回通知、异常通知等。同时,Pointcut接口可以匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相关方法根据需求进行切入...

    基于OOP 和AOP的软件产品线实现技术研

    作为目前最为主流的软件开发技术,面向对象的编程OOP(Object-...本文在相关问题分析的基础上对基于OOP 和AOP 的产品线实现技术进行了研究,并通 过一个酬金发放系统产品线的实例分析对相关方法进行了验证和分析。

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.z01

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    Spring AOP核心编程思想教程

    非常详细的介绍Spring AOP全栈技术点,开篇帮助同学们进行知识储备,...从Spring AOP概念开始引入,通过Spring AOP代理和判断模式进行,宝库各种模式,不断的深入学习,相信会给同学们带来不一样的Spring AOP技术体验。

    进击的编程思想!带你学Spring AOP核心编程思想教程 新角度学面向切面编程

    何谓Spring AOP? Spring AOP 是基于 AOP 编程模式的一个框架,它的...课程从Spring AOP概念开始引入,通过Spring AOP代理和判断模式进行,宝库各种模式,不断的深入学习,相信会给同学们带来不一样的Spring AOP技术

    C#开源的AOP框架–KingAOP基础

    AOP面向切面编程(Aspect Oriented Programming),是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。Spring框架用的核心技术就是AOP,是函数式编程的一种衍生范型。利用AOP的好处就是可以对业务...

    spring ioc aop

    spring 基础技术,ioc,aop,依赖注入

    JavaScript中AOP的实现与应用

    AOP (Aspect Oriented Programming) ,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生,利用AOP可以对业务逻辑的各个部分进行隔离...

    AOP在HPMIS中的应用 (2011年)

    结合医院人员管理信息系统(HPMI S)中权限管理的开发过程,分析采序OOP (object ...通过AspectJ ,给吐了AOP技术在HPMI S权限控制开发中具体的应序示例,实现了非主要功能的集中模块化,并在此基础上讨论了AOP技术的优越性。

    SpringAOP学习笔记

    ,文章属于基础级文章,适合入门级的小伙伴,它的概念,应用场景,实现原理及Spring的AOP的开发。全称:面向切面编程(AspectOrientedProgramming),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。...

    Spring学习day03(Spring中的AOP)

    简单的说它就是把程序中重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对已有方法进行增强。 (2)AOP的作用及优势 作用:在程序运行期间,不修改源码对已有方法进行增强。 ...

Global site tag (gtag.js) - Google Analytics