トッカンソフトウェア

SpringFrameworkでAOP(アスペクト指向プログラミング)(Javaアプリケーション)

今回はAOP(アスペクト指向プログラミング)をやります。これを使うことでメソッドの前後に処理を追加できます。


AOPを使用すると・・・

本来の動作とは関係ない処理(ログ出力など)をメソッドから抜き出し、AOPのメソッドに移動させることにより、
メソッドの中身がすっきりします。

イメージ

元のexecuteメソッド内には開始、終了のログ出力処理が入っていますが、これは本来の動作とは関係ない処理
なので別メソッドにしてしまいます。AOPを使用することでexecuteメソッドが呼ばれたときにbeforeメソッド、
afterメソッドが自動的に呼ばれます。

呼び出し元はexecuteメソッドを呼ぶだけでAOP使用前、後で処理の修正が必要なく、executeメソッド内に
beforeメソッド、afterメソッドを呼ぶ処理を記述する必要はありません。

AOPを使用することによりまったく別の場所にbeforeメソッド、afterメソッドを書くことが出来ます。

まずは動かしてみましょう


pom.xml
				
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>springTest</groupId>
	<artifactId>springTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.3</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.2.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.2.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.2.3.RELEASE</version>
		</dependency>
	</dependencies>
</project>


			
過去に追加したものはシンプルにするため消しました。今回用にspring-aspectsを追加しています。


Spring用の設定ファイル(SpringTest.xml)
				
<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-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean id="helloWorld" class="spring.test.SpringBean">
	</bean>

	<context:component-scan base-package="spring.test" />

	<aop:aspectj-autoproxy />

</beans>

			
過去に追加したものはシンプルにするため消しました。今回用にaspectj-autoproxyを追加してます。


実行対象となるJavaクラス(HelloWorldTest.java)
				
package spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class HelloWorldTest {
	public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext("C:\\workspace\\springApp\\SpringTest.xml");

		SpringBean bean = (SpringBean) context.getBean("helloWorld");
		bean.show("Test");
	}
}


			
このソースも一部変えました。DIしてから"Test"を引数に指定してshowメソッドを読んでいるだけです。


設定ファイルよりSpringがデータをセットするBeanクラス(SpringBean.java)
				
package spring.test;

public class SpringBean {

	public String show(String msg) {
		System.out.println("Hello World!!" + msg);
		return "Called";
	}
}


			
引数に文字列を追加してコンソールに出力するだけの処理をしています。


AOPの処理を行うクラス(SpringAspect.java)
				
package spring.test;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SpringAspect {

	@Before("execution(* spring..*(..))")
	private void before(JoinPoint jp) {
		System.out.println("Before:" + jp.getSignature().getName());
		Object[] args = jp.getArgs();
		for (Object o : args) {
			System.out.println("Before:" + o);
		}
	}

}


			
処理開始前に呼ばれる処理になります。@Beforeアノテーションの中で、どの条件で呼ばれるかの指定を行っています。


それでは実行してみましょう。

Before が付いているのがAOPのメソッドが出力しています。
"Hello World!!" が本来のメソッドで出力してます。



上の例では@Beforeだけをやりましたが、他にも@Around、@After、@AfterReturning などがあります。

アノテーション 説明
@Before メソッド実行前に呼ばれます
@After メソッド実行後に呼ばれます
@AfterReturning メソッド終了時に正常終了の場合、呼ばれます
@Around メソッド実行前に呼ばれ、この中でメソッドを実行します
@AfterThrowing 例外発生時に呼ばれます


実行対象となるJavaクラス(HelloWorldTest.java)
				
package spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class HelloWorldTest {
	public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext("C:\\workspace\\springApp\\SpringTest.xml");

		SpringBean bean = (SpringBean) context.getBean("helloWorld");
		// 引数あり
		String ret = bean.show("Test");
		System.out.println("---------- " + ret + " ----------");

		// 引数なし
		bean.show();
		System.out.println("----------  ----------");

		// 例外発生
		bean.show(null);
	}
}


			
bean.showを3回実行させています。1回目は引数あり、2回目は引数なし、3回目は例外発生させています。


設定ファイルよりSpringがデータをセットするBeanクラス(SpringBean.java)
				
package spring.test;

public class SpringBean {

	public String show(String msg) {
		System.out.println("3_show msg Called ->" + msg.trim());
		return "show Return";
	}

	public void show() {
		System.out.println("3_show Called");
	}
}


			
trimメソッドを実行させることにより、引数がNullの場合、NullPointerExceptionを発生させています。


AOPの処理を行うクラス(SpringAspect.java)
				
package spring.test;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SpringAspect {

	private void showJoinPoint(String from, JoinPoint jp) {
		System.out.println(from + ":" + jp.getTarget().getClass().getSimpleName() + "." + jp.getSignature().getName());
		Object[] args = jp.getArgs();
		for (Object o : args) {
			System.out.println(from + ":" + o);
		}
	}

	@Before("execution(* spring..*(..))")
	private void before(JoinPoint jp) {
		showJoinPoint("2_Before", jp);
	}

	@Around("execution(* spring..*(..))")
	private String around(ProceedingJoinPoint pjp) throws Throwable {
		showJoinPoint("1_Around", pjp);
		String ret = (String) pjp.proceed();
		System.out.println("4_Around Retun:" + ret);
		return ret;
	}

	@After("execution(* spring..*(..))")
	private void after(JoinPoint jp) {
		showJoinPoint("5_After", jp);
	}

	@AfterReturning(value = "execution(* spring..*(..))", returning = "str")
	private void afterReturn(JoinPoint jp, String str) {
		showJoinPoint("6_AfterRet", jp);
		System.out.println("7_AfterRet:" + str);
	}

	@AfterThrowing(value = "execution(* spring..*(..))", throwing = "e")
	private void afterThrowing(Exception e) {
		System.out.println("8_AfterThrow:" + e.toString());
	}
}

			


実行結果



ページのトップへ戻る