Файл: Кей С. Хорстманн.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 12.01.2024

Просмотров: 111

Скачиваний: 5

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

451
11.5. Обработка аннотаций на уровне исходного кода
Set extends Element> getElementsAnnotatedWith(
Class extends Annotation> a)
Set extends Element> getElementsAnnotatedWithAny(
Set> annotations)
// полезно для повторяющихся аннотаций

Эквивалентом интерфейса AnnotateElement для обработки аннотаций на уровне исходного кода является интерфейс AnnotatedConstruct. Для получения обычных или повторяющихся аннотаций из отдельного ан- нотированного класса служат следующие методы:
A getAnnotation(Class annotationType)
A[] getAnnotationsByType(Class
annotationType)

Интерфейс TypeElement представляет класс или интерфейс. А метод getEnclosedElements() получает список его полей и методов.

В результате вызова метода getSimpleName() по ссылке типа Element или метода getQualifiedName() по ссылке типа TypeElement получает- ся объект типа Name, который может быть преобразован в символьную строку методом toString().
11.5.3. Генерирование исходного кода с помощью аннотаций
Вернемся к рассмотренному ранее примеру генерирования методов типа
toString. Эти методы нельзя ввести в исходные классы. Ведь процессоры ан- нотаций способны производить только новые классы, а не изменять уже име- ющиеся. Следовательно, все методы должны быть введены в служебный класс
ToStrings следующим образом:
public class ToStrings { public static String toString(Point obj) {
Сгенерированный код
} public static String toString(Rectangle obj) {
Сгенерированный код
} public static String toString(Object obj) { return Objects.toString(obj);
}
}
В данном случае применять рефлексию не требуется, и поэтому аннотиру- ются методы доступа, но не поля:
@ToString public class Rectangle {
@ToString(includeName=false) public Point getTopLeft()
Java_SE_9_for_the_Impatient_2nd_Edit.indb 451 16.04.2018 16:40:27

Глава 11
„
Аннотации
452
{ return topLeft; }
@ToString public int getWidth() { return width; }
@ToString public int getHeight() { return height; }
}
И тогда процессор аннотаций должен сгенерировать следующий исход- ный код:
public static String toString(Rectangle obj) {
StringBuilder result = new StringBuilder(); result.append("Rectangle"); result.append("["); result.append(toString(obj.getTopLeft())); result.append(","); result.append("width="); result.append(toString(obj.getWidth())); result.append(","); result.append("height="); result.append(toString(obj.getHeight())); result.append("]"); return result.toString();
}
Шаблонный код выделен выше обычным шрифтом. Ниже приведен на- бросок метода, получающего метод toString() для класса с заданным пара- метром типа TypeElement.
private void writeToStringMethod(PrintWriter out,
TypeElement te) {
String className = te.getQualifiedName().toString();
Вывести заголовок метода и объявление построителя
символьных строк
ToString ann = te.getAnnotation(ToString.class); if (ann.includeName()) Вывести код для ввода имени класса for (Element c : te.getEnclosedElements()) { ann = c.getAnnotation(ToString.class); if (ann != null) { if (ann.includeName()) Вывести код, предназначенный
для ввода имени поля
Вывести код, предназначенный для присоединения
метода toString(obj.ИмяМетода())
}
}
Вывести код для возврата символьной строки
}
Ниже приведен набросок метода process() из процессора аннота- ций. В нем создается исходный файл для вспомогательного класса, а также
Java_SE_9_for_the_Impatient_2nd_Edit.indb 452 16.04.2018 16:40:27


453
11.5. Обработка аннотаций на уровне исходного кода выводится заголовок класса и по одному методу для каждого аннотируемого класса.
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment currentRound) { if (annotations.size() == 0) return true; try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(
"com.horstmann.annotations.ToStrings"); try (PrintWriter out = new PrintWriter( sourceFile.openWriter())) {
Вывести код для пакета и класса for (Element e : currentRound
.getElementsAnnotatedWith(
ToString.class)) { if (e instanceof TypeElement) {
TypeElement te = (TypeElement) e; writeToStringMethod(out, te);
}
}
Вывести код для метода toString(Object)
} catch (IOException ex) { processingEnv.getMessager().printMessage(
Kind.ERROR, ex.getMessage());
}
} return true;
}
За более подробными сведениями обращайтесь к примерам кода, со- провождающего данную книгу. Однако следует иметь в виду, что метод process() вызывается в последующих циклах обработки аннотаций с пустым списком аннотаций. И тогда происходит немедленный возврат из данного метода, чтобы не создавать исходный файл дважды.
СОВЕТ. Чтобы просмотреть циклы обработки аннотаций, выполните команду
javac с параметром
-XprintRounds. В итоге на экран будет выведен результат, аналогичный следующему:
Round 1: input files: {ch11.sec05.Point, ch11.sec05.Rectangle, ch11.sec05.SourceLevelAnnotationDemo} annotations: [com.horstmann.annotations.ToString] last round: false
Round 2: input files: {com.horstmann.annotations.ToStrings} annotations: []
Java_SE_9_for_the_Impatient_2nd_Edit.indb 453 16.04.2018 16:40:28

