我正在尝试做一些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产生的注释实例相同,并且它们的hashCode
和equals
已被适当地实现以保持兼容性,因此没有像在可接受的答案中直接实例化注释那样的奇怪警告。事实上,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");