AOP
spring-boot
SpringBoot

[Spring boot初心者向け]Aopの使い方


この記事の目的

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を使用しているので以下の通りです。


build.gradle

compile('org.springframework.boot:spring-boot-starter-aop')


次に、Aspectの実装をしていきます。


SampleAspect.java

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を使ったサンプル


SampleAspect.java

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を使ったサンプル


SampleAspect.java

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を使ったサンプル


SampleAspect.java

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を使ったサンプル


SampleAspect.java

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を使ったサンプル


SampleAspect.java

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式について簡単にまとめておきます。

基本的には以下の通りです。


sample.java

execution(*{パッケージ名}.{型、クラス}.{メソッド}({引数}))


例えば、


sample.java

execution(*com.sample.user.UserService.*(..)))


ですと、com.sample.user.UserServiceの任意のメソッドを対象にします。

他にも、


sample.java

execution(*com.sample.user.UserService.find*(..)))


ですと、com.sample.user.UserServiceの名前がfindから始まるメソッドを対象にします。

ここには書いてないですが、他にもPointCutの書き方はたくさんあるみたいなので調べてみてください。


参考にしたサイト・書籍

Spring徹底入門