##この記事の目的
springbootを使って半年足らずの僕が目から鱗だったことを書いていきます。
初心者の方の参考になればと思って書きました。
(springbootを使ってる人には当たり前のことばかりなので参考にならないと思います)
##はじめに
Spring BootでWebのBEを開発している際に、ログを出力したいことがありました。その時にAOPで共通処理としてログを埋め込もうと思い、AOPについて調べて実装しました。その実装方法について書きたいと思います。
##概要
AOPはアスペクト志向プログラミング(Aspect Oriented Programming)の略で複数のクラスに点在する横断的な関心事(ログとかキャッシュとか)を中心に設計や実装を行うプログラミング手法のことです。
具体的には、@Aspectアノテーションを付与したクラスに共通処理を実装するというもの。もう少し詳しく言いますと、ジョインポイントと呼ばれる部分で共通処理を埋め込む箇所を指定し、該当部分(クラスやメソッドなど)が呼ばれたタイミングで共通処理を実行させるというものです。
では、実際にコードを見ていく前に、AOPを使う上で頻出の用語について軽くまとめておきます。
単語 | 説明 |
---|---|
Aspect | AOPの単位となる横断的な関心事を示すモジュールそのもの。「ログの出力」や「例外ハンドリング」などの関心事がAspectになる。 |
Join point | 横断的な関心事を実行するポイントのこと。SpringのAOPではメソッドの実行時。 |
Advice | Join Pointで実行されるコードのこと。 |
Pointcut | 実行対象のJoin Pointを選択する表現のこと。Spring AOPではBean定義ファイルやアノテーションを使って定義する。 |
Weaving | アプリケーションコードの適切なポイントにAspectを入れ込む処理のこと。Spring AOPでは実行時に行う。 |
Target | AOP処理によって処理フローが変更されたオブジェクトのこと。 |
##AOPの実装方法
ここから先では実際に使用する方法をまとめていきます。
まず、依存性を注入します。私の場合はgradleを使用しているので以下の通りです。
compile('org.springframework.boot:spring-boot-starter-aop')
次に、Aspectの実装をしていきます。
package com.sample.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
}
ここまで準備ができたら、あとはAdviceを実装していきます。
Spring AOPで実装可能なAdviceは以下の5種類です。
Advice | 説明 |
---|---|
Before | JoinPointの前に実行される。例外のスローをのぞいて、Join Pointの処理フローを防ぐことはできない。 |
After | JoinPointの後に実行される。Join Pointの正常終了や例外のスローにかかわらず、常に実行される。 |
AfterReturning | JoinPointが正常に終了された場合に実行される。例外がスローされた場合には、実行されない |
AfterThrowing | JoinPointで例外がスローされた場合に実行される。正常終了した場合には、実行されない |
Around | JoinPointの前後で実行される。 |
Beforeを使ったサンプル
package com.sample.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
@Before("execution(* *..*SampleClass.*(..))")
public void startLog(Joinpoint joinpoint){
System.out.println("メソッド開始: " + joinpoint.getSignature());
}
}
Afterを使ったサンプル
package com.sample.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
@After("execution(* *..*SampleClass.*(..))")
public void startLog(Joinpoint joinpoint){
System.out.println("メソッド終了: " + joinpoint.getSignature());
}
}
AfterReturningを使ったサンプル
package com.sample.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
@AfterReturning("execution(* *..*SampleClass.*(..))", returning = "sample")
public void startLog(Joinpoint joinpoint, String Sample){
System.out.println("メソッド終了: " + joinpoint.getSignature() + "戻り値 = " + sample );
}
}
AfterThrowingを使ったサンプル
package com.sample.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
@AfterThrowing("execution(* *..*SampleClass.*(..))", throwing = "ex")
public void startLog(Joinpoint joinpoint, RuntimeException ex){
System.out.println("メソッド異常終了: " + joinpoint.getSignature());
ex.printStackTrace;
}
}
Aroundを使ったサンプル
package com.sample.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aruond;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SampleAspect {
@Around("execution(* *..*SampleClass.*(..))")
public Object log(Joinpoint joinpoint) throws Throwable{
System.out.println("メソッド開始: " + joinpoint.getSignature());
try{
//実行
Object result = joinpoint.proceed();
System.out.println("メソッド終了: " + joinpoint.getSignature() + "戻り値 = " + result);
return result;
} catch (Exception ex) {
System.out.println("メソッド異常終了: " + joinpoint.getSignature());
ex.printStackTrace();
throw ex;
}
}
}
##PointCut式について
最後にPointCut式について簡単にまとめておきます。
基本的には以下の通りです。
execution(*{パッケージ名}.{型、クラス}.{メソッド}({引数}))
例えば、
execution(*com.sample.user.UserService.*(..)))
ですと、com.sample.user.UserServiceの任意のメソッドを対象にします。
他にも、
execution(*com.sample.user.UserService.find*(..)))
ですと、com.sample.user.UserServiceの名前がfindから始まるメソッドを対象にします。
ここには書いてないですが、他にもPointCutの書き方はたくさんあるみたいなので調べてみてください。
###参考にしたサイト・書籍
Spring徹底入門