Spring3(DIxAOP)
はじめに
Springは、Javaでアプリケーションを作成する上で欠かすことのできないフレームワークです。 Springの中心的な機能であるDIxAOPのうちAOPについてご紹介します。。
以下の内容をご紹介します。
(1)AOP
AOPは、「アスペクト指向」と呼ばれるオブジェクト指向で対応しにくい機能に柔軟性を持たせるためのプログラミング指向です。
つまり、オブジェクトが"本来行うべき処理"と"本来行うべきでない処理(オマケの処理)"に分離することをいいます。
AOPを導入することで、複数のオブジェクトに記述されがちな共通の処理をまとめられるので、コードが見やすくなり、保守性が向上します。
1-1 AOPで用いられる用語
AOPで用いられる代表的な用語をご紹介します。用語 | 説明 | ||||||||||||
Join Point | Adviceを割り込ませるタイミング | ||||||||||||
Advice | AOPにより実現したい共通の処理(Join Pontで実行されるコード) Adviceが実行されるタイミングでいくつか種類が分かれる。 《Adviceの種類》
|
||||||||||||
Pointcut | Adviceを適用する条件 | ||||||||||||
AOP Proxy | AOPを実現するためにAOPフレームワークによって生成されるオブジェクト Spring Frameworkでは、「JDK dynamic proxy」と「CGLIB proxy」が提供されている。 |
(2)AOPの実現
AOPを実現するには、次の3種類があります。
- アノテーションを用いたAOP
- XML形式のBean定義ファイルを用いたAOP
- JavaConfigを用いたAOP
2-1 アノテーションを用いたAOP
AOPにより共通化する処理があるクラス宣言の前に@Aspectアノテーションを追加します。
DI同様、AOPを実現するには、XML形式のBean定義ファイルかJavaConfigが必要です。
次に、Adviceの実現についてご紹介します。
共通化する処理(メソッド)の宣言の前にアノテーションを追加します。
Adviceの種類に応じて実現方法が異なります。
アノテーション | @Before("Primitive Pointcut") @After("Primitive Pointcut") |
メソッド名 | 任意 |
メソッドの引数 | なし(基本的に) |
戻り値 | void(基本的に) |
アノテーション | @AfterReturning(value = "Primitive Pointcut" , returning = "変数名") |
メソッド名 | 任意 |
メソッドの引数 | 引数の型:AOP適用対象メソッドの戻り値の型と同じにする 変数名:アノテーション内のreturning属性に指定した変数名 |
戻り値 | void(基本的に) |
アノテーション | @Around("Primitive Pointcut") |
メソッド名 | 任意 |
メソッドの引数 | 引数の型:org.aspectj.lang.ProceedingJoinPoint 変数名:任意 |
戻り値 | AOP適用対象メソッドの戻り値の型と同じにする |
アノテーション | @Around(value = "Primitive Pointcut" , throwing = "変数名") |
メソッド名 | 任意 |
メソッドの引数 | 引数の型:キャッチしたい例外を記述する 変数名:アノテーション内のthrowing属性に指定した変数名 |
戻り値 | void(基本的に) |
最後に、Pointcutの実現についてご紹介します。Adviceの実現するアノテーション内の「Primitive Pointcut」に条件を指定することでPointcutを実現できます。
これは、Primitive PointcutというAspectJで用いられるPointcutの指定方法をSpringに持ち込んだものです。
AspectJのPrimitive Pointcutの内、executionをご紹介します。
- 呼び出し先の「メソッド(コンストラクタ)」を指定する
- 「メソッドの修飾子」、「throws 例外」は省略可能
- ワイルドカード「*」、「..」、「+」が利用可能
- 論理演算子が利用可能
ワイルドカード | 役割 |
* | 任意の型と一致させることが可能。 クラス名/パッケージ名の代わりとして使用可能。 |
.. | 任意(0以上)の引数と一致させることが可能。 任意(0以上)のクラス名/パッケージ名の代わりとして使用可能。 |
+ | クラス名の後に記述することで、 該当クラスやそのサブクラス及びインタフェースの実装クラスが全て指定される。 |
論理演算子 | 説明 |
|| または or | 論理和 |
&& または and | 論理積 |
! または not | 否定 |
2-2 XML形式のBean定義ファイルを用いたAOP
XML形式でBean定義を行うことで、アノテーションをクラスやインスタンスに記述しなくてもAOPを実現できるようになります。メソッドの形式は、「2-1 アノテーションを用いたAOP」と同じにしなければなりません。(Adviceの種類)
つまり、アノテーションだけがBean定義ファイルに移ったというイメージです。
2-3 JavaConfigを用いたAOP
JavaConfigは、1人で開発を行う場合に便利です。JavaConfigでAOPの定義を行う場合は、@EnableAspectJAutoProxyアノテーションをクラス宣言の前に追加します。「2-1 アノテーションを用いたAOP」のサンプルコードをご紹介します。 各クラスの階層関係は、次のようになります。
<!-- applicationContext.xml
アノテーションを有効にするためのxml形式のBean定義ファイル-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy />
<context:component-scan base-package="sample" />
</beans>
//AOPクラス
package sample.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import sample.aop.business.domain.DataRegister;
@Aspect
@Component
public class DefinedAspect {
@Before("execution(* findProduct(String))")
public void beforeAdvice() { System.out.println("Before Adviceは、AOP対象メソッドの呼び出し前に動作する"); }
@After("execution(* findProduct(String))")
public void afterAdvice() { System.out.println("After Adviceは、AOP対象メソッドの呼び出し後(完全終了後)に動作する"); }
@AfterReturning(value = "execution(* findProduct(String))" , returning = "dataRegister")
public void afterReturningAdvice(DataRegister dataRegister) { System.out.println("AfterReturning Adviceは、AOP対象メソッドの呼び出し後に動作する"); }
@Around("execution(* findProduct(String))")
public DataRegister aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
//メソッドの呼び出し前に動作するAdvice
System.out.println("Around Adviceは、まずAOP対象メソッドの呼び出し前に動作する");
//AOP対象メソッド呼び出し
DataRegister dataRegister = (DataRegister)pjp.proceed();
//メソッドの呼び出し後に動作するAdvice
System.out.println("Around Adviceは、AOP対象メソッドの呼び出し後にも動作する");
return dataRegister;
}
}
//プレゼンテーション層パッケージ
package sample;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sample.aop.business.domain.DataRegister;
import sample.aop.business.service.DataCall;
//コントローラの代わとしてmain()メソッドを実行する
public class SampleRun {
public static void main(String[] args) {
SampleRun sampleRun = new SampleRun();
sampleRun.execute();
}
@SuppressWarnings("resource")
public void execute() {
String path = "/sample/config/applicationContext.xml";
BeanFactory ctx = new ClassPathXmlApplicationContext(path);
DataCall dataCall = ctx.getBean(DataCall.class);
dataCall.addProduct(new DataRegister("ペン", 100));
DataRegister dataRegister_Pen = dataCall.findByProductName("ペン");
System.out.println(dataRegister_Pen);
}
}
//ビジネスロジック層(サービス)パッケージ
package sample.aop.business.service;
import sample.aop.business.domain.DataRegister;
//インタフェース
//データアクセス層とやり取りする
public interface DataMap {
void addProduct(DataRegister dataRegister);
DataRegister findProduct(String name);
}
//ビジネスロジック層(サービス)パッケージ
package sample.aop.business.service;
import sample.aop.business.domain.DataRegister;
//インタフェース
//プレゼンテーション層からサービスを呼び出す
public interface DataCall {
void addProduct(DataRegister dataRegister);
DataRegister findByProductName(String name);
}
//ビジネスロジック層(サービス)パッケージ
package sample.aop.business.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import sample.aop.business.domain.DataRegister;
//DataCallインタフェースの実装クラス
@Component //DIコンテナに登録
public class DataCallImpl implements DataCall {
@Autowired //DIコンテナに注入したいインスタンス
private DataMap dataMap;
@Override
public void addProduct(DataRegister dataRegister) { dataMap.addProduct(dataRegister); }
@Override
public DataRegister findByProductName(String name) { return dataMap.findByProductName(name); }
}
//ビジネスロジック層(サービス)パッケージ
package sample.aop.business.service;
import sample.aop.business.domain.DataRegister;
//インタフェース
//データアクセス層とやり取りする
public interface DataMap {
void addProduct(DataRegister dataRegister);
DataRegister findProduct(String name);
}
//ビジネスロジック層(ドメイン)パッケージ
package sample.aop.business.domain;
//サービスから呼び出されたらサービスにデータを返す
public class DataRegister {
private String name;
private int price;
public DataRegister(String name , int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
@Override
public String toString(){ return "name=" + name +", price=" + price; }
}
//データアクセス層パッケージ
package sample.aop.dataaccess;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import sample.aop.business.domain.DataRegister;
import sample.aop.business.service.DataMap;
//データベースの代わりとしてMapを利用
@Component //DIコンテナに登録
public class DataMapImpl implements DataMap {
private Map<String, DataRegister> storage = new HashMap<String , DataRegister>();
@Override
public DataRegister findProduct(String name) { return storage.get(name); }
public void addProduct(DataRegister dataRegister) { storage.put(dataRegister.getName(), dataRegister); }
}
Before Adviceは、AOP対象メソッドの呼び出し前に動作する
Around Adviceは、AOP対象メソッドの呼び出し後にも動作する
After Adviceは、AOP対象メソッドの呼び出し後(完全終了後)に動作する
AfterReturning Adviceは、AOP対象メソッドの呼び出し後に動作する
name=ペン, price=100
まとめ
「AOP概要」についてご紹介しました。
お役に立てると幸いです。