2013年5月28日火曜日

AspectJ の Pointcut - this と target の違いって?

はじめに

this と target についてそれぞれ thisは、実行中のオブジェクト、target は、対象オブジェクトであれば
A というクラスが B というクラスを呼出している場合、this は A クラス、target は B クラスになってほしいのですが
Spring の AspectJ では this と target が同じオブジェクトになってしまう。
調べてみると call では、上記のような動きになり、execution では同じオブジェクトになるようだ。
Spring の AspectJ は、execution のみで call に対応していないらしい。
そんな訳で本家の AspectJ を使って call と execution での this と target の違いを調べてみたいと思います。

今回は、簡単な実験クラスをいくつか作成して検証してみたいと思います。
ではでは!Getting Started!!


環境

Java JDK 1.7.0_21
フレームワーク aspectjrt 1.7.2
ビルドツール Maven 3.0.5
※mvnコマンドが実行できるように設定しておきます。
OS Windows XP


プロジェクト構成

Cドライブ直下に「studying-aspectj-pointcut」フォルダを作成し各ファイルを以下のように配置します。
ファイルは、すべて UTF-8 で保存します。

  • C:\studying-aspectj-pointcut
    • src
      • main
        • java
          • com
            • mydomain
              • App.java
              • Casino.java
              • Gambler.java
              • Money.java
              • MyAspect.java
    • pom.xml


POM ファイルの作成

AspectJ のコンパイルに aspectj-maven-plugin を使用しています。
また jar の生成には、maven-shade-plugin を使用しています。
このプラグインは、依存する jar を一旦バラして 今回作成したクラスと一緒に1つの jar ファイルにまとめてくれます。
POM ファイルを以下の内容で作成します。

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>com.mydomain</groupId>
  <artifactId>studying-aspectj</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>studying-aspectj</name>

  <properties>
    <java.version>1.7</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.7.2</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>studying-aspectj</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.4</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test-compile</goal>
            </goals>
            <configuration>
              <complianceLevel>1.6</complianceLevel>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>com.mydomain.App</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>


Aspect 実験クラス作成

