Spring AspectJの利用

AOPを実現するものとして、Javaでは「AspectJ」というライブラリが広く使われている。Spring AOPでこのAspectJを使ったAOP処理について説明する。

AspectJとpom.xmlの修正

AOPについて調べてみると、おそらく「AspectJ」というソフトウェアについて多く見つかるだろう。AspectJはJava AOPソフトウェアの事実上の標準と言ってよいほど広く使われているソフトウェアである。

Spring AOPにも、このAspectJを使ってAOPを実装するための機能が含まれている。前回使ったSpring AOPとはまた別の形でAOPを実装できるため、こちらの使い方についても学んでみよう。

では、まずAspectJを利用する準備をしよう。pom.xmlを開き、<dependencies>タグの中に次の内容を追加する。すでに記述されているspring-coreとspring-aopタグは削除してはいけない。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.6</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.6</version>
</dependency>

この例ではSpring Framework 4.1.7に合わせたバージョンを指定しているため、別のバージョンを使う場合は、それに合わせて<version>を調整する。

ここでは2つのライブラリを追加する。「AspectJ RT」はAspectJのランタイムプログラムである。このライブラリを追加するとAspectJの機能を使えるようになる。また「AspectJ Weaver」は、aspectの情報を基にaspectを構成したコードを生成するために必要なユーティリティプログラムである。Spring AOPでAspectJを使うには、この2つをセットで用意する必要がある。

Aspectクラスの作成

Aspectとして挿入する処理を持つクラスを作成してみよう。今回はAspectJを使うため、以前とは異なる形式になる。

次のコードが作成例である。com.devkuma.spring.aopパッケージに「SampleAspect」というクラス名でコードを作成する。

package com.devkuma.spring.aop;
 
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
public class SampleAspect {
 
    @Before("execution(* com.devkuma.spring.aop.SampleAopBean.*(..))")
    public void before() {
        System.out.println("before:");
    }
 
    @After("execution(* com.devkuma.spring.aop.SampleAopBean.*(..))")
    public void after() {
        System.out.println("after:");
    }
}

見てわかるように、これは非常に一般的なPOJOクラスである。どのインターフェースも実装せず、どのクラスも継承していない。違いはアノテーションが付与されていることだけである。

@Aspect
このクラスがAspectクラスであることを示す。AspectJで使用するクラスにはこのアノテーションを付与する。

@Before
このアノテーションはメソッドの前に付ける。メソッド実行前に挿入する処理であることを示すアノテーションである。Spring AOPの「before」に似たものと考えてよい。

@After
このアノテーションはメソッドの前に付ける。メソッド実行後に挿入される処理であることを示すアノテーションである。Spring AOPの「afterRunning」に相当するものと考えればよい。

アノテーション自体は簡単である。わかりにくいのは、@Beforeと@Afterの後ろの括弧()内に記述された内容である。これは次のように記述されている。

("execution (...... 割り当てるメソッドの指定 ...)")

executionの後の括弧()に、どのメソッドへこのメソッドを挿入するかを指定する。これはパッケージ名から正確にメソッドを指定する必要がある。ただし、すべての名前を完全に記述しなければならないという意味ではない。

ここではワイルドカード(*)を使えるため、それを利用して特定のパッケージやクラスのすべてのメソッドなどを指定できる。また、指定するメソッドの引数も「..」記号で不特定の引数を指定できる。

ここに書かれているものを見ると、次のようになっている。

* com.devkuma.spring.aop.SampleAopBean.*(..)

わかりやすく言えば、「不特定値」をXXと書くなら、このように書かれていることがわかる。

XX com.devkuma.spring.aop.SampleAopBean.XX(XX)

最初のXXは、publicやprivateのような指定がある場合を考えて付いている。そしてSampleAopBeanの後に付いたXXは、このクラス内にあるどのメソッドもすべて対象に指定するものである。また、(XX)は引数がどの形式でも該当するようにするためのものである。

このexecutionの書き方をある程度理解できれば、望みどおりにAOP処理の対象となるメソッドを指定できるようになる。

aopbean.xmlの作成

続いてBean設定ファイルを用意する。今回は「aopbean.xml」というファイルを新しく追加する。

「resources」フォルダに「aopbean.xml」ファイルを作成し、次のように内容を書く。

<?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: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/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <bean id="sampleAspect" class="com.devkuma.spring.aop.SampleAspect" />
    <bean id="sampleAopBean" class="com.devkuma.spring.aop.SampleAopBean">
        <property name="message" value="this is AOP bean!" />
    </bean>
 
    <aop:aspectj-autoproxy />
     
