AOPの静的エージェントと動的エージェント


転載先:http://listenzhangbin.com/post/2016/09/spring-aop-cglib/
AOP(Aspect Orient Prograamming)は、一般的には、指向プログラミングと呼ばれ、対象に向けた補足として、処理システムにおいて各モジュールに分布する横断的な注目点、例えば、事務管理、ログ、キャッシュなどに用いられます.AOP実現の鍵はAOPフレームが自動的に作成されるAOPエージェントであり、AOPエージェントは主に静的エージェントと動的エージェントに分けられ、静的エージェントの代表はAsppectJである.動的エージェントはSpring AOPに代表される.本論文はAspectJとSpring AOPの実現をそれぞれ分析して紹介します.
AsppectJのコンパイルでAOPを強化します.
前に述べたように、AsppectJは静的エージェントの強化であり、静的エージェントとはAOPフレームワークがコンパイル段階でAOPエージェントクラスを生成するので、コンパイル時に強化されるとも言われています.
実例をあげて話す.まず普通のハロークラスがあります.
public class Hello {
    public void sayHello() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        h.sayHello();
    }
}
AsppectJを使ってAsppectを作成します.
public aspect TxAspect {
    void around():call(void Hello.sayHello()){
        System.out.println("     ...");
        proceed();
        System.out.println("     ...");
    }
}
ここではSpringのような事務の場面をシミュレーションした.AsppectJのコンパイラを使ってコンパイルします.
ajc -d . Hello.java TxAspect.aj
コンパイルが完了したら、このハロークラスを実行します.以下の出力が見られます.
     ...
hello
     ...
明らかに、AOPはすでに発効しました.一体、AsppectJはHello類を修正しないで、ハロー類のために新しい機能を追加したのですか?
コンパイルしたHello.classを確認してください.
public class Hello {
    public Hello() {
    }

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

    public static void main(String[] args) {
        Hello h = new Hello();
        sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
    }
}
これはAsppectJのスタティックエージェントであり、コンパイル段階でAsppectをJavaバイトコードに編入し、実行する時は強化された後のAOPオブジェクトである.
public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
        System.out.println("     ...");
        ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
        System.out.println("     ...");
    }
Asppectがコンパイルした後のclassファイルから、実行の論理がより明確に見られます.プロシージャ方法は、被プロキシクラスをリピートして実行する方法です.
Spring AOPでAOPを実現します.
Aspring AOPで使用されている動的エージェントとは、AOPフレームはバイトコードを修正することなくメモリに一時的にAOPオブジェクトを生成し、このAOPオブジェクトは目標オブジェクトのすべての方法を含み、特定の接点で強化処理を行い、元のオブジェクトを元に戻す方法である.
Spring AOPにおける動的エージェントは主に2つの方法があり、JDKダイナミックエージェントとCGLOIBダイナミックエージェントがあります.JDK動的エージェントは、反射によってプロキシされたクラスを受信し、プロキシが要求されるクラスは、インターフェースを実装しなければならない.JDKダイナミックエージェントの中核は、InvocationHandlerインターフェースとProxyクラスです.
ターゲットクラスがインターフェースを実現していない場合、Spring AOPはCGLOIBを使用してターゲットクラスを動的に代理することを選択します.CGLOIB(Code Generation Library)は、コード生成のクラスであり、実行時にある種類のサブクラスを動的に生成することができます.CGLOIBは継承方式による動的エージェントですので、ある種類がfinalと表記されていると、CGLOIBを動的エージェントとして使用することはできません.
以上の言い方を検証するために、簡単なテストができます.まずインターフェースを実現する状況をテストします.
インターフェースを定義する
public interface Person {
    String sayHello(String name);
}
実装クラス
@Component
public class Chinese implements Person {

    @Timer
    @Override
    public String sayHello(String name) {
        System.out.println("-- sayHello() --");
        return name + " hello, AOP";
    }

    public void eat(String food) {
        System.out.println("    :" + food);
    }

}
ここの@Timer注解は自分で定義した普通の注釈で、Pointcutをマークします.
Asppectを定義する
@Aspect
@Component
public class AdviceTest {

    @Pointcut("@annotation(com.listenzhangbin.aop.Timer)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before() {
        System.out.println("before");
    }
}
実行
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

    //      Person     
    @Autowired
    private Person chinese;

    @RequestMapping("/test")
    public void test() {
        chinese.sayHello("listen");
        System.out.println(chinese.getClass());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}
出力
before
-- sayHello() --
class com.sun.proxy.$Proxy53
このタイプはcomp.sun.proxy.53で、前述のProxy類ですので、ここのSpring AOPはJDKのダイナミックエージェントを使用しています.
また、インターフェースを実現しない状況を見て、Chinese類を修正します.
@Component
public class Chinese {

    @Timer
//    @Override
    public String sayHello(String name) {
        System.out.println("-- sayHello() --");
        return name + " hello, AOP";
    }

    public void eat(String food) {
        System.out.println("    :" + food);
    }

}
実行
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

    //   Chinese   
    @Autowired
    private Chinese chinese;

    @RequestMapping("/test")
    public void test() {
        chinese.sayHello("listen");
        System.out.println(chinese.getClass());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}
出力
before
-- sayHello() --
class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168
CGLOIBによってクラスが強化された、いわゆる動的エージェントが見られます.ここのCGLOIBエージェントはSpring AOPの代理であり、このクラスはいわゆるAOPエージェントであり、AOPエージェント類は接点で動的に補強処理に織り込まれている.
結び目
AspectJはコンパイル時にターゲットオブジェクトを強化し、Spring AOPの動的エージェントは各動作時に動的に強化され、AOPプロキシオブジェクトを生成し、AOPプロキシオブジェクトを生成するタイミングが違っています.相対的にAsppectJの静的プロキシ方式はより良い性能を持っていますが、AspectJは特定のコンパイラを必要として処理します.Spring AOPは特定のコンパイラ処理が不要です.
参考文献
  • Spring AOP実現原理とCGLOIBアプリケーション
  • Spring容器AOPの実現原理——ダイナミックエージェント
  • AOPの下層実装-CGLOIB動的エージェントとJDK動的エージェント