76

I am trying to do some Java annotation magic. I must say I am still catching up on annotation tricks and that certain things are still not quite clear to me.

So... I have some annotated classes, methods and fields. I have a method, which uses reflection to run some checks on the classes and inject some values into a class. This all works fine.

However, I am now facing a case where I need an instance (so to say) of an annotation. So... annotations aren't like regular interfaces and you can't do an anonymous implementation of a class. I get it. I have looked around some posts here regarding similar problems, but I can't seem to be able to find the answer to what I am looking for.

I would basically like to get and instance of an annotation and be able to set some of it's fields using reflection (I suppose). Is there at all a way to do this?

carlspring
  • 31,231
  • 29
  • 115
  • 197
  • 1
    You can't instantiate or modify annotations. They already exists when you run the code. You can only retrieve them. Can you give some code examples for what you're trying to do? – NilsH Apr 30 '13 at 12:21
  • have you tried getAnnotation() available in java.lang.Class/java.lang.reflect.Method – Dev Blanked Apr 30 '13 at 12:21
  • Have you tried getClass().getAnnotations()? – prasanth Apr 30 '13 at 12:23
  • 1
    I am not trying to check if a class is annotated with a certain annotation. This part of the injection I have already implemented using reflection. I have a corner case, where I have to add a `MyAnnotation` to a `Set` and I don't have an instance of the annotation. – carlspring Apr 30 '13 at 12:25
  • Seeing some code explaining the corner case would help. – NilsH Apr 30 '13 at 12:28
  • 2
    Interesting question. I tried to look around and got these. http://stackoverflow.com/questions/266903/create-annotation-instance-with-defaults-in-java Just look at the first answer by Ralph. I think that should do. http://stackoverflow.com/questions/2786292/is-it-possible-to-instantiate-a-java-annotation-given-a-class-extends-annotati Would this help? – LPD Apr 30 '13 at 12:22
  • Looks like that. Not very sure tho.. – LPD Apr 30 '13 at 12:54

10 Answers10

100

Well, it's apparently nothing all that complicated. Really!

As pointed out by a colleague, you can simply create an anonymous instance of the annotation (like any interface) like this:

MyAnnotation:

public @interface MyAnnotation
{

    String foo();

}

Invoking code:

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

Credits to Martin Grigorov.

Community
  • 1
  • 1
