Spring Boot DI/AOP

AOPでOrderアノテーションを利用して実行順序を制御してみた

AOP(Aspect Oriented Programming)を利用することで、複数のプログラムに共通する処理を、アスペクト(Aspect)と呼ばれる別のプログラムに集約することができるが、Aspectのクラスに@Orderアノテーションを付与することで、Aspect内のメソッドの実行順序を制御することができる。

今回は、Aspectのクラスに@Orderアノテーションを付与した場合の動作を確認してみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

Spring BootでAOPを利用してみたSpringフレームワークの基本として、AOP(Aspect Oriented Programming)という概念がある。 AOP...

サンプルプログラムの作成

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

DemoInvocation1クラスの内容は以下の通りで、@Orderアノテーションの値に10を指定している。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(10)
public class DemoInvocation1 {

    // ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoInvocation1.class);

    /**
     * Beforeアノテーションにより、指定したメソッドの前に処理を追加する
     * Beforeアノテーションの引数には、Pointcut式 execution(戻り値 パッケージ.クラス.メソッド(引数))
     * を指定し、ここではControllerクラスの全メソッドの実行前にログ出力するようにしている
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @Before("execution(* com.example.demo.*Controller.*(..))")
    public void beforeAnnotationLog1(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called beforeAnnotationLog1 : " + signature);
    }

    /**
     * Afterアノテーションにより、指定したメソッドの後に処理を追加する
     * Afterアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @After("execution(* com.example.demo.*Controller.*(..))")
    public void afterAnnotationLog1(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterAnnotationLog1 : " + signature);
    }

    /**
     * AfterReturningアノテーションにより、指定したメソッドが正常終了した場合に、
     * 指定したメソッドの後に処理を追加する
     * AfterReturningアノテーションの引数には、Pointcut式を指定
     *
     * @param jp          横断的な処理を挿入する場所
     * @param returnValue 指定したメソッドの戻り値
     */
    @AfterReturning(value = "execution(* com.example.demo.*Controller.*(..))"
            , returning = "returnValue")
    public void afterReturningAnnotationLog1(JoinPoint jp, Object returnValue) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterReturningAnnotationLog1 : " + signature
                + ", returning : " + returnValue);
    }

    /**
     * Aroundアノテーションにより、指定したメソッドの前後に処理を追加する
     * Aroundアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @return 指定したメソッドの戻り値
     */
    @Around("execution(* com.example.demo.*Controller.*(..))")
    public Object aroundAnnotationLog1(ProceedingJoinPoint jp) {
        //返却オブジェクトを定義
        Object returnObj = null;
        //指定したクラスの指定したメソッド名・戻り値の型を取得
        String signature = jp.getSignature().toString();
        //開始ログを出力
        log.debug("called aroundAnnotationLog1 start : " + signature);
        try {
            //指定したクラスの指定したメソッドを実行
            returnObj = jp.proceed();
        } catch (Throwable t) {
            log.error("error aroundAnnotationLog1 : ", t);
        }
        //終了ログを出力
        log.debug("called aroundAnnotationLog1 end : " + signature);
        //指定したクラスの指定したメソッドの戻り値を返却
        //このように実行しないと、Controllerクラスの場合、次画面遷移が行えない
        return returnObj;
    }

}

DemoInvocation2クラスの内容は以下の通りで、@Orderアノテーションの値に20または5を指定している。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(20)
//@Order(5)
public class DemoInvocation2 {

    // ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoInvocation2.class);

    /**
     * Beforeアノテーションにより、指定したメソッドの前に処理を追加する
     * Beforeアノテーションの引数には、Pointcut式 execution(戻り値 パッケージ.クラス.メソッド(引数))
     * を指定し、ここではControllerクラスの全メソッドの実行前にログ出力するようにしている
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @Before("execution(* com.example.demo.*Controller.*(..))")
    public void beforeAnnotationLog2(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called beforeAnnotationLog2 : " + signature);
    }

    /**
     * Afterアノテーションにより、指定したメソッドの後に処理を追加する
     * Afterアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @After("execution(* com.example.demo.*Controller.*(..))")
    public void afterAnnotationLog2(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterAnnotationLog2 : " + signature);
    }

    /**
     * AfterReturningアノテーションにより、指定したメソッドが正常終了した場合に、
     * 指定したメソッドの後に処理を追加する
     * AfterReturningアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @param returnValue 指定したメソッドの戻り値
     */
    @AfterReturning(value = "execution(* com.example.demo.*Controller.*(..))"
            , returning = "returnValue")
    public void afterReturningAnnotationLog2(JoinPoint jp, Object returnValue) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterReturningAnnotationLog2 : " + signature
                + ", returning : " + returnValue);
    }

    /**
     * Aroundアノテーションにより、指定したメソッドの前後に処理を追加する
     * Aroundアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @return 指定したメソッドの戻り値
     */
    @Around("execution(* com.example.demo.*Controller.*(..))")
    public Object aroundAnnotationLog2(ProceedingJoinPoint jp) {
        //返却オブジェクトを定義
        Object returnObj = null;
        //指定したクラスの指定したメソッド名・戻り値の型を取得
        String signature = jp.getSignature().toString();
        //開始ログを出力
        log.debug("called aroundAnnotationLog2 start : " + signature);
        try {
            //指定したクラスの指定したメソッドを実行
            returnObj = jp.proceed();
        } catch (Throwable t) {
            log.error("error aroundAnnotationLog2 : ", t);
        }
        //終了ログを出力
        log.debug("called aroundAnnotationLog2 end : " + signature);
        //指定したクラスの指定したメソッドの戻り値を返却
        //このように実行しないと、Controllerクラスの場合、次画面遷移が行えない
        return returnObj;
    }

}

