Получить класс поля в обработчике аннотаций

Я пишу свой первый обработчик Annotations и испытываю проблемы с чем-то, что кажется тривиальным, но я не могу найти никакой информации об этом.

У меня есть элемент, аннотированный с моей аннотацией

@MyAnnotation String property;

Когда я получаю это свойство как элемент в моем процессоре, я никак не могу получить тип элемента. В этом случае a хотел бы получить экземпляр класса или TypeElement, представляющий String.

Я попытался создать экземпляр объекта класса типа контейнера с помощью Class.forName(), но он исключил исключение ClassNotFoundException. Я думаю, это связано с тем, что у меня нет доступа к загрузчику классов, содержащему класс?

Ответы

Ответ 1

При запуске вашего обработчика аннотаций у вас нет доступа к скомпилированным классам. Точка обработки аннотаций заключается в том, что она выполняется с предварительным компиляцией.

Вместо этого вам нужно создать обработчик аннотации, который специально обрабатывает ваш тип аннотации, а затем использовать зеркальный API для доступа к полю. Например:

@SupportedAnnotationTypes("com.example.MyAnnotation")
public class CompileTimeAnnotationProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                           RoundEnvironment roundEnv) {
        // Only one annotation, so just use annotations.iterator().next();
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(
                annotations.iterator().next());
        Set<VariableElement> fields = ElementFilter.fieldsIn(elements);
        for (VariableElement field : fields) {
            TypeMirror fieldType = field.asType();
            String fullTypeClassName = fieldType.toString();
            // Validate fullTypeClassName
        }
        return true;
    }
}

Для проверки вы не можете использовать любые классы, которые еще не скомпилированы (включая те, которые собираются компилироваться с помощью аннотации), используя что-то вроде MyType.class. Для этого вы должны использовать только строки. Это связано с тем, что обработка аннотаций происходит во время фазы предварительной компиляции, известной как "генерация источника", что позволяет генерировать исходный код перед запуском компилятора с помощью аннотаций.

Пример проверки, подтверждающий, что тип поля java.lang.String (который уже скомпилирован):

for (VariableElement field : fields) {
    TypeMirror fieldType = field.asType();
    String fullTypeClassName = fieldType.toString();
    if (!String.class.getName().equals(fullTypeClassName)) {
        processingEnv.getMessager().printMessage(
                Kind.ERROR, "Field type must be java.lang.String", field);
    }
}

Ресурсы

Edit:

Я хочу, чтобы тип поля получал аннотации этого типа. Но это не похоже, что это будет возможно?

В самом деле, это возможно! Это можно сделать, используя больше методов на TypeMirror:

if (fieldType.getKind() != TypeKind.DECLARED) {
    processingEnv.getMessager().printMessage(
            Kind.ERROR, "Field cannot be a generic type.", field);
}
DeclaredType declaredFieldType = (DeclaredType) fieldType;
TypeElement fieldTypeElement = (TypeElement) declaredFieldType.asElement();

Здесь у вас есть два варианта:

  • Если аннотация, которую вы пытаетесь найти, уже скомпилирована (т.е. из другой библиотеки), вы можете напрямую ссылаться на класс, чтобы получить аннотацию.
  • Если аннотация, которую вы пытаетесь найти, не скомпилирована (т.е. она компилируется в текущем вызове javac, которая запускает APT), вы можете ссылаться на нее через экземпляры AnnotationMirror.

Уже скомпилировано

DifferentAnnotation diffAnn = fieldTypeElement.getAnnotation(
        DifferentAnnotation.class);
// Process diffAnn

Очень прямолинейно, это дает вам прямой доступ к самой аннотации.

Не скомпилировано

Обратите внимание, что это решение будет работать независимо от того, компилируется компиляция или нет, это просто не так чисто, как код выше.

Вот несколько методов, которые я написал один раз, чтобы извлечь определенное значение из зеркала аннотации по имени его класса:

private static <T> T findAnnotationValue(Element element, String annotationClass,
        String valueName, Class<T> expectedType) {
    T ret = null;
    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
        DeclaredType annotationType = annotationMirror.getAnnotationType();
        TypeElement annotationElement = (TypeElement) annotationType
                .asElement();
        if (annotationElement.getQualifiedName().contentEquals(
                annotationClass)) {
            ret = extractValue(annotationMirror, valueName, expectedType);
            break;
        }
    }
    return ret;
}

private static <T> T extractValue(AnnotationMirror annotationMirror,
        String valueName, Class<T> expectedType) {
    Map<ExecutableElement, AnnotationValue> elementValues = new HashMap<ExecutableElement, AnnotationValue>(
            annotationMirror.getElementValues());
    for (Entry<ExecutableElement, AnnotationValue> entry : elementValues
            .entrySet()) {
        if (entry.getKey().getSimpleName().contentEquals(valueName)) {
            Object value = entry.getValue().getValue();
            return expectedType.cast(value);
        }
    }
    return null;
}

Скажем, что вы ищете аннотацию DifferentAnnotation, и ваш исходный код выглядит следующим образом:

@DifferentAnnotation(name = "My Class")
public class MyClass {

    @MyAnnotation
    private String field;

    // ...
}

Этот код напечатает My Class:

String diffAnnotationName = findAnnotationValue(fieldTypeElement,
        "com.example.DifferentAnnotation", "name", String.class);
System.out.println(diffAnnotationName);