AOP(Aspect Oriented Programming)を利用することで、複数のプログラムに共通する処理を、アスペクト(Aspect)と呼ばれる別のプログラムに集約することができるが、Aspectのクラスに@Orderアノテーションを付与することで、Aspect内のメソッドの実行順序を制御することができる。
今回は、Aspectのクラスに@Orderアノテーションを付与した場合の動作を確認してみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
DemoInvocation1クラスの内容は以下の通りで、@Orderアノテーションの値に10を指定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | 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を指定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 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アノテーションを指定していない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 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内のメソッドの実行順序を制御することができる。