概要としては、次のような感じです。
App クラスのメインメソッドで Money クラスを作成して Gambler クラスの paySalary() メソッドに渡します。
Gambler クラスは、Casino クラスの gambling() メソッドに 受け取った Money クラスを渡します。
そして、持ち金の 20% が無くなります。
簡単に言うと貰った給料で即パチンコ打ってまぁまぁ負けるというような感じです。(笑

App.java
package com.mydomain;

public class App {

  public static void main(String[] args) {

    Money money = new Money();
    money.setValue(100);

    Gambler gambler = new Gambler();
    gambler.paySalary(money);
  }
}
Money.java
package com.mydomain;

public class Money {

  private int value;

  public int getValue() {
    return value;
  }

  public void setValue(int value) {
    this.value = value;
  }
}
Gambler.java
package com.mydomain;

public class Gambler {

  public void paySalary(Money money) {

    System.out.printf("Gambler paySalary %s %n", money.getValue());

      Casino casino = new Casino();
      casino.gambling(money);
  }
}
Casino.java
package com.mydomain;

public class Casino {

  public void gambling(Money money) {

    money.setValue((int)(money.getValue() * 0.8));
    System.out.printf("Casino gambling %s %n", money.getValue());
  }
}


Aspect クラスを作成して Pointcut で遊ぶ

Casino クラスの gambling() メソッドに着目して this と target を調べてみたいと思います。
this と target が call と execution ではどう違うかを Around アドバイスを使って見てみます。
MyAspect クラスを以下の内容で作成します。

MyAspect.java
package com.mydomain;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {

  @Around("call(void com.mydomain.Casino.gambling(Money))")
  public Object aroundCall(ProceedingJoinPoint pjp) throws Throwable {
    
    System.out.printf("AroundCall [this:%s] [target:%s] %n", pjp.getThis(), pjp.getTarget());
    
    Object retVal = pjp.proceed();
    return retVal;
  }

  @Around("execution(void com.mydomain.Casino.gambling(Money))")
  public Object aroundExec(ProceedingJoinPoint pjp) throws Throwable {
    
    System.out.printf("AroundExec [this:%s] [target:%s] %n", pjp.getThis(), pjp.getTarget());
    
    Object retVal = pjp.proceed();
    return retVal;
  }
}

コマンドプロンプトを開きCドライブ直下の「studying-aspectj-pointcut」フォルダに移動後、 mvn コマンドでビルドします。
そして生成された jar ファイルを実行します。

cd c:\studying-aspectj-pointcut
mvn clean package
java -jar C:\studying-aspectj-pointcut\target\studying-aspectj.jar

実行結果は以下のようになると思います。

Gambler paySalary 100
AroundCall [this:com.mydomain.Gambler@1c7e2da] [target:com.mydomain.Casino@1fe571f]
AroundExec [this:com.mydomain.Casino@1fe571f] [target:com.mydomain.Casino@1fe571f]
Casino gambling 80

なるほど call では、this が呼出元クラス、target は、呼出先クラスになり execution では、this、target 両方対象クラスになる。
納得だ!
今度は、Before アドバイスでも試してみる。
MyAspect クラスのメソッドを以下の内容に差し替えます。

MyAspect.java
...
  @Before("call(void com.mydomain.Casino.gambling(Money)) && this(Gambler) && target(Casino)")
  public void beforeCall(){ 
    System.out.printf("Before Call %n");
  }

  @Before("execution(void com.mydomain.Casino.gambling(Money)) && this(Casino) && target(Casino)")
  public void beforeExecution(){ 
    System.out.printf("Before Execution %n");
  }
...

再ビルド後、実行すると以下のようになると思います。

Gambler paySalary 100
Before Call
Before Execution
Casino gambling 80

Aspect は呼出されているようだけど、なんだかよく分からないので this と target をキャプチャして print してみる。
MyAspect クラスのメソッドを以下の内容に差し替えて再ビルド後、実行する。

MyAspect.java
...
  @Before("call(void com.mydomain.Casino.gambling(Money)) && this(obj01) && target(obj02)")
  public void beforeCall(Gambler obj01, Casino obj02){ 
    System.out.printf("Before Call [this:%s] [target:%s] %n", obj01, obj02);
  }

  @Before("execution(void com.mydomain.Casino.gambling(Money)) && this(obj01) && target(obj02)")
  public void beforeExecution(Casino obj01, Casino obj02){ 
    System.out.printf("Before Execution [this:%s] [target:%s] %n", obj01, obj02);
  }
...
Gambler paySalary 100
Before Call [this:com.mydomain.Gambler@7eb366] [target:com.mydomain.Casino@33f0de]
Before Execution [this:com.mydomain.Casino@33f0de] [target:com.mydomain.Casino@33f0de]
Casino gambling 80

ふむふむ、Before アドバイスでも call と execution で this と target が異なることが分かる。
試しに execution の this を Gambler クラス(call での呼出元クラス)変更してみる。
MyAspect クラスの beforeExecution() メソッドを以下の内容に差し替えて再ビルド後、実行する。

MyAspect.java
...
  @Before("execution(void com.mydomain.Casino.gambling(Money)) && this(obj01) && target(obj02)")
  public void beforeExecution(Gambler obj01, Casino obj02){ 
    System.out.printf("Before Execution [this:%s] [target:%s] %n", obj01, obj02);
  }
...
Gambler paySalary 100
Before Call [this:com.mydomain.Gambler@d1c778] [target:com.mydomain.Casino@7eb366]
Casino gambling 80

おぉ!execution の アドバイスが呼出されなくなった。
当たり前か?(笑
ノッてきたぞ!

もう少し遊んでみる。
今度は、args を使ってメソッドの引数で使われている Money クラスを Pointcut 条件にしてみる。
args も this と target と同じようにクラス・インターフェイスの型で Pointcut 条件を指定する。
キャプチャもできるようだ。
within は、this と target と同じようにクラス・インターフェイスの型で Pointcut 条件を指定するのだけどキャプチャはできない。
また実行オブジェクト、対象オブジェクトというような区別もなく指定した型が Pointcut 条件 になるようだ。
!within(MyAspect) で MyAspect クラスの before() メソッドを Aspect の対象外にしています。
それというのも before() メソッドも引数で Money クラスを受けるの Pointcut 条件 にマッチして
結果再帰呼出になってしまうからです。
MyAspect クラスのメソッドを以下の内容に差し替えて再ビルド後、実行する。

MyAspect.java
...
  @Before("args(obj01) && !within(MyAspect)")
  public void before(Money obj01){ 
    System.out.printf("Before [args:%s] %n", obj01.getValue());
  }
...
Before [args:100]
Before [args:100]
Gambler paySalary 100
Before [args:100]
Before [args:100]
Casino gambling 80

むむむ… Aspect は呼出されているようだけど、なんだかよく分からない。
Around アドバイスを使ってthis、target、メソッドを print してみる。
MyAspect クラスのメソッドを以下の内容に差し替えて再ビルド後、実行する。

MyAspect.java
...
  @Around("args(Money) && !within(MyAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
 
    System.out.printf(
      "Around [this:%s] [target:%s] [signature:%s] %n",
      pjp.getThis(), pjp.getTarget(), pjp.getSignature());

    Object retVal = pjp.proceed();
    return retVal;
  }
...
Around [this:null] [target:com.mydomain.Gambler@83b1b] [signature:void com.mydomain.Gambler.paySalary(Money)]
Around [this:com.mydomain.Gambler@83b1b] [target:com.mydomain.Gambler@83b1b] [signature:void com.mydomain.Gambler.paySalary(Money)]
Gambler paySalary 100
Around [this:com.mydomain.Gambler@83b1b] [target:com.mydomain.Casino@b32ed4] [signature:void com.mydomain.Casino.gambling(Money)]
Around [this:com.mydomain.Casino@b32ed4] [target:com.mydomain.Casino@b32ed4] [signature:void com.mydomain.Casino.gambling(Money)]
Casino gambling 80

なるほど、call と execution の両方に作用しているようだ。
this が null になっているところは、App クラスの main() メソッドが static だからだろう。
また、main() メソッド中 の Gambler クラスの paySalary() メソッドの呼出には、call が作用しているのだろう。
もう少し Pointcut 条件を絞ってみる。
Gamblerクラス が Casino クラスの gambling() メソッドを呼出しているところあたりに作用するようにしてみる。
MyAspect クラスのメソッドを以下の内容に差し替えて再ビルド後、実行する。

MyAspect.java
...
  @Around("this(Gambler) && target(Casino) && args(Money)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {

    System.out.printf(
      "Around [this:%s] [target:%s] [signature:%s] %n",
   pjp.getThis(), pjp.getTarget(), pjp.getSignature());

    Object retVal = pjp.proceed();
    return retVal;
  }
...
Gambler paySalary 100
Around [this:com.mydomain.Gambler@1d0d45b] [target:com.mydomain.Casino@125d06e] [signature:void com.mydomain.Casino.gambling(Money)]
Casino gambling 80

あれ?これは、「@Around("call(void com.mydomain.Casino.gambling(Money))")」と同じでは?
最初に戻ってしまった…
なんとなくオチがついたようなので今回はこの辺で。


おわりに

this と target について、とりあえずは整理できたと思う。
本家の AspectJ には、まだまだ、ディープな Pointcut があるのだけれども
Spring が対応している AspectJ の主な Pointcut は、今回、使った思う。
残りは、アノテーションを Pointcut 条件にするものがある。
また、Spring 独自の bean なんてなのもあるようだ。
これらは、またいつかの機会に。

参考URL
http://www.eclipse.org/aspectj/doc/next/progguide/index.html
http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html
http://netail.net/aosdwiki/index.php?AspectJ%2F%B4%CA%B0%D7%A5%EA%A5%D5%A5%A1%A5%EC%A5%F3%A5%B9
http://maven.apache.org/plugins/maven-shade-plugin/
http://mojo.codehaus.org/aspectj-maven-plugin/