-S14: 車が走ってる映像

今回説明する機能は、Seasar2.0.2以降で動作します。最新版をダウンロードしてください。
Javaインターフェイスはインスタンス化できません。以下の例は、NGです。

Car car = new Car();

しかし、Seasar2ではAspectを用いることでインスタンス化が可能です。これはとてもユニークな機能です。

list14-1. インターフェイスを実装するMovie.java
package tutorial.org.seasar.console;
import org.seasar.framework.aop.AroundAdvice;
import org.seasar.framework.aop.Joinpoint;
public class Movie implements AroundAdvice {
  public Object invoke(Joinpoint joinpoint) throws Throwable {
    System.out.println("It looks like running.");
    return null;
  }
}

設定XMLは以下のとおりです。特に新しいことはありませんが、コンポーネントクラスの指定に注意してください。

list14-2. class属性にインターフェイスを指定したcar.xml修正版
<?xml version="1.0" encoding="UTF-8"?>
<components>
  <component class="tutorial.org.seasar.console.Car">
    <aspect>
      <component class="tutorial.org.seasar.console.Movie"/>
    </aspect>
  </component>
</components>

実行結果は以下のとおりです。

It looks like running.

list14-2の<component>エレメントのclass属性の値は、Carです。これはこれまで用いてきたJavaインターフェイスです。Seasar2は、<aspect>エレメントを持つ<component>エレメントでは、そのコンポーネント生成において単純にクラスのコンストラクタを用いたインスタンス生成を行うのではなく、AspectをWevingするための「AOP Proxy」の生成を行います。このAOP Proxyはバイトコードエンジニアリングによってクラス継承およびメソッドにJoinpointを生成するための仕掛けを施します。その際に、継承元クラスはSeasar2の用いているバイトコードエンジニアリングライブラリであるCGLibがインターフェイスでも継承を可能とするため、インターフェイスクラスでもSeasar2ではインスタンス化できるのです。
一方で、Adviceのほうは注意が必要です。list14-1のように、AroundAdvice#invoke(Joinpoint)の実装において、Seasar2のAdvice実装では普通行う、joinpoint.proceed()をそのままでは記述できません。なぜならばJoinpointが保持するのは<component>エレメントのclass属性の値で指定されたクラスのインスタンスが持つメソッドであり、インターフェイスがその値であった場合には該当するメソッドの実装がないためAbstractMethodErrorが発生してしまいます。
そこで次の例は、Adviceと実装のハイブリッドな動きをする例です。

list14-3. 壊れていて走れないBrokenCar.java
package tutorial.org.seasar.console;
public abstract class BrokenCar implements Car {
  public void check(boolean flag) {
    System.out.println("It is broken.");
  }
}

Adviceも増やします。

list14-4. 壊れているかを判断するDealer.java
package tutorial.org.seasar.console;
import org.seasar.framework.aop.AroundAdvice;
import org.seasar.framework.aop.Joinpoint;
public class Dealer implements AroundAdvice {
  public Object invoke(Joinpoint joinpoint) throws Throwable {
    Object[] args = joinpoint.getArgs();
    if( (args.length != 0) && (args[0] instanceof Boolean) ) {
      boolean arg = ((Boolean)args[0]).booleanValue();
      if(arg) {
        System.out.println("It is fine!");
      } else {
        joinpoint.proceed();
      }
    } 
    return null;
  }
}

これらの設定XMLは、

list14-5. カーディーラーがウソをつく?car.xml修正版
<?xml version="1.0" encoding="UTF-8"?>
<components>
  <component class="tutorial.org.seasar.console.BrokenCar">
    <initMethod name="check">
      <arg>true</arg>
    </initMethod>
    <aspect pointcut="check">
      <component class="tutorial.org.seasar.console.Dealer"/>
    </aspect>
    <aspect pointcut="run">
      <component class="tutorial.org.seasar.console.Movie"/>
    </aspect>
  </component>
</components>

実行結果は、

It is fine!
It looks like running.

また、list14-5の<initMethod>エレメントによるBrokenCar#check()の呼び出しの引数で、<arg>true</arg>とあるのを<arg>false</arg>とすると、

It is broken.
It looks like running.

BrokenCarは抽象クラス(abstract)です。インターフェイスだけでなく、抽象クラスもAspectをWevingしていればインスタンス化できます。実行のそれぞれ一行目がBrokenCar#check()、二行目がBrokenCar#run()の結果ですが、check()に<initMethod>エレメントで渡している引数を変えると出力も変わってます。"true"なら"It is fine!"、これはlist14-4のDealerが出力する文字列です。"false"なら"It is broken."。これはlist14-3のBrokenCarが出力しています。この動作の切り分けは、Dealer#invoke(Joinpoint)でやってます。引数がtrueだとそのままSystem.out.println("It is fine!");が実行され、falseだとjoinpoint.proceed();で、コンポーネントのメソッドを呼びに行きます。ここでは抽象クラスとはいえ、該当するcheck()メソッドは実装されていますから、問題なく実行されます。