carlspring
  • 31,231
  • 29
  • 115
  • 197
  • Hmm, looks like you also need to implement [`annotationType`](http://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Annotation.html#annotationType()). Presumably this would just return `MyAnnotation.class`. – Paul Bellora Apr 30 '13 at 15:14
  • Interesting thing to note. I had actually let the IDE generate the methods and hadn't noticed it implemented that as well. It was left to return `null` and worked. I have set it to the annotations's class type, although I am not sure what it should be really. – carlspring Apr 30 '13 at 15:23
  • I'm guessing that's required for annotation lookup functionality. For your specific case it's probably not needed but seems more correct to return the right thing (since you have to implement it regardless). – Paul Bellora Apr 30 '13 at 15:26
  • 8
    Caveat: the anonymous instance of `MyAnnotation` is an instance of class, not annotation, i.e. both `annotation.getClass().isInterface()` and `annotation.getClass().isAnnotation()` returns `false`. – Tomáš Záluský Jan 11 '15 at 12:22
  • 4
    Another caveat: `annotation.getClass()` also doesn't have potential metaannotations of `MyAnnotation`. For accessing them, one must get original class first: `annotation.getClass().getInterfaces()[0]`. I ran into this problem when I needed to mock annotations in test. (Note `@Inherited` doesn't help since it's about inheriting class having annotations, not annotations themselves.) – Tomáš Záluský Jan 11 '15 at 12:35
  • 7
    According to the JavaDoc of `Annotation` you need to implement `equals` and `hashCode` as well. For example qualifier annotations for `java.inject.Inject` could have problems to find the correct bean otherwise. – Tobias Liefke Aug 08 '16 at 13:26
21

The proxy approach, as suggested in Gunnar's answer is already implemented in GeantyRef:

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

This will produce an annotation equivalent to what you'd get from:

@MyAnnotation(name = "someName")

Annotation instances produced this way will act identical to the ones produced by Java normally, and their hashCode and equals have been implemented properly for compatibility, so no bizarre caveats like with directly instantiating the annotation as in the accepted answer. In fact, JDK internally uses this same approach: sun.reflect.annotation.AnnotationParser#annotationForMap.

The library itself is tiny and has no dependencies (and does not rely on JDK internal APIs).

Disclosure: I'm the developer behind GeantyRef.

kaqqao
  • 12,984
  • 10
  • 64
  • 118
  • 1
    i would prefer this over the other approaches, as it delivers the best result (same as [Gunnar's Answer](https://stackoverflow.com/a/16326389/294657)), and because it does not use Oracle JDK internal classes like Tobias'es answer, which likely make it incompatible with other JDKs/JVMs. – hoijui Feb 12 '18 at 07:34
  • It is a shame that the exception you throw here is a checked exception, which is only used to indicate problems with the arguments given -- that typically should be a runtime exception. – john16384 Nov 15 '21 at 20:44
  • 1
    @john16384 This is to emulate Java's own APIs as closely as possible, for better or worse. But in hindsight, it may not have been the best idea, since this method serves vastly different scenarios from Java's internal annotation factory. I might change it in the next release. – kaqqao Nov 16 '21 at 14:45
10

You can also absolutely stupidly (but simply) create a dummy annotation target and get it from there

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

And

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

Creating method/parameter targeted annotation instances may be a little more elaborate, but this approach has the benefit of getting the annotation instance as the JVM would normally do. Needless to say it is as simple as it can get.

Ravi Sanwal
  • 584
  • 5
  • 14
9

You could use an annotation proxy such as this one from the Hibernate Validator project (disclaimer: I'm a committer of this project).

Gunnar
  • 18,095
  • 1
  • 53
  • 73
7

You can use 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"));
    }
}

Downside: Classes from sun.* are subject to change in later versions (allthough this method exists since Java 5 with the same signature) and are not available for all Java implementations, see this discussion.

If that is a problem: you could create a generic proxy with your own InvocationHandler - this is exactly what AnnotationParser is doing for you internally. Or you use your own implementation of MyAnnotation as defined here. In both cases you should remember to implement annotationType(), equals() and hashCode() as the result is documented specifically for java.lang.Annotation.

Tobias Liefke
  • 8,637
  • 2
  • 41
  • 58
3

Rather crude way using the proxy approach with the help of 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);
        }
    });
}

The types of passed properties are not checked with the actual type declared on the annotation interface and any missing values are discovered only during runtime.

Pretty similar in function to the code mentioned in kaqqao's answer (and probably Gunnar's Answer as well), without the downsides of using internal Java API as in Tobias Liefke's answer.

carlspring
  • 31,231
  • 29
  • 115
  • 197
oujesky
  • 2,837
  • 1
  • 19
  • 18
1

I did this for adding annotation reference on my weld unit test:

@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";
        }
    }
}

usage:

Bean<?> createUserInfo() {
    return MockBean.builder()
            .types(UserInfo.class)
            .qualifiers(AuthenticatedUser.Literal.INSTANCE)
            .create((o) -> new UserInfo())
            .build();
}
Fabrizio Stellato
  • 1,727
  • 21
  • 52
1

Using 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));
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
0

@Gunnar's answer is the simplest way for most webservice as we already got hibernate, for example KafkaListener kafkaListener = new org.hibernate.validator.internal.util.annotation.AnnotationDescriptor.Builder<>(KafkaListener.class, ImmutableMap.of("topics", new String[]{"my-topic"})).build().getAnnotation(); and all other properties will stay default.

Nick Allen
  • 1,647
  • 14
  • 20
0

Take a look at AnnoBuilder. The nice thing is that it can use method reference instead of name of an attribute

@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");
ZhongYu
  • 19,446
  • 5
  • 33
  • 61