1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Springについて勉強したことまとめる#4(AOP実装編)

Posted at

こんにちは!
23卒新卒エンジニアのkamaです!
前回は、知識ベースだけではなく、ハンズオン形式で、DIの実装の仕方を学んでいきました。

今回は、前回作ったプログラムに、AOPで関数実行前後に文字列を表示させるようにします。

AOPに関して補足

#2でAOPについて説明しましたが、今回本を読んでいたら、いろいろと知らなかった事が書いてあったので、それを補足として書いていきます。

AOPのおさらいですが、AOPはアスペクト指向プログラミングのことです。
各プログラムで、メインになる機能とは別に、どのプログラムでも適宜必要になる、ログ出力、データアクセス等のサブ機能を、別のところで用意しといて、その都度必要なところに渡してあげる、というものでした。

AOPの説明自体はこれで問題ないのですが、実際にAOPをプログラムに起こすうえで必要になる知識がいくつかあるので、紹介です。

AOPの用語

まず、AOPを実装するにしても、それに用いる用語がわかっていないと、意味が分からなくなってしまうと思うので、覚えときましょう!

用語 内容
Advice 横断的関心事の実装(メソッド)のこと。ログ出力や、トランザクション管理をする関数を指す
Aspect Adviceをまとめたもの(クラス)のこと。
JoinPoint Adviceをメイン処理のどこに適用するかというタイミング、関数実行前後が基本
PointCut Adviceを挿入できる場所。例えば、関数名にgetが含まれる時、など処理する条件の定義ができる
Interceptor 処理の制御を横取り(インターセプト)するための仕組み。SpringFrameworkでは、このインターセプトという仕組みで、Adviceをメイン処理に追加したように見せる
Target Adviceが挿入される対象のこと

特に、インターセプタがどういう働きをしているのかは、知っておいたほうが頭がこんがらがりにくいと思います。

インターセプタの働き

これは、インターセプタがない状態で、横断的関心事だけ取り出してAdviceにした状態です。
image.png

これに、インターセプタを設置すると、
image.png

という形になっています。
このインターセプタというのは様々なところで耳にすることがある機能ですが、Springでは、AOP Proxyという名前を冠しており、SpringFrameworkが勝手に生成してくれるので、ユーザー側は意識せずに使うことができるようになっています。

Adviceの種類

SpringFrameworkには、Adviceを実行するために利用できるJoinPointが5種類あります。

Advice 内容                     アノテーション
Before メイン処理が実行される前に実行する      @Before
After メイン処理が正常に終了した後で実行する    @After
AfterReturning メイン処理で例外がスローされた後で実行する @AfterReturning
AfterThrowing メイン処理の実行結果を問わず終了後に実行する @AfterThrowning
Around メイン処理の実行の前後で実行される      @Around

Pointcut式

先の用語の部分でAdviceを挿入できる場所、という説明をしたPointCutですが、それを実装するために、Pointcut式というものを利用します。
Pointcut式というものを使うと、Adviceを適用するスクリプトを指定することができるようになります。
Pointcut式には、記法が複数あり、今回はexecution指示子というものを使った解説をしていきます。

execution指示子の構文
execution(戻り値の型 パッケージ名.クラス名.関数名(引数))

Pointcut式にはワイルドカードを使うことができる

ワイルドカード 効果
* 任意の文字列を表す。パッケージにつければ任意のパッケージ1階層分、引数に置けば1つ以上の引数を指す
.. 0個以上のパッケージを指す。引数に置くと、0個以上の引数を指す
+ クラス名の後に記述し、クラスとサブクラスや実装クラスすべてを表す。
EX.他のPointcut式指示子
指示子 解説
execution 関数の型や修飾子、戻り値やクラス、関数名、引数など、様々な要素から、特定の関数を対象にすることができる。
execution(戻り値の型 パッケージ名.クラス名.関数名(引数))
within 特定のパッケージまたは、クラス内のすべての関数を対象にする
within(パッケージ名..*)
target 特定のクラスまたは、インターフェースを対象にする
target(クラス名)
annotation 特定のアノテーションが付与されている関数を対象にする
@annotation(アノテーション名)
aegs 特定の引数を持つメソッドを対象にする
args(引数)

