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

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

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

@MyAnnotation String property;

Когда я получаю это свойство как элемент в моем процессоре, я никак не могу получить тип элемента. В этом случае a хотел бы получить экземпляр Class или 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);
    }
}

Ресурсы

Редактировать:

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

Это действительно возможно! Это можно сделать, используя больше методов на 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();

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

  1. Если аннотация, которую вы пытаетесь найти, уже скомпилирована (то есть из другой библиотеки), вы можете обратиться к классу напрямую, чтобы получить аннотацию.
  2. Если аннотация, которую вы пытаетесь найти, не скомпилирована (т.е. она компилируется в текущем вызове 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);
Другие вопросы по тегам