仮想オフィスを活用したITエンジニア研修とは?
- 1.AOPとは?
- 2.AOPを使うメリット
- 3.AOPを実装できるプログラミング言語とフレームワーク
- 4.AOPを実装する前に押さえておきたい用語
- 5.SpringフレームワークでのAOPの使い方
- 6.覚えておきたいアノテーション
フレームワークを勉強する中で、なじみのない「AOP」という単語が出てきて、意味がわからずに困ってしまったことはないでしょうか。
本記事では、「AOP」について詳しく解説していきます。
1.AOPとは?
AOPは「Aspect Oriented Programming」の略で、日本語に直訳すると「アスペクト思考プログラミング」です。
「アスペクト」には「外観」のほかに「継続」や「反復」といった意味がありますが、プログラミングの世界における「アスペクト」は、「関心事」と訳します。
ここでいう「関心事」とは、「いろいろな場所で実行することがある共通処理」を指します。
例えば、「すべてのメソッド開始時にはログを出力する」ということを実現したい場合、すべてのメソッドに「ログを出力する処理」を記述するのは少し面倒です。
AOPを利用し、「ログ出力」を「関心事」として定義することで、すべてのメソッドに対して「ログ出力」の処理を差し込むことが可能になります。
2.AOPを使うメリット
概要を理解したところで、AOPを使うメリットを押さえていきましょう。
①開発の効率が上がる
AOPを活用することで、同じ処理を複数箇所に記述する必要がなくなります。
プログラミングの際に共通処理を気にする必要がなくなると、そのクラスで実現する処理の実装に注力できるのです。
実装するコードの量も減るため、結果として開発の効率が上がります。
②共通処理のメンテナンスが楽になる
AOPの大きな特徴として、「共通処理を外部から注入する」という点が挙げられます。
AOPで注入する処理は共通処理として一元管理されるため、共通処理を変更したい場合にはその部分を変更するだけで済むのです。
複数箇所を一度に変更するようなことがなくなるので、メンテナンスの面でも見ても非常に有用と言えるでしょう。
③オブジェクト指向のメリットは維持できる
オブジェクト指向の特徴として、「カプセル化」という概念があります。
オブジェクトの垣根を越えて共通処理を実装する場合、このカプセル化の概念が崩される場合があります。
これを解決するために利用するのがAOPです。
AOPを適用することで外部から処理を別途注入することが可能となるため、
カプセル化を崩さずに共通処理を実現できるようになります。
3.AOPを実装できるプログラミング言語とフレームワーク
続いて、実際にAOPが使える言語やフレームワークを紹介します。
AOPは多くのフレームワークで採用されているので、ぜひ活用していきましょう。
①AOPに対応した言語
標準でAOPに対応している言語はありませんが、各言語を拡張する形で登場した言語が存在します。
AspectJは、Javaに対してアスペクト思考を適用するために拡張された言語です。
同様に、C++ を拡張した「AspectC++」や、Rubyを拡張した「AspectR」が存在します。
②Spring Framework
Java向けのフルスタックフレームワークであるSpring FrameworkもAOPを標準でサポートしています。
特定のアノテーションを付与することで、その処理を注入してくれます。
③JBoss AOP
Javaの標準であるJakarta EEでは、AOPをサポートしていません。
ですが、Jakarta EE実装のJBossプロジェクトでは、AOPを実現するための「JBoss AOP」が提供されています。
JBoss AOPを利用することで、Jakarta EE基準のJavaアプリケーションに対してAOPを実装できるようになります。
④Seasar2
Seasar2は、Java向けのフレームワークです。主にDI(依存性の注入)に特化したフレームワークで、同様にAOPにも対応しています。
2016年時点でサポートが終了しているため、利用する際には注意しましょう。
4.AOPを実装する前に押さえておきたい用語
AOPを使ったプログラミングをする際に、押さえておきたい単語を紹介します。
①アドバイス(Advice)
処理内容を記述したものを「アドバイス」と呼びます。
②ジョインポイント(JoinPoint)
アドバイスを注入する場所を「ジョインポイント」と呼びます。
アドバイスを結合(ジョイン)する場所(ポイント)と覚えましょう。
③ポイントカット(PointCut)
アドバイスをジョインするポイントにおいて、処理を実行する条件などを指定したい場合もあるでしょう。
その指定方法を「ポイントカット」と呼びます。
アドバイスとジョンポイント、ポイントカットの覚え方
アドバイスとジョインポイント、ポイントカットはセットで覚える必要があるので注意しましょう。
覚え方のコツとしては、
【ポイントカット】の場合、【アドバイス】を【ジョインポイント】へ注入する
というように、ひとつの文章として覚えておくとよいでしょう。
④アドバイザ(Advisor)
実際にAOPを実装する際には、アドバイスとポイントカットはセットで記述することになります。
アドバイスとポイントカットの両方をもつモジュールクラスを作成する場合、そのクラスを「アドバイザ」と呼びます。
⑤インターセプタ(Interceptor)
Interceptは「割り込み」の意味をもちます。
インターセプタは、本来はAOPの用語ではないため注意しましょう。
ただし、インターセプタの目的は「特定の処理に対して共通処理を注入する」であり、AOPにおけるアドバイザと同じような意味合いで利用されます。
ちなみに、AOPに非対応でもインターセプタをサポートしているフレームワークも多く存在します。
それらにあわせて、アドバイザのクラス名を「~Interceptor」とする場合もありますので、インターセプタの存在自体は覚えておいて損はないでしょう。
⑥プロキシ(Proxy)とターゲット(Target)
プロキシは「アドバイスをもつオブジェクト」、ターゲットは「プロキシされる(アドバイスが注入される)オブジェクト」を指します。
プロキシとターゲットは対になる単語ですので、こちらもセットで覚えておきましょう。
5.Spring FrameworkでのAOPの使い方
それでは、Spring Frameworkを用いて実際にAOPを実現してみましょう。
今回は、Spring BootプロジェクトでAOPを実現してみます。
①pom.xmlに設定する
Spring Frameworkでは、AOPの機能を提供する「spring-aop」がライブラリとして提供されています。
AOPは、DIとならびSpringのコア機能とも言える存在であり、Spring Bootの依存関係にAOPがあらかじめ含まれています。
そのため、Spring Bootの場合であれば依存関係を追加することなくAOPの機能を利用できます。
Spring AOPが依存関係にない場合には?
もし、Spring FrameworkのプロジェクトにAOPの依存関係がない場合には、以下の依存関係を追加しましょう。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
②AOPを実装する
それでは、AOPを実装してみましょう。
今回は、コントローラクラスの処理前にログを出力するアドバイザを作成していきます。
package jp.co.trainocamp.sample.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MethodLogger {
@Before("within(jp.co.trainocamp.sample.aop.controller.*)")
public void logBefore(JoinPoint joinPoint) {
// JoinPointからクラス名を取得
String className = joinPoint.getTarget().getClass().getName();
// JoinPointからメソッド名を取得
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("[%s] %s Start!", className, methodName));
}
}
このアドバイザは、「jp.co.trainocamp.sample.aop.controller」パッケージのクラスに対し、ターゲットとなるクラスのメソッド実行前に行うアドバイスの処理を記述しています。
③コントローラーファイルを実装する
次に、アドバイザが注入される対象(ターゲット)となるコントローラを実装します。
package jp.co.trainocamp.sample.aop.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class RootController {
@GetMapping("/")
public String getMessage() {
return "index";
}
}
また、動作確認のためテンプレートとなるHTMLを用意しましょう。
src/main/resources/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
Hello, AOP.
</body>
</html>
④ 実行する
プログラムの用意ができたら、アプリケーションを実行しましょう。
ブラウザを開き、「http://localhost:8080」へアクセスします。
以下のように表示されることで、コントローラが正常に動作することが確認できました。
次にコンソールを見てみましょう。
以下のように、MethodLoggerクラスで定義したメッセージが出力されていれば、AOPの実装ができています。
6.覚えておきたいアノテーション
最後に、Spring AOPを使う際に覚えておきたいアノテーションを紹介します。
①ジョインポイントに関するアノテーション
アドバイスの注入ポイントを指定するポイントカットの指定方法として、以下のようなアノテーションが存在します。
状況に応じて使い分けるとよいでしょう。
アノテーション | 説明 |
@Before | ターゲットの処理前に実行する |
@After | ターゲットの処理後に実行する |
@Around | ターゲットのメソッドの代わりに実行される (アドバイスの中でターゲットの実行が必要になる) |
@AfterReturning | ターゲットが正常終了したら実行する (例外がスローされたら実行されない) |
@AfterThrowing | ターゲットが例外をスローしたら実行する |
②ポイントカットの指定
AOPを実装する場合、ジョインポイントだけでなくポイントカットも指定する必要があります。
execution
executionは、対象とするターゲットを正規表現の形式で表現します。
例えば、以下のような記述をした場合、publicで公開された「doGet」という名前のメソッドに対してアドバイスを注入します。
@Before("execution(public * doGet(..))")
また、メソッド名の一部を正規表現として記述することもできます。
@Before("execution(public * do*(..))")
within
特定のクラスやパッケージに対して適用する場合には、withinを利用します。
例えば、以下の場合には「jp.co.trainocamp.controller」のすべてのクラスが対象になります。
@Before("within(jp.co.trainocamp.sample.aop.controller.*)")
target
特定のクラスオブジェクトに対して適用したい場合にはtargetを利用します。
targetがwithinと異なる点は、指定したクラスを継承したクラスにも適用されるという点です。
例えば以下のような指定をした場合、TestServiceはもちろんのこと、TestServiceを継承したクラスも対象になります。
@Before("target(jp.co.trainocamp.sample.aop.service.TestService)")
ただし、TestServiceに記述のない(継承していない)メソッドには適用されないため注意しましょう。
args
argsを利用することで、指定した引数をもつメソッドのみを対象とできます。
ただし、単体で利用できず、他のポイントカットと併用する必要があるため注意しましょう。
例として、Stringを引数とするdoGetメソッドを対象とする場合の処理を紹介します。
@Before("execution(* *..doGet(..)) && args(java.lang.String)")
@annotation
@annotationを指定することで、特定のアノテーションをもつメソッドを対象にできます。
他のポイントカットと異なり、「@」をつけることを忘れないようにしましょう。
例として、SpringBootの「@GetMapping」をもつメソッドを対象とする方法を紹介します。
@Before("@annotation(org.springframework.web.bind.annotation.GetMapping)")