リフレクションを使ったAOPのためのNotNull引数チェック

EJB3でのAOPのためには、@AroundInvokeアノテーションを付与したメソッドを定義したインターセプタ用のクラス(これはバインドしたいクラス自身に定義してもOK)を作り、バインドしたいクラスにインターセプタクラスを@Interceptorsアノテーションを使って付与してあげればよい(クラス自身にインターセプタメソッドを定義している場合は不要)。

インターセプタメソッドの引数に取るInvocationContextでは対象のメソッドやパラメータをとってこれるので、これを使ってNullチェックを自動で挿し込めるようにしたい。
そうすると、メソッドごとに以下のようなチェックが不要になる。

if (param1 == null) {
    throw new IllegalArgumentException("param1 is null.");
}

今回はその準備。ちょんプロでどんなインターセプタメソッドにすればよいか想像をふくらませる。

まず引数がNull以外であることを制約するためのアノテーションを作る。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
}

続いて、上記の@NotNullを使ったメソッドを持つ対象のクラスを定義。Nullが許可されていない二つのIntegerを持つメソッド。

package sample;

public class Sample {

    public int add(@NotNull Integer a, @NotNull Integer b) {
        return a + b;
    }
}

NotNullチェック付きメソッドの実装。今回はメソッドの実装は、Method#invoke()をそのまま呼ぶ。

public Object invokeWithNullCheck(Object target, Method method, Object... params) throws Exception {
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();

    for (int i = 0; i < parameterAnnotations.length; i++) {
        Object param = params[i];

        Annotation[] parameterAnnotation = parameterAnnotations[i];
        for (Annotation annotation : parameterAnnotation) {
            if (annotation.annotationType().equals(NotNull.class)) {
                if (param == null) {
                    throw new IllegalArgumentException(target.getClass().getName()
                            + "#"
                            + method.getName()
                            + " contains wrong null arguments: "
                            + Arrays.toString(params));
                }
            }
        }
    }

    return method.invoke(target, params);
}

で、実際に呼び出してみる(JUnitテスト)。

@Test
public void testOK() throws Exception {
    Sample sample = new Sample();

    Method method = sample.getClass().getMethod("add", Integer.class, Integer.class);

    assertEquals(8,
            ((Integer) invokeWithNullCheck(sample, method, 5, 3)).intValue());
}

@Test
public void testNG() throws Exception {
    Sample sample = new Sample();

    Method method = sample.getClass().getMethod("add", Integer.class, Integer.class);

    try {
        invokeWithNullCheck(sample, method, 5, null);
        fail();
    } catch (IllegalArgumentException e) {
        assertEquals(
                "sample.Sample#add contains wrong null arguments: [5, null]",
                e.getMessage());
    }
}