Получить класс поля в процессоре аннотаций
Я пишу свой первый процессор аннотаций и испытываю проблемы с чем-то, что кажется тривиальным, но я не могу найти информацию об этом.
У меня есть элемент, аннотированный моей аннотацией
@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);
}
}
Ресурсы
- Главная страница APT
- Зеркальный API Javadocs (Java 7 и старше)
- Изменить: Зеркальный API Javadocs (Java 8)
- Обратите внимание, что зеркальный API теперь стандартизирован в Java 8 под
javax.lang.model
и старый API устарел. Смотрите этот пост в блоге для получения дополнительной информации. Если вы использовалиjavax
занятия, тогда вам не нужно беспокоиться.
- Обратите внимание, что зеркальный API теперь стандартизирован в Java 8 под
Редактировать:
Я хочу получить тип поля, чтобы получить аннотации к этому типу. Но это не похоже, что это будет возможно?
Это действительно возможно! Это можно сделать, используя больше методов на 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);