【Java難病】Javaコアライブラリによる簡単なAOP

13324 ワード

Springは非常にホットなオープンソースフレームワークであり、AOP(カットプログラミング向け)はSpringの最も重要な概念の一つであり、AOPの思想をよりよく理解し、学習するために、コアライブラリを使用して一度に良い方法を実現することができます.
まずAOPの概念を紹介して、AOP(Aspect Oriented Programming)、つまり切面プログラミングに向いて、いわゆる切面プログラミングに向いて、1つの横断面の角度からコードの思想を設計して、伝統的なOOP思想はパッケージで継承して多態で1種の縦方向の階層関係を構築して、しかし横方向の関係を定義することに適していませんAOPの思想はこれに対してとても良い補充を行いました.
例えば、ログ管理コードは多くのオブジェクト階層に横方向に分散することが多いが、それに対応するオブジェクトのコア機能は無関係と言える.権限検証、デバッグ出力、トランザクションなど、類似のコードも多く、コードの多重化や管理に不利である.
このときAOPテクノロジーは、「横断」テクノロジーを使用してオブジェクトの内部を深くカプセル化し、複数のクラスに影響を及ぼす共通の動作を再利用可能なモジュールにカプセル化し、「Aspect」、すなわち断面と命名した.「接面」とは、単純に言えば、ビジネスに関係なく、ビジネスモジュールに共通に呼び出された論理または責任をカプセル化し、システムの重複コードを低減し、モジュール間の結合度を低減し、後続の操作性とメンテナンス性に有利である.
ではAOPはどのように実現したのでしょうか.
答えはダイナミックエージェントです(エージェントについては別の章で詳しく紹介しますが、ここでは省略します).動的エージェントを実装するには、JDK動的エージェントとCGLib動的エージェントの2つの方法があります.
では、それぞれ2つの方法で簡単な栗を作ります.
まず,計算インタフェースICalculatorとそのインタフェースを実現した計算機クラスCalculatorImplがあると仮定するシーンを設計する.
public interface ICalculator {
    //    
    public int add(int a,int b);
    //  
    public int subtract(int a,int b);
    //  
    public int multiply(int a,int b);
    //  
    public int devide(int a,int b);
}
public class CalculatorImpl implements ICalculator{
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }

    @Override
    public int multiply(int a, int b) {
        return a * b;
    }

    @Override
    public int devide(int a, int b) {
        return a / b;
    }
}

元の計算機クラスの内部コードを変更せずに計算機の各メソッドの使用回数を記録するにはどうすればいいですか?
ダイナミックエージェントができたら、実は簡単です.まずクラスを作成してInvocationHandlerインタフェースを実現し、invokeメソッドを上書きします.
public class TestHandler implements InvocationHandler {

    private Object targetObject;
    private int useTimes;

    //
    public Object bind(Object targetObject){
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //do something
        before();
        Object result = method.invoke(targetObject,args);
        after();
        return result;
    }
private void before(){ System.out.println("we can do something before calculate."); } private void after(){ useTimes++; System.out.println(" :"+useTimes+" "); } }

コードが少し多いようですが、主な方法はinvoke方法で、中のObject result=method.invoke(targetObject,args);元のパラメータで元のメソッドを実行し続けることに相当します.ここのbeforeとafterはカスタム関数であり、ここの使用回数統計など、ターゲットコードの実行前後にやりたいことをすることができます.
bindメソッドでは、ターゲットエージェントオブジェクトが転送され、エージェントクラスインスタンスが返されます.次に、使用方法を見てみましょう.
public class TestProxy {
    public static void main(String[] args) {
        TestHandler proxy = new TestHandler();
        ICalculator calculator = (ICalculator)proxy.bind(new CalculatorImpl());
        int result = calculator.add(1,2);
        System.out.println("result is:"+result);
        result = calculator.subtract(3,2);
        System.out.println("result is:"+result);
        result = calculator.multiply(4,6);
        System.out.println("result is:"+result);
        result = calculator.devide(6,2);
        System.out.println("result is:"+result);
    }
}

まずTestHandlerを定義し,bind法によりエージェントインスタンスを取得した後,このインスタンスを直接使用することができるようになった.実行結果は次のとおりです.
we can do something before calculate.
   :1 
result is:3
we can do something before calculate.
   :2 
result is:1
we can do something before calculate.
   :3 
result is:24
we can do something before calculate.
   :4 
result is:3

これにより,CalculatorImpl内部コードを修正せずにコードを拡張することを実現した.
次にCGLib方式で一度実現する.
まず、MethodInterceptorインタフェースを実装し、interceptメソッドを上書きするクラスを作成します.他のコードはJDKエージェントを使用するのと大きく異なり,エージェントオブジェクトを取得する過程だけが異なる.
public class CGLibProxy implements MethodInterceptor {
    private int useTimes;
    private Object target;

    public Object getInstance(Object target){
        this.target=target;
        Enhancer enhancer =new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }

    private void before(){
        System.out.println("we can do something before calculate.");
    }

    private void after(){
        useTimes++;
        System.out.println("   :"+useTimes+" ");
    }
}

テスト:
public class TestCGLibProxy {
    public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        ICalculator calculator = (ICalculator) cgLibProxy.getInstance(new CalculatorImpl());
        int result = calculator.add(1,2);
        System.out.println("result is:"+result);
        result = calculator.subtract(3,2);
        System.out.println("result is:"+result);
        result = calculator.multiply(4,6);
        System.out.println("result is:"+result);
        result = calculator.devide(6,2);
        System.out.println("result is:"+result);
    }
}

実行結果は次のとおりです.
we can do something before calculate.
   :1 
result is:3
we can do something before calculate.
   :2 
result is:1
we can do something before calculate.
   :3 
result is:24
we can do something before calculate.
   :4 
result is:3

今、私たちは同じ結果を得ました.(cglib-2.2.2.jar asm-3.3.jarの2つのパッケージをインポートする必要があります)
2つの方法にはそれぞれ長さがあり、JDKエージェントはまず1つのインタフェースを設定してからエージェントを実現する必要があります.これはその欠点であり、その利点でもあります.欠点はこのように面倒で、すでにパッケージされていて、インタフェースを実現していないクラスをエージェントすることができません.CGLibエージェントの方法ではインタフェースを使用する必要はありません.しかし、そのため、JDKエージェントはクラス内のインタフェースを上書きするメソッドのみをブロックし、CGLibはクラスのすべてのメソッド呼び出しをブロックします.両者にはそれぞれメリットとデメリットがあるので、具体的な状況を具体的に分析する必要があります.Springでも2つのエージェントモードが混在している.
 本当に大切なものは、目では見えない.