如何创建注释的实例



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

所以…我有一些带注释的类、方法和字段。我有一个方法,它使用反射对类运行一些检查,并向类注入一些值。

然而,我现在面临的情况是,我需要一个注释的实例(可以说)。所以…注释不像普通的接口,你不能做一个类的匿名实现。我明白了。我已经在这里看了一些关于类似问题的帖子,但我似乎无法找到我正在寻找的答案。

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

嗯,显然没有那么复杂。真的!

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

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。

Gunnar回答中建议的代理方法已经在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.

库本身很小,没有依赖关系(也不依赖于JDK内部api)。

披露:我是GeantyRef的开发人员。

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

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

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

创建针对方法/参数的注释实例可能更复杂一些,但这种方法的好处是可以像JVM通常那样获得注释实例。不用说,这是最简单的了。

您可以使用Hibernate Validator项目中的注释代理(免责声明:我是这个项目的提交者)。

您可以使用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编写的。

在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的缺点。

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

@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();
}

使用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));

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

看看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");

相关内容

  • 没有找到相关文章

最新更新