67

我正在尝试做一些 Java 注释魔术。我必须说我仍在追赶注释技巧,并且某些事情对我来说还不是很清楚。

所以...我有一些带注释的类、方法和字段。我有一个方法,它使用反射对类运行一些检查并将一些值注入到一个类中。这一切都很好。

但是,我现在面临一个需要注释实例(可以这么说)的情况。所以......注释不像常规接口,你不能对一个类进行匿名实现。我得到它。我在这里查看了一些关于类似问题的帖子,但我似乎无法找到我正在寻找的答案。

我基本上想获取注释的实例,并能够使用反射设置其中的一些字段(我想)。有没有办法做到这一点?

4

10 回答 10

90

好吧,这显然没有那么复杂。真的!

正如一位同事所指出的,您可以像这样简单地创建注释的匿名实例(就像任何接口一样):

我的注释:

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

于 2013-04-30T14:59:46.893 回答
20

Gunnar 的回答中建议的代理方法已经在GeantyRef中实现:

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

这将产生一个等同于您从中获得的注释:

@MyAnnotation(name = "someName")

以这种方式生成的注释实例将与 Java 正常生成的实例相同,并且它们hashCode已经equals正确实现以实现兼容性,因此没有像在接受的答案中直接实例化注释这样的奇怪警告。事实上,JDK 内部使用了同样的方法:sun.reflect.annotation.AnnotationParser#annotationForMap

该库本身很小并且没有依赖项(并且不依赖于 JDK 内部 API)。

披露:我是 GeantyRef 背后的开发者。

于 2017-04-24T16:18:58.107 回答
9

您可以使用注释代理,例如来自 Hibernate Validator 项目的注释代理(免责声明:我是该项目的提交者)

于 2013-05-01T20:58:02.960 回答
7

您可以使用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定义的那样。在这两种情况下,您都应该记住实施,因为结果是专门为.annotationType()equals()hashCode()java.lang.Annotation

于 2016-08-08T13:19:05.393 回答
5

您也可以绝对愚蠢地(但简单地)创建一个虚拟注释目标并从那里获取它

@MyAnnotation(foo="bar", baz=Blah.class)
private static class Dummy {}

final MyAnnotation annotation = Dummy.class.getAnnotation(MyAnnotation.class)

创建以方法/参数为目标的注解实例可能会稍微复杂一些,但这种方法的好处是可以像 JVM 通常那样获取注解实例。不用说它很简单。

于 2020-05-30T22:55:34.787 回答
1

在 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 的答案(可能还有Gunnar 的答案)中提到的代码非常相似,没有像Tobias Liefke 的答案那样使用内部 Java API 的缺点。

于 2019-08-06T10:00:08.083 回答
1

我这样做是为了在我的焊接单元测试中添加注释参考:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser {

    String value() default "foo";

    @SuppressWarnings("all")
    static class Literal extends AnnotationLiteral<AuthenticatedUser> implements AuthenticatedUser {

        private static final long serialVersionUID = 1L;

        public static final AuthenticatedUser INSTANCE = new Literal();

        private Literal() {
        }

        @Override
        public String value() {
            return "foo";
        }
    }
}

用法:

Bean<?> createUserInfo() {
    return MockBean.builder()
            .types(UserInfo.class)
            .qualifiers(AuthenticatedUser.Literal.INSTANCE)
            .create((o) -> new UserInfo())
            .build();
}
于 2020-02-11T12:02:38.890 回答
0

@Gunnar 的答案是大多数 web 服务最简单的方法,因为我们已经休眠,例如 KafkaListener kafkaListener = new org.hibernate.validator.internal.util.annotation.AnnotationDescriptor.Builder<>(KafkaListener.class, ImmutableMap.of("topics", new String[]{"my-topic"})).build().getAnnotation();,所有其他属性将保持默认。

于 2020-10-14T08:22:17.763 回答
0

看看AnnoBuilder。好处是它可以使用方法引用而不是属性名称

@interface Foo
{
    String value();
    int[] flags() default {0};
}

//test

    // @Foo(value="abc", flags={1})
    Foo foo1 = AnnoBuilder.of(Foo.class)
        .def(Foo::value, "abc")
        .def(Foo::flags, 1)
        .build();

    // @Foo(value="abc")
    Foo foo2 = AnnoBuilder.build(Foo.class, Foo::value, "abc");

    // @Foo("abc")
    Foo foo3 = AnnoBuilder.build(Foo.class, "abc");
于 2021-08-18T18:49:24.267 回答
0

使用hibernate-commons-annotations

<dependency>
    <groupId>org.hibernate.common</groupId>
    <artifactId>hibernate-commons-annotations</artifactId>
    <version>5.1.2.Final</version>
</dependency>
public final class Utils {
    public static <T extends Annotation> T newAnnotation(Class<? extends Annotation> annotationType, Map<String, Object> annotationParams) {
        var annotationDescriptor = new AnnotationDescriptor(annotationType);
        annotationParams.forEach(annotationDescriptor::setValue);
        return AnnotationFactory.create(annotationDescriptor);
    }
}
var annotation = Utils.<Length>newAnnotation(Length.class, Map.of("min", 1, "max", 10));
于 2022-03-01T13:19:40.417 回答