AspectJを利用したプロジェクトのMavenでのビルドとテスト (Load-Time Weaving)

はじめに

あるOSSの解析のためにAspectJを利用した。 その際にMavenアスペクトのビルドとテストを行ったので、その記録を残しておく。 ここでは、テストメソッドにログ出力を織り込むことにする。

ディレクトリ構成

$ tree
.
|-- pom.xml
`-- src
    |-- main
    |   |-- java
    |   |   `-- redj
    |   |       `-- aspectj
    |   |           `-- LoggingAspect.java
    |   `-- resources
    `-- test
        `-- java
            `-- redj
                `-- aspectj
                    `-- LoggingAspectTest.java

LoggingAspect.java

アスペクトを準備。 パッケージredj.aspectjの任意のクラスにおいて、testをプレフィックスに持つメソッドの実行前にログを出力する。

package redj.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LoggingAspect {

    @Pointcut("execution(* redj.aspectj.*.test*(..))")
    public void atTestMethod() {
    }

    @Before("atTestMethod()")
    public void logBeforeTest() {
        System.out.println("***** begin test");
    }
}

LoggingAspectTest.java

テストクラスを準備。 アスペクトが織り込まれるように、testをプレフィックスに持つメソッドを定義しておく。

package redj.aspectj;

import org.junit.Test;

public class LoggingAspectTest {

    @Test
    public void testFoo() {
        System.out.println("test foo");
    }

    @Test
    public void testBar() {
        System.out.println("test bar");
    }
}

pom.xml

testフェーズでLoad-Time Weavingを利用するようにpom.xmlを構成。 かなり面倒。

maven-dependency-plugin:build-classpathを利用してaspectjweaverのJarファイルのパスを組み立てて、maven-surefire-plugin:testに引き渡しているところがポイント。 maven-surefire-plugin:testでは、プロパテイを遅延評価する必要があるので@{ }を利用している。

aspectj-maven-plugin:compileを利用してaop-ajc.xmlを生成する。 aop-ajc.xmlには、aspectjweaverに認識させるアスペクトの定義情報が書かれている。 ただし、aspectj-maven-pluginのcomplianceLevelのデフォルトは1.4なので、明示的に1.5以上に設定しないと、アノテーションを利用したアスペクトコンパイルでエラーを起こす。 さらに、AspectJのバージョンがずれていると警告が出るので、aspectj-maven-pluginにaspectjtoolsへの依存を明示的に書いて、バージョンを合わせる。

<?xml version="1.0" encoding="UTF-8"?>
<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>redj</groupId>
    <artifactId>aspectj</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <properties>
        <!-- maven-compiler-plugin -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <!-- versions of plugins -->
        <maven-dependency-plugin.version>3.0.0</maven-dependency-plugin.version>
        <aspectj-maven-plugin.version>1.10</aspectj-maven-plugin.version>
        <maven-surefire-plugin.version>2.20</maven-surefire-plugin.version>

        <!-- versions of dependencies -->
        <aspectj.version>1.8.10</aspectj.version>
        <junit.version>4.12</junit.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>${maven-dependency-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-classpath</goal>    <!-- binds by default to generate-sources -->
                        </goals>
                        <configuration>
                            <outputProperty>aspectj-weaver-path</outputProperty>
                            <includeGroupIds>org.aspectj</includeGroupIds>
                            <includeArtifactIds>aspectjweaver</includeArtifactIds>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>${aspectj-maven-plugin.version}</version>
                <dependencies>
                    <dependency>    <!-- upgrade AspectJ -->
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>    <!-- binds by default to compile -->
                        </goals>
                        <configuration>
                            <complianceLevel>1.8</complianceLevel>
                            <outxml>true</outxml>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <executions>
                    <execution>
                        <id>default-test</id>
                        <goals>
                            <goal>test</goal>   <!-- binds by default to test -->
                        </goals>
                        <configuration>
                            <argLine>-javaagent:@{aspectj-weaver-path}</argLine>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

ビルド

テストメソッドの実行前にログが出力されていることが確認できる。

$ mvn clean install
(略)
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running redj.aspectj.LoggingAspectTest
***** begin test
test bar
***** begin test
test foo
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.153 s - in redj.aspectj.LoggingAspectTest
(略)

参考