Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

この記事の目的

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徹底入門

yut_arrows
Java/Kotlin/Springを書いてきましたが、書けるとは一言も言ってないです
https://yu-tarrrr.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした