2

我在网上尝试了很多东西,但似乎没有什么对我有用。我想知道注释方法是否已经@Overriden (与它的值相同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 "";
    }
}
4

2 回答 2

2

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

通常的解决方法是指定一个正常人不会明确指定的默认值,然后检查该值。例如,JPA用于 ""此目的。

一个人可能会尝试

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 很可能会将默认值的显式分配标记为冗余。

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

于 2019-05-18T20:54:55.287 回答
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();
    }

}

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

于 2021-06-07T10:34:43.990 回答