いざ実践!!

前回使用したPrefecturesOfJapanプロジェクトを使用して進めていきます!

パッケージの作成

src/main/javaを右クリックしてcom.example.demo.aopというパッケージを作成しましょう。

build.gradleの修正

現状まだAOPを利用するために必要な依存関係が追加されていないので、追加しましょう!

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-aop'//追加
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
AOPクラス作成

Adviceの作成を行うためのAOPクラスを作成します。
名前はSampleAspectとします。

クラスの作成方法は前回と同様で、aopパッケージに作成します。

作成出来たら、SampleAspectクラスに@Aspect@Componentアノテーションを与えます。

SampleAspect
package com.example.demo.aop;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect 
@Component
public class SampleAspect {

}
Advice作成

ここで、やっとAdviceの作成に取り掛かることができます!
ただ、ここで焦って誤字をすると、かな~~~~りめんどくさいエラーが出て苦しむことになる(敗北済み)ので、ちゃんと丁寧にやっていきましょう!

まずは、@Beforeアノテーションが付与されたクラスを作成するのですが、ここで注意!!
@Beforeアノテーションは、引数としてPointcut式を記入する必要があります。
このPointcut式は、すべて入力アシスタントなしで入力することになるので、うち間違えてしまうと大変なことになります

SampleAspect
    //この部分危険!!
	@Before("execution(* com.example.demo.used.*Printer.*(..))")
	public void beforeAdvice(JoinPoint joinPoint) {
		
	}

続いて、Adviceの中身を書いていきましょう。
基本的には、自分で実行したいことを書けばOKです。
引数となっているJoinPoint型に.getSignature()を使うと、呼び出した関数の情報を取得できます。

SampleAspect
	@Before("execution(* com.example.demo.used.*Printer.*(..))")
	public void beforeAdvice(JoinPoint joinPoint)  {
		
		System.out.println("\n" + new SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date()));
		System.out.println(String.format("メソッド名:%s", joinPoint.getSignature().getName()));
		System.out.println("-----------------------");
	}

ここでもう1つ注意しないといけないのが、JoinPointに関するimportです。
このJoinPointなんですが、似たものにJoinpointというものがあります。
PointのPpになっただけのものですが、別のパッケージをimportしてしまうと、getSignature()などが正常に動かないので注意が必要です。

//正しいもの
import org.aspectj.lang.JoinPoint;
//間違っているもの
import org.aopalliance.intercept.Joinpoint;

ちなみに、BeforeとAfter、After Returning、After Throwingは構文が変わらないので、割愛します。

実行

ここまで書ければ、あとは手を加える必要がありません!
びっくりするぐらい簡単に実装できましたね!!

コンソール
2023/05/24
メソッド名:prefecturePrint
-----------------------
東京です

2023/05/24
メソッド名:prefecturePrint
-----------------------
大阪やで

@Before@Afterに変更するとこうなります。

コンソール
東京です

2023/05/24
メソッド名:prefecturePrint
-----------------------
大阪やで

2023/05/24
メソッド名:prefecturePrint
-----------------------
AroundAdviceの書き方

最後に、AroundAdviceのコードだけ置いておきます!
AroundAdviceだけは構文が少し違うので注意!!

SampleAspect
	@Around("execution(* com.example.demo.used.*Printer.*(..))")
	public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        //実行前処理
		System.out.println("私が住んでいるのは");
		
        //メイン処理を実行する
		Object result = joinPoint.proceed();
		
        //実行後処理
		System.out.println("------------------");
		
        //戻り値を返す必要があれば、object型で返す。
		return result;
	}

結果

コンソール
私が住んでいるのは
東京です
------------------
私が住んでいるのは
大阪やで
------------------

あとがき

DIと比べると、やることも少なくて、簡単に実装できましたね。
私は、これを最初に見た際、めちゃくちゃ便利じゃん!!と思いました。
Unityでゲーム作ってた頃は1つのコードに数十個のDebug.log();を書くことがざらだったので...

メインのコードに一切手を加えることなく、外からデバッグ用のログ出力をできるのはめちゃくちゃいいですね!!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?