AOP(Aspect Oriented Programming)を利用することで、複数のプログラムに共通する処理を、アスペクト(Aspect)と呼ばれる別のプログラムに集約することができるが、Aspectのクラスに@Orderアノテーションを付与することで、Aspect内のメソッドの実行順序を制御することができる。
今回は、Aspectのクラスに@Orderアノテーションを付与した場合の動作を確認してみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
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型の最大値を指定した場合と同じ扱いになることが確認できる。


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

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

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

4) 以下のように、DemoInvocation2クラスで、値に5を指定した@Orderアノテーションを有効にする。

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

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

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





