如何在Java中使用自定义类型注解

10

Java 8有一个被称为类型注解的特性(JSR 308)。我想将其用于简单的对象映射框架。我想定义这样的注释@ExpectedType

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpectedType {
    public Class<?> value();
}

然后在我的代码中像这样使用它:

public class SomeServiceImpl() {
    public @ExpectedType(ObjectA_DTO.class) IObjectA doSomething(@ExpectedType(ObjectA_Entity.class) IObjectA obj) {
        return (ObjectA_Entity) obj; // it's correct
    }
}

IObjectA 是由类 ObjectA_DTOObjectA_Entity 实现的接口。 我想以这种方式使用服务:

// it's correct
assert someService.doSomething(new ObjectA_DTO()).getClass() == ObjectA_DTO.class;

我想将SomeServiceImpl方法的调用更改为使用Object Mapper。可以通过使用JSR 269生成代码或AOP来实现。

问题是我编写了一个简单的注解处理器,它根本不处理类型注解。简单注解处理器的源代码如下:

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SimpleAnnotationsProcessor extends AbstractProcessor {

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        try {
            for (TypeElement e : annotations) {
                messager.printMessage(Diagnostic.Kind.NOTE, e.toString());
                for (Element elem : roundEnv.getElementsAnnotatedWith(e)) {
                    messager.printMessage(Diagnostic.Kind.NOTE, elem.toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}

有没有想法如何使用或如何通过SimpleAnnotationsProcessor访问类型注释?我认为对于我来说,使用可插拔的注释处理API并不是必要的,我认为它比Java反射性能更好。无论如何,我也不知道如何通过Java反射访问类型注释。


我猜我不太清楚你试图实现什么目标。如果您期望的是这种行为,那为什么不直接将方法签名设置为 ObjectA_DTO doSomething(ObjectA_Entity) 呢? - Ian McLaird
方法签名不能这样,因为我想像这样使用服务实例 someService.doSomething(new ObjectA_DTO())。我想要实现对象映射器来将 ObjectA_DTO 映射到 ObjectA_Entity,并且类型注释 @ExpectedType 定义了目标类型。这就是为什么签名必须是 IObjectA doSomething(IObjectA) 的原因。 - Boris Šuška
我刚好路过看到了你的问题... 我曾经做过一个关于产品线开发的学期项目,其中我们处理了注释。现在,我会给你留下这个非常重要的教程,它真正让我对这个主题有了深入的了解(你可以跳过第三部分关于代码生成的内容)- https://deors.wordpress.com/2011/09/26/annotation-types/ 以后,我会再次查看这篇文章以了解进展情况。 - ThisClark
谢谢你提供的链接。我在说的是注释,它们被注释为@Target(ElementType.TYPE_PARAMETER)@Target(ElementType.TYPE_USE)。它们不会传递到注解处理器的 process 方法中。 - Boris Šuška
3个回答

3
我不确定我理解你想要实现什么,但是这里是一个例子,展示了如何使用Java反射API访问您的注释:
package test;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;

public class TypeParameterTest {

    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExpectedType {
        public Class<?> value();
    }

    public static interface IObjectA {}

    public static class ObjectA_DTO implements IObjectA {}

    public static class ObjectA_Entity implements IObjectA {}

    public static class SomeServiceImpl {
        public @ExpectedType(ObjectA_DTO.class) IObjectA doSomething(@ExpectedType(ObjectA_Entity.class) IObjectA obj) {
            return (ObjectA_Entity) obj;
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, SecurityException {
        Method m = SomeServiceImpl.class.getMethod("doSomething", IObjectA.class);
        AnnotatedType returnType = m.getAnnotatedReturnType();
        Annotation returnTypeAnnotation = returnType.getAnnotation(ExpectedType.class);
        System.out.println(returnTypeAnnotation);

        AnnotatedType[] parameters = m.getAnnotatedParameterTypes();
        for (AnnotatedType p : parameters) {
            Annotation parameterAnnotation = p.getAnnotation(ExpectedType.class);
            System.out.println(parameterAnnotation);
        }
    }
}

输出结果如下:
@test.TypeParameterTest$ExpectedType(value=class test.TypeParameterTest$ObjectA_DTO)
@test.TypeParameterTest$ExpectedType(value=class test.TypeParameterTest$ObjectA_Entity)

请注意,虽然并非所有可能的类型注释都可以通过反射 API 访问,但是如果必要的话,您始终可以从字节码中读取它们(请参阅我的答案这里)。

我想使用注解处理器来完成它,因为在运行时它可能比反射更快,但无论如何我接受你的答案。检查器框架可以从注解处理器中读取类型注释(而不是从字节码中读取)。你知道它是怎么做到的吗? - Boris Šuška
抱歉,我还没有使用Checker框架。我不相信注解处理器比使用反射更快(虽然不确定)。但在遇到实际的性能问题之前,我不会考虑任何优化。通常情况下,反射已经足够快了... - Balder
你知道如何通过反射API访问用于本地变量的类型注解吗?就像这样 @NotNull String str = ""; 或者 String str = (@Nullable String) null; 在方法体内部?我想使用 @ExpectedType 注解,就像这样:ObjectA_DTO aDto = (@ExpectedType ObjectA_DTO) someService.doSomething(...); - Boris Šuška
使用Java反射API无法访问在变量声明或强制转换表达式中声明的类型注释。您可以通过使用外部库(例如ASM)读取方法的字节码来访问它们。我在此答案的最后一个“hello world”示例中使用了这种方法。以这种方式访问类型注释当然是进行静态代码分析和工具化的一种选择,但我不建议将其用于运行时访问类型注释... - Balder

0

我认为您混淆了在运行时使用注释和各种工具在“编译”时使用注释的用途。 Processor 接口是供工具(编译器、javadoc 生成器)使用的,而不是在您的运行时代码中使用。


我想生成更好性能的代码,就像MapStruct或Lombok一样,而不是在运行时使用Java反射。 - Boris Šuška

-1
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SearchDefinition {

    public String identifier() default "";

}

@SearchDefinition - 可以在任何地方使用


2
这个答案完全是错误的,@Target(ElementType.FIELD) 注解不能在任何地方使用。它只能用于类字段。如果省略 @Target,则几乎可以在任何地方使用,除了类型转换 ((@SearchDefinition String) "abc") 或泛型类型 (List<@SearchDefinition String>)。 - Boris Šuška

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接