如何创建注释的实例

我正在尝试做一些 Java 注释魔术。我必须说,我还在学习注释技巧,有些事情我还不太清楚。

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

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

我基本上希望获取一个注释的实例,并能够使用反射设置它的一些字段(我认为)。有什么办法吗?

43230 次浏览

显然没那么复杂

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

我的注解:

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

您可以使用 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.*的类在以后的版本中可能会发生变化(尽管这个方法在 Java5之后就已经存在了,并且具有相同的签名) ,并且不适用于所有的 Java 实现,参见 这个讨论

如果这是一个问题: 您可以创建一个通用的代理与您自己的 InvocationHandler-这正是什么 AnnotationParser正在为您内部。或者使用自己定义的 给你实现 MyAnnotation。在这两种情况下,您都应该记住实现 annotationType()equals()hashCode(),因为结果是专门为 java.lang.Annotation记录的。

加纳的回答中建议的代理方法已在 GeantyRef中实施:

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

这将生成与您从中获得的内容等价的注释:

@MyAnnotation(name = "someName")

通过这种方式生成的注释实例将与 Java 通常生成的注释实例行为相同,并且它们的 hashCodeequals已经为兼容性正确实现,所以没有像在已接受的答案中直接实例化注释那样奇怪的警告。实际上,JDK 内部使用相同的方法: AnnotationParser # annotationForMap

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

披露: 我是 GeantyRef 的开发者。

在 ApacheCommons 注释工具的帮助下,使用代理方法的方法相当粗糙

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

传递的属性的类型不用在注释接口上声明的实际类型进行检查,任何丢失的值只有在运行时才会被发现。

在功能上与 卡卡的回答中提到的代码非常相似(可能也包括 加纳的回答) ,但是没有使用内部 JavaAPI 的缺点,就像在 Tobias Liefke 的回答。中一样

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

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

您也可以创建一个虚拟的注释目标并从中获取它

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

还有

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

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

@ Gunnar 的回答对于大多数 Web 服务来说是最简单的方式,因为我们已经进入了休眠状态, 比如说 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");

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