Gradle を使って JAR ファイルへ AspectJ を適用

Gradle を使って既存の JAR ファイルへ AspectJ を適用してみました。

Gradle 用の AspectJ プラグインとして gradle-aspectj というものがあるようですが、今回は AspectJaspectjtools) に含まれている Ant 用の AjcTask (iajc) を Gradle から使う事にします。

AjcTask の利用方法

Gradle で AjcTask を使用するには Gradle ビルドスクリプト (build.gradle) で下記のように定義します。

ant.taskdef(resource: 'org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties', classpath: <aspectjtools へのパス>)

ant.iajc() で AjcTask を利用できるようになります。

Struts の JAR ファイルへ AspectJ を適用

今回は下記のアスペクト定義を Struts 1.3.10 の JAR ファイル (struts-core-1.3.10.jar) へ適用してみる事にします。

org.apache.struts.mock パッケージを除いた org.apache.struts 以降のパッケージに属しているクラスの BeanUtils.populate() 呼び出し箇所でプロパティ名がパターンにマッチした際に IllegalArgumentException を発生させるような処理を織り込みます。

アスペクト定義 src/main/java/fits/sample/StrutsAspect.java
package fits.sample;

import java.util.Map;
import java.util.regex.Pattern;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;

@Aspect
public class StrutsAspect {
    private final static Pattern PATTERN = Pattern.compile("(^|\\W)[cC]lass\\W");

    @Around(
        "call(void org.apache..BeanUtils.populate(Object, Map)) &&" + 
        "within(org.apache.struts..*) && " +
        "!within(org.apache.struts.mock.*) && " +
        "args(bean, properties)"
    )
    public void aroundPopulate(ProceedingJoinPoint pjp, Object bean, Map properties) throws Throwable {
        if (properties != null) {
            checkProperties(properties);

            pjp.proceed();
        }
    }

    private void checkProperties(Map properties) {
        for (Object key : properties.keySet()) {
            String property = (String)key;

            if (PATTERN.matcher(property).find()) {
                throw new IllegalArgumentException(key + " is invalid");
            }
        }
    }
}

ビルドスクリプトの内容は下記のようになります。

aspectjtools のパスを取得するために configurations へ ajc を定義し、AspectJ を適用するタスクを ajc という名称で定義しました。

struts-core-1.3.10.jar へ AspectJ を適用するための依存ライブラリは、configurations.compile から取得して、ant.iajc の classpath へ設定するようにしています。

なお、commons-fileupload や servlet-api 等の依存ライブラリはアスペクト定義の内容によって代わると思います。

ビルドスクリプト build.gradle
apply plugin: 'java'

repositories {
    mavenCentral()
}

configurations {
    ajc
}

dependencies {
    ajc "org.aspectj:aspectjtools:1.8.0"
    compile "org.aspectj:aspectjrt:1.8.0"
    compile "org.apache.struts:struts-core:1.3.10"
    compile 'commons-fileupload:commons-fileupload:1.3.1'
    compile 'javax.servlet:servlet-api:2.5'
}

task ajc << {
    ant.taskdef(resource: 'org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties', classpath: configurations.ajc.asPath)

    ant.iajc(outJar: "struts-core_custom.jar", source: '1.7', showWeaveInfo: 'true') {
        sourceroots {
            sourceSets.main.java.srcDirs.each {
                pathelement(location: it.absolutePath)
            }
        }
        classpath {
            // 依存ライブラリの設定
            pathelement(location: configurations.compile.asPath)
        }
        inpath {
            // struts-core へのパスを設定
            pathelement(location: configurations.compile.files.find { it.path.contains 'struts-core' }.absolutePath)
        }
    }
}

実行結果は下記のようになり、 ActionServlet クラスと RequestUtils クラスへ StrutsAspect が適用された JAR ファイル (struts-core_custom.jar) がカレントディレクトリへ作成されます。

実行結果
> gradle ajc --info
・・・
:ajc (Thread[main,5,main]) started.
:ajc
Executing task ':ajc' (up-to-date check took 0.0 secs) due to:
  Task has not declared any outputs.
[ant:iajc] weaveinfo Join point 'method-call(void org.apache.commons.beanutils.BeanUtils.populate(java.lang.Object, java.util.Map))' in Type 'org.apache.struts.action.ActionServlet' (ActionServlet.java:845) advised by around advice from 'fits.sample.StrutsAspect' (StrutsAspect.java:19)
[ant:iajc] weaveinfo Join point 'method-call(void org.apache.commons.beanutils.BeanUtils.populate(java.lang.Object, java.util.Map))' in Type 'org.apache.struts.util.RequestUtils' (RequestUtils.java:473) advised by around advice from 'fits.sample.StrutsAspect' (StrutsAspect.java:19)
:ajc (Thread[main,5,main]) completed. Took 5.211 secs.

BUILD SUCCESSFUL

今回使用したサンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20140518/