Spring Boot DI/AOP

AOPで対象メソッドの引数・戻り値・メソッド名・リクエストURLを取得してみた

今回は、Aspectクラスで対象メソッドの引数・戻り値・メソッド名・リクエストURLを取得してみたので、そのサンプルプログラムを共有する。

前提条件

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

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

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

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

アスペクトクラスの内容は以下の通りで、endLogメソッド内で、対象メソッドの引数・戻り値・メソッド名・リクエストURLを取得している。

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.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class DemoInvocation {

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

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

    /**
     * AfterReturningアノテーションにより、指定したメソッドが正常終了した場合に、
     * 指定したメソッドの後に処理を追加する
     * AfterReturningアノテーションの引数には、Pointcut式を指定
     *
     * @param jp 横断的な処理を挿入する場所
     * @param returnValue 指定したメソッドの戻り値
     */
    @AfterReturning(value = "execution(public * com.example.demo.*Controller.*(..))"
        , returning = "returnValue")
    public void endLog(JoinPoint jp, Object returnValue){
        //引数を取得しログに出力
        logger.info("引数 : " + getArgStr(jp));
        //戻り値を取得しログに出力
        logger.info("戻り値 : " + returnValue);
        //メソッド名を取得しログに出力
        String methodName 
            = ((MethodSignature)jp.getSignature()).getMethod().getName();
        logger.info("メソッド名 : " + methodName);
        //リクエストURLを取得しログに出力
        HttpServletRequest request 
            = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        logger.info("リクエストURL : " + request.getRequestURL().toString());
        //終了ログを出力
        String signature = jp.getSignature().toString();
        logger.info("終了ログ : " + signature);
        logger.info("");
    }

    /**
     * 指定したメソッドの引数の文字列を取得する
     *
     * @param jp 横断的な処理を挿入する場所
     * @return 指定したメソッドの引数
     */
    private String getArgStr(JoinPoint jp){
        StringBuilder sb = new StringBuilder();
        Object[] args = jp.getArgs();
        if(args.length > 0){
            for(Object arg : args){
                sb.append(arg + ", ");
            }
            sb.delete(sb.length() - 2, sb.length() - 1);
        }else{
            sb.append("(なし)");
        }
        return sb.toString();
    }

}



また、コントローラクラス・Formクラスの内容は以下の通り。

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

@Controller
@SessionAttributes(types = DemoForm.class)
public class DemoController {

    /**
     * Formオブジェクトを初期化して返却する
     * @return Formオブジェクト
     */
    @ModelAttribute("demoForm")
    public DemoForm createDemoForm(){
        DemoForm demoForm = new DemoForm();
        return demoForm;
    }

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

    /**
     * 確認画面に遷移する
     * @param demoForm demoFormオブジェクト
     * @param mav ModelAndViewオブジェクト
     * @return ModelAndViewオブジェクト
     */
    @PostMapping("/confirm")
    public ModelAndView confirm(DemoForm demoForm, ModelAndView mav){
        System.out.println("demoForm name : " + demoForm.getName());
        mav.setViewName("confirm");
        return mav;
    }

}
package com.example.demo;

import lombok.Data;

@Data
public class DemoForm {

    /** 名前 */
    private String name;

}



さらに、HTMLファイルの内容は以下の通り。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>入力画面</title>
</head>
<body>
   <p>下記必要事項を記載の上、「確認」ボタンを押下してください。</p><br/>
    <form method="post" th:action="@{/confirm}" th:object="${demoForm}">
        名前: <input type="text" th:value="*{name}" th:field="*{name}" />
        <br/><br/>
        <input type="submit" value="確認" />
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>確認画面</title>
</head>
<body>
   <p>入力内容を確認してください。</p><br/>
    <form method="post" th:object="${demoForm}">
        <span th:text="'名前: ' + *{name}">ここに名前が表示されます</span>
        <br/><br/>
        <input type="button" value="戻る" onclick="history.back();" />
    </form>
</body>
</html>



フリーランスエンジニアのエージェントは就業中でも無料で登録できるITエンジニアには、フリーランスという働き方がある。 フリーランスとは、会社や団体などに所属せず、仕事に応じて自由に契約する人のこ...

その他、build.gradle、application.propertiesの内容は以下の通り。

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'
	//lombokを利用するための設定
	compileOnly 'org.projectlombok:lombok:1.18.10'
	annotationProcessor 'org.projectlombok:lombok:1.18.10'
}
server.port = 8084
logging.level.root = INFO
logging.level.com.example.demo = DEBUG
logging.file = C:/work/logs/demo.log



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

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

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

2) このときのログ出力内容は以下の通りで、赤枠の引数・戻り値・メソッド名・リクエストURLが出力されていることが確認できる
サンプルプログラムの実行結果_2

3) 画面上で名前を入力し、「確認」ボタンを押下すると、以下のように、確認画面に入力した名前が表示される
サンプルプログラムの実行結果_3_1

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

4) このときのログ出力内容は以下の通りで、赤枠の引数・戻り値・メソッド名・リクエストURLが出力されていることが確認できる
サンプルプログラムの実行結果_4

要点まとめ

  • Aspectクラスで、JoinPointクラス等を利用して、対象メソッドの引数・戻り値・メソッド名・リクエストURLを取得することができる。