DemoInvocation3クラスの内容は以下の通りで、@Orderアノテーションを指定していない。

package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DemoInvocation3 {

    // ログ出力のためのクラス
    private static Log log = LogFactory.getLog(DemoInvocation3.class);

    /**
     * Beforeアノテーションにより、指定したメソッドの前に処理を追加する
     * Beforeアノテーションの引数には、Pointcut式 execution(戻り値 パッケージ.クラス.メソッド(引数))
     * を指定し、ここではControllerクラスの全メソッドの実行前にログ出力するようにしている
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @Before("execution(* com.example.demo.*Controller.*(..))")
    public void beforeAnnotationLog3(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called beforeAnnotationLog3 : " + signature);
    }

    /**
     * Afterアノテーションにより、指定したメソッドの後に処理を追加する
     * Afterアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     */
    @After("execution(* com.example.demo.*Controller.*(..))")
    public void afterAnnotationLog3(JoinPoint jp) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterAnnotationLog3 : " + signature);
    }

    /**
     * AfterReturningアノテーションにより、指定したメソッドが正常終了した場合に、
     * 指定したメソッドの後に処理を追加する
     * AfterReturningアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @param returnValue 指定したメソッドの戻り値
     */
    @AfterReturning(value = "execution(* com.example.demo.*Controller.*(..))"
            , returning = "returnValue")
    public void afterReturningAnnotationLog3(JoinPoint jp, Object returnValue) {
        // ログを出力
        String signature = jp.getSignature().toString();
        log.debug("called afterReturningAnnotationLog3 : " + signature
                + ", returning : " + returnValue);
    }

    /**
     * Aroundアノテーションにより、指定したメソッドの前後に処理を追加する
     * Aroundアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @return 指定したメソッドの戻り値
     */
    @Around("execution(* com.example.demo.*Controller.*(..))")
    public Object aroundAnnotationLog3(ProceedingJoinPoint jp) {
        //返却オブジェクトを定義
        Object returnObj = null;
        //指定したクラスの指定したメソッド名・戻り値の型を取得
        String signature = jp.getSignature().toString();
        //開始ログを出力
        log.debug("called aroundAnnotationLog3 start : " + signature);
        try {
            //指定したクラスの指定したメソッドを実行
            returnObj = jp.proceed();
        } catch (Throwable t) {
            log.error("error aroundAnnotationLog3 : ", t);
        }
        //終了ログを出力
        log.debug("called aroundAnnotationLog3 end : " + signature);
        //指定したクラスの指定したメソッドの戻り値を返却
        //このように実行しないと、Controllerクラスの場合、次画面遷移が行えない
        return returnObj;
    }

}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-aop-order/demo

なお、OrderアノテーションとOrderedインタフェースの内容は以下のようになっているため、@Orderアノテーションの値には、任意のint型の値を指定できることと、@Orderアノテーションを指定しなかった場合は、@Orderアノテーションでint型の最大値を指定した場合と同じ扱いになることが確認できる。
Orderアノテーション

Orderedインタフェース



「EaseUS Todo Backup」は様々な形でバックアップ取得が行える便利ツールだったパソコン内のデータを、ファイル/パーティション/ディスク等の様々な単位でバックアップしたり、バックアップのスケジュール設定や暗号化設定も...

サンプルプログラムの実行結果

サンプルプログラムの実行結果は、以下の通り。

1) 以下のように、DemoInvocation2クラスで、値に20を指定した@Orderアノテーションを有効にする。
サンプルプログラムの実行結果_1

2) 1)の状態でSpring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)/」にアクセスすると、以下のように、index.htmlの画面が表示されることが確認できる。
サンプルプログラムの実行結果_2

3) 2)の状態でコンソールログに出力された結果は以下の通りで、Aspectで@Aroundアノテーション(jp.proceedメソッド実行前)・@Beforeアノテーションを付与したメソッドは@Orderアノテーションで指定した値の昇順に、@Aroundアノテーション(jp.proceedメソッド実行後)・@Afterアノテーション・@AfterReturningを付与したメソッドは@Orderアノテーションで指定した値の降順に、それぞれ実行されることが確認できる。
サンプルプログラムの実行結果_3

4) 以下のように、DemoInvocation2クラスで、値に5を指定した@Orderアノテーションを有効にする。
サンプルプログラムの実行結果_4

5) 4)の状態でSpring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)/」にアクセスすると、以下のように、index.htmlの画面が表示されることが確認できる。
サンプルプログラムの実行結果_5

6) 5)の状態でコンソールログに出力された結果は以下の通りで、3)の状態と比べ、DemoInvocation1クラスとDemoInvocation2クラスの実行順序が入れ替わっていることが確認できる。
サンプルプログラムの実行結果_6

要点まとめ

  • AOP(Aspect Oriented Programming)を利用することで、複数のプログラムに共通する処理を、アスペクト(Aspect)と呼ばれる別のプログラムに集約することができるが、Aspectのクラスに@Orderアノテーションを付与することで、Aspect内のメソッドの実行順序を制御することができる。