Глава 11
„
Аннотации
454
last round: false
Round 3: input files: {} annotations: [] last round: true
В данном примере было продемонстрировано, каким образом инструмен- тальные средства могут собирать аннотации из исходных файлов для полу- чения других файлов. Формируемые в итоге файлы совсем не обязательно должны быть исходными. Процессоры аннотаций могут сформировать дес- крипторы XML-разметки, файлы свойств, сценарии командного процессора, документацию в формате HTML и пр.
НА ЗАМЕТКУ. Выше было показано, каким образом обрабатываются аннотации в исход- ных файлах и в выполняющейся программе. Третья возможность состоит в том, чтобы обрабатывать аннотации в файлах классов, что обычно делается по ходу их загрузки в виртуальную машину. Для обнаружения и вычисления аннотаций и перезаписи байт-ко- дов потребуется инструментальное средство вроде ASM (
http://asm.ow2.org/).
Упражнения
1. Поясните, каким образом можно изменить метод Object.clone(), что- бы воспользоваться аннотацией @Cloneable вместо маркерного интер- фейса Cloneable.
2. Если бы аннотации присутствовали в первых версиях Java, то интер- фейс Serializable, безусловно, был бы снабжен аннотацией. Реализуй- те аннотацию @Serializable. Выберите текстовый или двоичный фор- мат для сохраняемости. Предоставьте классы для потоков ввода-вывода, чтения и записи, сохраняющих состояние объектов путем сохранения и восстановления всех полей, содержащих значения примитивных ти- пов или же самих поддающихся сериализации. Не обращайте пока что внимание на циклические ссылки.
3. Повторите предыдущее упражнение, но позаботьтесь о циклических ссылках.
4. Введите аннотацию @Transient в механизм сериализации, действую- щий подобно модификатору доступа transient.
5. Определите аннотацию @Todo, содержащую сообщение, описывающее все, что требуется сделать. Определите процессор аннотаций, произво- дящий из исходного файла список напоминаний о том, что требуется
Java_SE_9_for_the_Impatient_2nd_Edit.indb 454 16.04.2018 16:40:28


455
Упражнения сделать. Предоставьте описание аннотируемого элемента кода и сооб- щение о том, что требуется сделать.
6. Преобразуйте аннотацию из предыдущего упражнения в повторяющу- юся аннотацию.
7. Если бы аннотации существовали в первых версиях Java, они, скорее всего, выполняли бы роль утилиты
javadoc. Определите аннотации @
Param, @Return и т.д. и составьте из них элементарный HTML-документ с помощью процессора аннотаций.
8. Реализуйте аннотацию @TestCase, сформировав исходный файл, имя которого состоит из имени класса, где эта аннотация встречается, а так- же из имени Test. Так, если исходный файл MyMath.java содержит сле- дующие строки:
@TestCase(params="4", expected="24")
@TestCase(params="0", expected="1") public static long factorial(int n) { ... }
то сформируйте исходный файл MyMathTest.java со следующими опе- раторами:
assert(MyMath.factorial(4) == 24); assert(MyMath.factorial(0) == 1);
Можете допустить, что тестовые методы являются статическими и что элемент аннотации params содержит разделяемый запятыми список параметров соответствующего типа.
9. Реализуйте аннотацию @TestCase как динамическую и предоставьте инструментальное средство для ее проверки. И в этом случае можете допустить, что тестовые методы являются статическими. Можете также ограничиться умеренным набором параметров и возвращаемых типов, описываемых символьными строками в элементах аннотации.
10. Реализуйте процессор аннотаций @Resource, принимающий объект некоторого класса и обнаруживающий поля типа String, помечаемые аннотацией @Resource(name="URL"). Затем организуйте загрузку содер- жимого по заданному URL и внедрите строковую переменную с этим содержимым, используя рефлексию.
Java_SE_9_for_the_Impatient_2nd_Edit.indb 455 16.04.2018 16:40:28