web-dev-qa-db-ja.com

注釈のインスタンスを作成する方法

いくつかのJavaアノテーションマジック。

だから...注釈付きのクラス、メソッド、フィールドがあります。リフレクションを使用してクラスでいくつかのチェックを実行し、クラスに値を注入するメソッドがあります。これはすべて正常に動作します。

しかし、私は注釈のインスタンス(いわば)が必要な場合に直面しています。そのため、アノテーションは通常のインターフェースとは異なり、クラスの匿名実装を行うことはできません。わかった。同様の問題に関するいくつかの投稿をここで見ましたが、探しているものへの答えを見つけることができないようです。

基本的に、アノテーションのインスタンスを取得して、リフレクションを使用してそのフィールドの一部を設定できるようにしたいと思います(そうですね)。これを行う方法はありますか?

52
carlspring

まあ、どうやらそれほど複雑なことではないようです。 Really !

同僚が指摘したように、次のようにアノテーションの匿名インスタンスを作成できます(他のインターフェイスと同様)。

MyAnnotation:

public @interface MyAnnotation
{

    String foo();

}

呼び出しコード:

class MyApp
{
    MyAnnotation getInstanceOfAnnotation(final String foo)
    {
        MyAnnotation annotation = new MyAnnotation()
        {
            @Override
            public String foo()
            {
                return foo;
            }

            @Override
            public Class<? extends Annotation> annotationType()
            {
                return MyAnnotation.class;
            }
        };

        return annotation;
    }
}

Martin Grigorov へのクレジット。

64
carlspring

Gunnar's answer で提案されているプロキシアプローチは、すでに GeAnTyRef で実装されています。

Map<String, Object> annotationParameters = new HashMap<>();
annotationParameters.put("name", "someName");
MyAnnotation myAnnotation = TypeFactory.annotation(MyAnnotation.class, annotationParameters);

これにより、取得するものと同等の注釈が生成されます。

@MyAnnotation(name = "someName")

この方法で生成された注釈インスタンスは、Java通常生成されるものと同じ動作をします。また、hashCodeequalsは互換性のために適切に実装されているため、奇妙なことはありません受け入れられた回答のように、アノテーションを直接インスタンス化する場合のような注意点実際、JDKは内部的にこの同じアプローチを使用しています: Sun.reflect.annotation.AnnotationParser#annotationForMap

ライブラリ自体は小さく、依存関係はありません。

開示:私はGeAnTyRefの開発者です。

11
kaqqao

Hibernate Validatorプロジェクトの this onethis one などの注釈プロキシを使用できます(免責事項:私は後者のコミッターです)。

6
Gunnar

Sun.reflect.annotation.AnnotationParser.annotationForMap(Class, Map)を使用できます:

_public @interface MyAnnotation {
    String foo();
}

public class MyApp {
    public MyAnnotation getInstanceOfAnnotation(final String foo) {
        MyAnnotation annotation = AnnotationParser.annotationForMap(
            MyAnnotation.class, Collections.singletonMap("foo", "myFooResult"));
    }
}
_

欠点:_Sun.*_のクラスは後のバージョンで変更される可能性があります(ただし、このメソッドはJava 5が同じ署名を持つため)が存在し、すべてのJava実装、 この説明 を参照してください。

それが問題の場合:独自のInvocationHandlerを使用して汎用プロキシを作成できます。これは、AnnotationParserが内部的に行っていることです。または、定義済みのMyAnnotationの独自の実装 here を使用します。どちらの場合も、annotationType()equals()、およびhashCode()を実装することを忘れないでください。結果は_Java.lang.Annotation_専用にドキュメント化されています。

5
Tobias Liefke

Apache Commons AnnotationUtils の助けを借りてプロキシアプローチを使用するむしろ粗雑な方法

public static <A extends Annotation> A mockAnnotation(Class<A> annotationClass, Map<String, Object> properties) {
    return (A) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class<?>[] { annotationClass }, (proxy, method, args) -> {
        Annotation annotation = (Annotation) proxy;
        String methodName = method.getName();

        switch (methodName) {
            case "toString":
                return AnnotationUtils.toString(annotation);
            case "hashCode":
                return AnnotationUtils.hashCode(annotation);
            case "equals":
                return AnnotationUtils.equals(annotation, (Annotation) args[0]);
            case "annotationType":
                return annotationClass;
            default:
                if (!properties.containsKey(methodName)) {
                    throw new NoSuchMethodException(String.format("Missing value for mocked annotation property '%s'. Pass the correct value in the 'properties' parameter", methodName));
                }
                return properties.get(methodName);
        }
    });
}

渡されたプロパティのタイプは、アノテーションインターフェイスで宣言された実際のタイプではチェックされず、不足している値は実行時にのみ検出されます。

kaqqao's answer (そしておそらく Gunnar's Answer -)))、内部Java APIを Tobias Liefkeの答えのように使用することのマイナス面なし)

0
oujesky