Java AOPの各種実現方式

5207 ワード

AOPは切面プログラミング向けであり,プログラム実行中のいくつかの「面」(例えばある関数実行前)を切開し,コード実行を挿入する.
本文は主にAOPの各種実現方式を分析する.
AOPのコンパイル時挿入(静的符号化,AspectJ,注釈プロセッサ)
コンパイル時に挿入するのは一般的にクラスがclassファイルにコンパイルするときにいくつかの手足をして、コンパイルが完了したclassファイルに私たちが挿入したい横断論理を含ませることです.
スタティツクコーディング
自分で1つのエージェントクラスを書いて、この切り込みのロジックを加えて、この性能はきっと最も良くて、しかし普通はすべて切り込みの地方が同じロジックを置く必要があって、それらを集中的に管理するのがAOPの意義です
class SayHello {
    public void sayH() {
        System.out.println("H");
    }
}

class SayHelloProxy extends SayHello{
    public void sayH() {
        System.out.println("----before----");
        super.SayH();
        System.out.println("----after----");
    }

    public static void main(String[] args) {
        SayHello sayHello = new SayHelloProxy();
        sayHello.sayH();
    }
}

AspectJ
AspectJは、Java言語を拡張した断面向けのフレームワークです.
AspectJはjavacコンパイラを直接使用せず、それを使用する際にAjcコンパイラでjavaファイルをコンパイルする必要があります.これは、同じクラスのメソッドで相互に呼び出されても横断論理(実行時に動的エージェントができない)コードが次のように実行できる点が特別です.
public class SayHello {
    public void SayH() {
        System.out.println("H");
    }

    public void Saye() {
        System.out.println("e");
        SayH();
    }
}
//              
public aspect HelloAspect {
    pointcut HelloPointCut() : execution(public void SayHello.SayH());

    //  SayHello.SayH()         
    before() : HelloPointCut(){
        System.out.println("----before----");
    }

    //  SayHello.SayH()         
    after() : HelloPointCut(){
        System.out.println("----after----");
    }
}
//    
public class Main {
    public static void main(String[] args) {
        SayHello sayHello = new SayHello();
        sayHello.SayH();
        sayHello.Saye();
    }
}

出力:
----before----
H
----after----
e
----before----
H
----after----

動的プロキシ出力の場合は、次のようになります.
----before----
H
----after----
e
H

コールアウトプロセッサ
この注釈プロセッサはjavacコンパイル時に追加パラメータを追加する必要があり、AbstractProcessorを継承して注釈プロセッサを記述することで、注釈を読み込んでこのクラスに対して様々な操作を行うことができ、もちろんAOPを実現することもできるが、これは造輪子に属し、実現は複雑である.
AOPの実行時挿入(JDK動的エージェント,cglib,クラスロード時改バイトコード)
JDKダイナミックエージェント
動的エージェントは、実行時に動的に生成されたエージェントクラスであり、横断論理を挿入し、メソッドを呼び出すときに元のクラスのメソッドを呼び出さずに、エージェントクラスを呼び出すメソッドである.コンパイル時の挿入よりもパフォーマンスが劣るが、特殊なコンパイラは必要なく、実現される.
//     
public class SayHelloImpl implements SayHello {
    @Override
    public void sayH() {
        System.out.println("H");
    }

    @Override
    public void saye() {
        System.out.println("e");
        SayH();
    }
}
//    
public class ProxyAbout implements InvocationHandler {
    private SayHelloImpl o = new SayHelloImpl();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("----");
        method.invoke(o, args);
        System.out.println("----");
        return null;
    }
}
public class Main {
    public static void main(String[] args) {
        //          
        SayHello sayHello = (SayHello) Proxy.newProxyInstance(SayHello.class.getClassLoader(), 
                new Class[]{SayHello.class}, new ProxyAbout());
        //                 
        sayHello.saye();
        sayHello.sayH();
    }
}

エージェントのクラスはこのような$Proxy 0です
public final class $Proxy0 extends Proxy implements SayHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    //      
    public final void sayH() throws  {
        try {
            //    h    var1         ProxyAbout   invoke   
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void saye() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

SayH()を呼び出すと、その内部にProxyAboutのinvoke()メソッドが呼び出されます.このメソッドは、saye()を呼び出すとsayH()に追加されないすべての横断論理が呼び出されます.
cglib
CGLIBは強力で高性能なコード生成パッケージです.インタフェースを実現していないクラスにエージェントを提供し、JDKのダイナミックエージェントに良い補完を提供します.
cglibエージェントの原理は上のjdk動的エージェントの原理と似ており、実行時にエージェントクラスを動的に生成し、エージェントクラスと付き合います.違いは主にjdk動的エージェントを使用する場合、被エージェントのクラスにインタフェースを生成させなければなりませんが、cglibは必要ありません.cglibのエージェントクラスは被エージェントクラスを継承し、jdk動的エージェントは被エージェントクラスを継承しているためです.同じインタフェースでは、cglibのエージェントクラスがfinal staticによって修飾されたメソッドをエージェントできないこともあります.
もう1つは、cglibには、jdkダイナミックエージェントの反射呼び出しよりもパフォーマンスが優れた反射呼び出しを回避できるFASTCLASSメカニズムがあります.
クラスロード時にバイトコードを変更
これも、横断論理を追加する必要があるクラスをカスタムクラスローダを介して仮想マシンにロードし、ロードする前にClassファイルを修正し、ASM、Javassistのようなツールを使用することができます.
SpringのAOP処理方法
Springは、1つのインタフェースを実装するクラスでjdkダイナミックエージェントを使用し、インタフェースを実装しない場合にcglibを使用します.