Create Annotation instance with defaults, in Java

2020-01-30 07:20发布

问题:

How can I create an instance of the following annotation (with all fields set to their default value).

    @Retention( RetentionPolicy.RUNTIME )
    public @interface Settings {
            String a() default "AAA";
            String b() default "BBB";
            String c() default "CCC";
    }

I tried new Settings(), but that does not seem to work...

回答1:

To create an instance you need to create a class that implements:

  • java.lang.annotation.Annotation
  • and the annotation you want to "simulate"

For example: public class MySettings implements Annotation, Settings

But you need to pay special attention to the correct implementation of equals and hashCode according to the Annotation interface. http://download.oracle.com/javase/1.5.0/docs/api/java/lang/annotation/Annotation.html

If you do not want to implement this again and again then have a look at the javax.enterprise.util.AnnotationLiteral class. That is part of the CDI(Context Dependency Injection)-API. (@see code)

To get the default values you can use the way that is described by akuhn (former known as: Adrian). Settings.class.getMethod("a").getDefaultValue()



回答2:

You cannot create an instance, but at least get the default values

Settings.class.getMethod("a").getDefaultValue()
Settings.class.getMethod("b").getDefaultValue()
Settings.class.getMethod("c").getDefaultValue()

And then, a dynamic proxy could be used to return the default values. Which is, as far as I can tell, the way annotations are handled by Java itself also.

class Defaults implements InvocationHandler {
  public static <A extends Annotation> A of(Class<A> annotation) {
    return (A) Proxy.newProxyInstance(annotation.getClassLoader(),
        new Class[] {annotation}, new Defaults());
  }
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    return method.getDefaultValue();
  }
}

Settings s = Defaults.of(Settings.class);
System.out.printf("%s\n%s\n%s\n", s.a(), s.b(), s.c());


回答3:

I compile and ran below with satisfactory results.

class GetSettings {
    public static void main (String[] args){
      @Settings final class c { }
      Settings settings = c.class.getAnnotation(Settings.class);
      System.out.println(settings.aaa());
    }
}


回答4:

had the same issue, i solved it as follows.

public static FieldGroup getDefaultFieldGroup() {
    @FieldGroup
    class settring {
    }
    return settring.class.getAnnotation(FieldGroup.class);
}


回答5:

If used with a method:

@Settings
public void myMethod() {
}

Now your annotation is initialized with default values.



回答6:

This works with Sun/Oracle Java 5,6,7,8: (but could potentially break with Java 9 due to the sun classes involved). //edit Just verified that this still works with OpenJDK 9b59.

package demo;

import sun.reflect.annotation.AnnotationParser;

import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class AnnotationProxyExample
{

    public static void main(String[] args)
    {

        System.out.printf("Custom annotation creation: %s%n", 
                createAnnotationInstance(Collections.singletonMap("value", "required"), Example.class));

        System.out.printf("Traditional annotation creation: %s%n", 
                X.class.getAnnotation(Example.class));
    }

    private static <A extends Annotation> A createAnnotationInstance(Map<String, Object> customValues, Class<A> annotationType)
    {

        Map<String, Object> values = new HashMap<>();

        //Extract default values from annotation
        for (Method method : annotationType.getDeclaredMethods())
        {
            values.put(method.getName(), method.getDefaultValue());
        }

        //Populate required values
        values.putAll(customValues);

        return (A) AnnotationParser.annotationForMap(annotationType, values);
    }

    @Example("required")
    static class X
    {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface Example
    {
        String value();
        int foo() default 42;
        boolean bar() default true;
    }
}

Output:

Custom annotation creation: @demo.AnnotationProxyExample$Example(bar=true, foo=42, value=required)
Traditional annotation creation: @demo.AnnotationProxyExample$Example(bar=true, foo=42, value=required)


回答7:

There is alternative solution, if you can afford to change the body of Settings class:

@Retention( RetentionPolicy.RUNTIME )
public @interface Settings {
        String DEFAULT_A = "AAA";
        String DEFAULT_B = "BBB";
        String DEFAULT_C = "CCC";

        String a() default DEFAULT_A;
        String b() default DEFAULT_B;
        String c() default DEFAULT_C;
}

Then you can simply reference Settings.DEFAULT_A (yes, a better name would help!).