是否有可能知道注释方法是否被覆盖(布尔值)?

时间:2019-05-17 10:42:08

标签: java annotations reflect

我在网上尝试了很多事情,但似乎没有任何效果。我想知道注释方法是否为@Override n(两者的值与其default相同)。

看看这个例子:

public class AnnoTest {

    @Anno
    private String something;

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        Field field = AnnoTest.class.getDeclaredField("something");
        field.setAccessible(true);
        boolean isDefault= field.getAnnotation(Anno.class).annotationType().getDeclaredMethod("include").isDefault();
        System.out.println(isDefault); //returns false

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;
    }
}

由于某种原因,它返回false。当我将其更改为:

@Anno(include = false)
private String something;

它再次返回false。有没有办法知道该值是否已在注释中声明?

我知道我可以将默认值与其当前值进行比较,但是它对我不起作用。我想知道它是否已经声明。


换句话说,我需要执行以下操作的魔术布尔:

@Anno
private String something;

返回false

@Anno(include = true)
private String something;

返回true

@Anno(include = false)
private String something;

返回true


其原因是我希望添加一个名为“ parent”的方法(到我的注释中)。声明了父项(字符串)后,此字段将继承名为parent的字段的注释。看一下这个例子:

public class AnnoTest {

    @Anno(include = false)
    private Something something = new Something();

    @Anno(parent = "something")
    private Something somethingElse  = new Something();

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        AnnoTest test = new AnnoTest();

        Field somethingField = AnnoTest.class.getDeclaredField("something");
        somethingField.setAccessible(true);

        Field somethingElseField = AnnoTest.class.getDeclaredField("somethingElse");
        somethingField.setAccessible(true);

        Anno anno = somethingElseField.getAnnotation(Anno.class);

        if (anno.parent().equals("something")) {
            boolean include = somethingField.getAnnotation(Anno.class).include();
            test.somethingElse.isIncluded = include;
        }

        //If not declared it will return true, which it should be false, because "something" field has it false.
        boolean include = somethingElseField.getAnnotation(Anno.class).include();
        //if somethingElse has declared "include", dominate the value, else keep it from the parent
        test.somethingElse.isIncluded = include;

    }

    public class Something {
        boolean isIncluded;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;

        String parent() default "";
    }
}

2 个答案:

答案 0 :(得分:1)

反射api不允许查询注释值是已明确指定还是仅默认设置。

通常的解决方法是指定一个默认值,任何没有主意的人都可以明确指定该默认值,然后检查该值。例如,为此目的,JPA uses ""

一个人可以尝试

Boolean value() default null;

但是正如您在注释中正确指出的那样,java不支持Boolean注释值,仅支持boolean注释值。您可以改为使用带有3个值的枚举,但这可能会对您的用户造成负担。

这留下了黑暗的魔力:您可以自己解析类文件。这将起作用,因为该类文件仅列出了指定的注释属性,如以下javap输出所示:

给予

@Anno(false)
public void foo() 

我们得到

Constant pool:
    ...
    #16 = Utf8               Lstackoverflow/Anno;
    #17 = Utf8               value
    #18 = Integer            0

  public void foo();
    descriptor: ()V
    flags: ACC_PUBLIC
    RuntimeVisibleAnnotations:
      0: #16(#17=Z#18)

但给定

@Anno()
public void foo() {

我们得到

Constant pool:
  ...
  #16 = Utf8               Lstackoverflow/Anno;

public void foo();
  descriptor: ()V
  flags: ACC_PUBLIC
  RuntimeVisibleAnnotations:
    0: #16()

也就是说,即使您设法做到这一点,也可能会使用户感到惊讶并使他们的工具混乱。例如,IDE很可能会将默认值的显式分配标记为冗余。

如果有可能,我将因此更改您的注释,这样您就不必区分是否已明确指定布尔值。

答案 1 :(得分:1)

我知道几年过去了,但作为参考,有一种不那么黑暗的魔法来实现这一目标。如果您有权访问 .java 文件,则可以使用 JavaCompiler api 处理注释并了解注释方法是否被覆盖。 小例子:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.*;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.Trees;

import java.util.List;
import java.util.Set;

@Retention(RetentionPolicy.RUNTIME)
@interface Anno {
    boolean include() default false;
}

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
class AnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(Anno.class)) {
            final Trees trees = Trees.instance(processingEnv);
            List<? extends AnnotationTree> annotationTrees = ((MethodTree) trees.getTree(e)).getModifiers().getAnnotations();
            System.out.printf("%s is annotated with %s%n", e, annotationTrees);
            if (annotationTrees.size() > 0 && annotationTrees.get(0).getArguments().size() > 0) {
                System.out.println("Using overridden value");
            } else {
                System.out.println("Using default value");
            }
        }
        return true;
    }
}

class Main {
    @Anno(include = false)
    public void includeFalse() {
    }

    @Anno(include = true)
    public void includeTrue() {
    }

    @Anno()
    public void includeDefault() {
    }

    public static void main(String[] args) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        File file = new File(System.getProperty("user.dir") + "/src/Annot.java"); // Location of the .java file
        Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(file));
        JavaCompiler.CompilationTask task = compiler.getTask(null,
                fileManager,
                null,
                null,
                null,
                fileObjects);
        task.setProcessors(Collections.singletonList(new AnnotationProcessor()));
        task.call();
    }

}

我仍然不建议这样做,因为如前所述,它会给用户带来很多困惑。事实上,我知道这个技巧的唯一原因是它在我们的代码中导致了一个很难找到的错误:)