支援対象地域:札幌、仙台、関東、愛知、関西、広島、福岡


はじめに

Springは、Javaでアプリケーションを作成する上で欠かすことのできないフレームワークです。 Springの中心的な機能であるDIxAOPのうちAOPについてご紹介します。。

以下の内容をご紹介します。



(1)AOP

AOPは、「アスペクト指向」と呼ばれるオブジェクト指向で対応しにくい機能に柔軟性を持たせるためのプログラミング指向です。
つまり、オブジェクトが"本来行うべき処理""本来行うべきでない処理(オマケの処理)"に分離することをいいます。
AOPを導入することで、複数のオブジェクトに記述されがちな共通の処理をまとめられるので、コードが見やすくなり、保守性が向上します

1-1 AOPで用いられる用語

AOPで用いられる代表的な用語をご紹介します。
【AOPで用いられる用語】
用語 説明
Join Point Adviceを割り込ませるタイミング
Advice AOPにより実現したい共通の処理(Join Pontで実行されるコード)
Adviceが実行されるタイミングでいくつか種類が分かれる。
《Adviceの種類》
Adviceの種類 説明
Before Join Pontのに実行される
After Join Pontの後(例外の発生に関わらず)に実行される
AfterReturning Join Pontが完全終了した後に実行される
Around Join Pontの前後に実行される
AfterThrowing Join Pontで例外発生時に実行される
Pointcut Adviceを適用する条件
AOP Proxy AOPを実現するためにAOPフレームワークによって生成されるオブジェクト
Spring Frameworkでは、「JDK dynamic proxy」と「CGLIB proxy」が提供されている。
【AOPイメージ】


(2)AOPの実現

AOPを実現するには、次の3種類があります。

《AOPの実現方法》
  1. アノテーションを用いたAOP
  2. XML形式のBean定義ファイルを用いたAOP
  3. JavaConfigを用いたAOP

2-1 アノテーションを用いたAOP

AOPにより共通化する処理があるクラス宣言の前に@Aspectアノテーションを追加します。
DI同様、AOPを実現するには、XML形式のBean定義ファイルJavaConfigが必要です。

次に、Adviceの実現についてご紹介します。
共通化する処理(メソッド)の宣言の前にアノテーションを追加します。 Adviceの種類に応じて実現方法が異なります。

【Adviceの実現】(Adviceの種類)
《Before Advice、After Adviceの実現》
アノテーション @Before("Primitive Pointcut")
@After("Primitive Pointcut")
メソッド名 任意
メソッドの引数 なし(基本的に)
戻り値 void(基本的に)

《AfterReturning Adviceの実現》
アノテーション @AfterReturning(value = "Primitive Pointcut" , returning = "変数名")
メソッド名 任意
メソッドの引数 引数の型:AOP適用対象メソッドの戻り値の型と同じにする
変数名:アノテーション内のreturning属性に指定した変数名
戻り値 void(基本的に)

《Around Adviceの実現》
アノテーション @Around("Primitive Pointcut")
メソッド名 任意
メソッドの引数 引数の型:org.aspectj.lang.ProceedingJoinPoint
変数名:任意
戻り値 AOP適用対象メソッドの戻り値の型と同じにする

《AfterThrowing Adviceの実現》
アノテーション @Around(value = "Primitive Pointcut" , throwing = "変数名")
メソッド名 任意
メソッドの引数 引数の型:キャッチしたい例外を記述する
変数名:アノテーション内のthrowing属性に指定した変数名
戻り値 void(基本的に)

最後に、Pointcutの実現についてご紹介します。Adviceの実現するアノテーション内の「Primitive Pointcut」に条件を指定することでPointcutを実現できます。
これは、Primitive PointcutというAspectJで用いられるPointcutの指定方法をSpringに持ち込んだものです。
AspectJのPrimitive Pointcutの内、executionをご紹介します。

execution([メソッドの修飾子] メソッドの戻り値の型 パッケージ.クラス名またはインタフェース.メソッド名(引数の型| , 引数の型…|) [throws 例外])
  • 呼び出し先の「メソッド(コンストラクタ)」を指定する
  • 「メソッドの修飾子」、「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); }
}
実行結果
Around Adviceは、まずAOP対象メソッドの呼び出し前に動作する
Before Adviceは、AOP対象メソッドの呼び出し前に動作する
Around Adviceは、AOP対象メソッドの呼び出し後にも動作する
After Adviceは、AOP対象メソッドの呼び出し後(完全終了後)に動作する
AfterReturning Adviceは、AOP対象メソッドの呼び出し後に動作する
name=ペン, price=100


まとめ

AOP概要」についてご紹介しました。
お役に立てると幸いです。