Spring Boot DI/AOP

AOPで独自アノテーションの有無を判定してみた

今回は、クラスとメソッドに付与できる独自アノテーションを作成し、そのアノテーションが付与されているかどうかを判定する処理をAspectクラスで実装してみたので、そのサンプルプログラムを共有する。

前提条件

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

IntelliJ IDEA上でGradleを使ってWeb画面のSpring Bootプロジェクトを作成してみたSpring Bootのプロジェクトを新規作成を「IntelliJ IDEA」のメニューから実施しようとしたところ、無料の「Commun...

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

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

独自アノテーションのプログラムの内容は以下の通りで、クラスとメソッドにこのアノテーションを付与できるようになっている。

package com.example.demo;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Documented;

//アノテーションの付与対象をクラス(ElementType.TYPE)とメソッド(ElementType.METHOD)にする
//RetentionPolicyはclassファイルに記録され実行時に参照できるモード(Runtime)とする
//JavaDoc指定対象(@Documented)とする
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoAnnotation {
}



また、このアノテーションがクラスやメソッドに付与されているかどうかを取得する処理は、以下のAspectクラスで実装している。

package com.example.demo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Aspect
@Component
public class DemoInvocation {

    //ログ出力のためのクラス
    private Logger logger = LogManager.getLogger(DemoInvocation.class);

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

        //@DemoAnnotationアノテーション(クラス)の有無をログに出力
        Class<?> classObj = jp.getTarget().getClass();
        DemoAnnotation classAno = classObj.getAnnotation(DemoAnnotation.class);
        boolean hasClassAno = (classAno != null);
        System.out.println("hasClassAno? : " + hasClassAno);

        //@DemoAnnotationアノテーション(メソッド)の有無をログに出力
        MethodSignature jpSignature = (MethodSignature)jp.getSignature();
        Method method = jpSignature.getMethod();
        DemoAnnotation methodAno = method.getAnnotation(DemoAnnotation.class);
        boolean hasMethodAno = (methodAno != null);
        System.out.println("hasMethodAno? : " + hasMethodAno);
    }

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

}



また、build.gradleの内容は以下の通りで、AOPを利用するための設定を追加している。

plugins {
	id 'org.springframework.boot' version '2.1.7.RELEASE'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	//AOPを利用するための設定
	implementation 'org.springframework.boot:spring-boot-starter-aop'
}

さらに、application.propertiesの内容は以下の通りで、ログ出力の設定を追加している。

server.port = 8084
logging.level.root = INFO
logging.level.com.example.demo = DEBUG
logging.file = C:/work/logs/demo.log



また、コントローラクラスの内容は以下の通りで、独自アノテーションをクラスと一部メソッドに追加している。

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@DemoAnnotation
public class DemoController {

    /**
     * 初期表示画面に遷移する
     * @return 初期表示画面へのパス
     */
    @GetMapping("/")
    public String index(){
        return "index";
    }

    /**
     * メソッドに独自アノテーションが有る場合の処理
     * @return 初期表示画面へのパス
     */
    @GetMapping("/has_method_annotation")
    @DemoAnnotation
    public String has_method_annotation(){
        return "index";
    }

    /**
     * メソッドに独自アノテーションが無い場合の処理
     * @return 初期表示画面へのパス
     */
    @GetMapping("/has_no_method_annotation")
    public String has_no_method_annotation(){
        return "index";
    }
}



その他、HTMLファイルの内容は以下の通り。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index page</title>
</head>
<body>
    <form method="get" th:action="@{/has_method_annotation}">
        <input type="submit" value="メソッドのアノテーションが有る場合" />
    </form>
    <br/><br/>
    <form method="get" th:action="@{/has_no_method_annotation}">
        <input type="submit" value="メソッドのアノテーションが無い場合" />
    </form>
</body>
</html>

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

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

1) Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下の画面が表示される
サンプルプログラムの実行結果_1

2) この画面で「メソッドのアノテーションが有る場合」ボタンを押下すると、画面遷移はしないが、ログでクラス・メソッドともアノテーションが有る旨が表示される
サンプルプログラムの実行結果_2_1

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

3) さらに、この画面で「メソッドのアノテーションが無い場合」ボタンを押下すると、画面遷移はしないが、ログでメソッドのアノテーションが無い旨が表示される
サンプルプログラムの実行結果_3_1

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

要点まとめ

  • Aspectクラスで、クラスまたはメソッドのgetAnnotationメソッドを利用することで、特定のアノテーションの有無を判定することができる。