</beans>

今回は、これまで使ってきたbean.xmlに比べて記述することが多い。内容について次のように整理してみよう。

<beans>タグの属性

まず<beans>タグ部分を見てみよう。次の属性が追加されている。

xmlns : aop = "http://www.springframework.org/schema/aop"

そして、このスキーマ位置を示す値がxsi:schemaLocation値に追加されている。具体的には次の部分である。

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

これは設定ファイルで使われている<aop:aspectj-autoproxy/>を使用できるようにするためのものである。少し長いが、必ず追記する必要がある。

Bean登録

<bean id = "sampleAspect"class = "comdevkuma.spring.aop.SampleAspect"/>
<bean id = "sampleAopBean"class = "com.devkuma.spring.aop.SampleAopBean">
    <property name = "message"value = "this is AOP bean!"/>
</bean>

これはすでに何度も書いてきたBean登録タグである。SampleAspectクラスとSampleAopBeanクラスをそれぞれBeanとして登録する。

AspectJ自動プロキシ

<aop:aspectj-autoproxy/>

これはAspectJ用のタグであり、先にSpring AOPで利用したProxyFactoryBeanに相当するものを自動生成するタグである。これを記述すると、ProxyFactoryBeanで用意される機能が自動的に含まれる。

プログラム実行

これでAspectJを使う準備ができた。それではBeanを利用してみよう。com.devkuma.spring.aopパッケージのAppクラスを次のように修正する。

package com.devkuma.spring.aop;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("aopbean.xml");
         
        SampleAopBean bean = (SampleAopBean) app.getBean("sampleAopBean");
        String msg = bean.getMessage();
        bean.setMessage("<<" + msg + ">>");
        bean.printMessage();
    }
 
}

実行結果:

before :
after :
before :
after :
before :
message : [<< this is AOP bean! >>
after :

ここではgetMessageでメッセージを取得し、それを基にsetMessageでメッセージを設定し、その後printMessageで内容を出力している。それぞれのメソッド呼び出しごとに、その前後でbefore / afterが出力されることがわかる。

以前のSpring AOPとの決定的な違いは、getBeanで取得して実行するクラスである。Spring AOPではProxyFactoryBeanを通じて修正されたSampleAopBeanを取得できるようになっていた。

今回getBeanで取得したものはSampleAopBeanである。つまり、SampleAopBeanクラス自体に変更されたメソッドが挿入されているのである。

動作を確認したら、aopbean.xmlを開いて<aop : aspectj-autoproxy/>タグを削除する。そして実行すると、今度は「message:[« this is AOP bean! »]」だけが表示される。before / afterに挿入されたメソッドが消えたことがわかる。

このようにAspectJを使用すると、自動プロキシのON / OFFだけで処理の挿入と削除を簡単に行える。使用するBeanをプロキシBeanから元に戻すことはできない。

Bean設定クラスの利用

これでAspectJの基本的な利用方法はわかった。Spring Frameworkでは、Beanは設定ファイルではなくクラスを使って定義できた。AspectJでも定義クラスで設定してみよう。

com.devkuma.spring.aopパッケージに「SampleAspectConfig」というクラスを作成する。そして次のようにコードを書く。

package com.devkuma.spring.aop;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
@Configuration
@EnableAspectJAutoProxy
public class SampleAspectConfig {
     
    @Bean
    SampleAopBean sampleAopBean() {
        return new SampleAopBean("this is AspectJ bean.");
    }
     
    @Bean
    SampleAspect sampleAspect() {
        return new SampleAspect();
    }
 
}

これがAspectJ設定クラスである。作成した後、App.javaを開いて次の文を修正する。

App修正前

ApplicationContext app =
    new ClassPathXmlApplicationContext ( "aopbean.xml");

App修正後

ApplicationContext app =
    new AnnotationConfigApplicationContext (SampleAspectConfig.class);

これで実行すると、以前と同じようにAspectJを利用した挿入が行われ、メソッド呼び出しの前後で処理が実行される。このクラスには、設定クラスであることを示す@Configurationのほかに、次のアノテーションが追加されている。

@EnableAspectJAutoProxy

これはBean定義ファイルに入れていた<aop:aspectj-autoproxy/>タグに相当する。これを記述することでAspectJの自動プロキシ機能がONになり、自動的にAspectクラスのメソッド挿入が行われる。

Beanの定義はこれまでと変わらない。ただ、@EnableAspectJAutoProxyを追加するだけでAspectJの機能がONになる点が違う。設定クラスへ変更しても、AspectJ利用の簡単さは変わらないことがわかる。