Главная страница
Навигация по странице:

  • ToString ts = cl.getAnnotation(ToString.class)

  • Rectangle

  • Кей С. Хорстманн


    Скачать 1.62 Mb.
    НазваниеКей С. Хорстманн
    Дата07.04.2023
    Размер1.62 Mb.
    Формат файлаpdf
    Имя файлаjavase9bazovyykurs.pdf
    ТипДокументы
    #1044418
    страница4 из 4
    1   2   3   4
    447
    11.4. Обработка аннотаций во время выполнения
    @ToString public class Rectangle {
    @ToString(includeName=false) private Point topLeft;
    @ToString private int width;
    @ToString private int height;
    }
    Во время выполнения нельзя изменить реализацию метода toString() в отдельном классе. Вместо этого можно предоставить метод, способный от- форматировать любой объект, обнаруживая и применяя аннотации @To
    String, если они имеются.
    Для обработки аннотаций служат следующие методы из интерфейса
    AnnotatedElement, который реализуется в классах рефлексии Class, Field,
    Parameter, Method, Constructor и Package:
    T getAnnotation(Class)
    T getDeclaredAnnotation(Class)
    T[] getAnnotationsByType(Class)
    T[] getDeclaredAnnotationsByType(Class)
    Annotation[] getAnnotations()
    Annotation[] getDeclaredAnnotations()
    Как и остальные методы рефлексии, методы со словом Declared в их име- ни получают аннотации в самом классе, тогда как остальные методы включа- ют наследуемые аннотации. В контексте аннотаций это означает аннотацию
    @Inherited, применяемую в суперклассе.
    Если аннотация не является повторяющейся, для ее обнаружения следует вызвать метод getAnnotation(), как показано ниже.
    Class cl = obj.getClass();
    ToString ts = cl.getAnnotation(ToString.class); if (ts != null && ts.includeName()) ...
    Обратите внимание на то, что методу getAnnotation() передается объ- ект класса для аннотации (в данном случае — объект ToString.class), а возвращается объект некоторого класса-заместителя, реализующего интер- фейс ToString. Для получения значений элементов аннотации можно вы- звать методы из этого интерфейса. Если же аннотация отсутствует, то метод getAnnotation() возвращает пустое значение null.
    Дело несколько усложняется, если аннотация оказывается повторяющей- ся. Если вызвать метод getAnnotation() для поиска повторяющейся аннота- ции, которая на самом деле не повторялась, то и в этом случае может быть получено пустое значение null. Объясняется это тем, что повторяющиеся ан- нотации были заключены в оболочку контейнерной аннотации.
    Java_SE_9_for_the_Impatient_2nd_Edit.indb 447 16.04.2018 16:40:27

    Глава 11
    „
    Аннотации
    448
    В данном случае следует вызвать метод getAnnotationsByType(), где про- сматривается контейнер и предоставляется массив повторяющихся аннота- ций. Если бы имелась только одна аннотация, то она была бы получена в массиве единичной длины. Имея в своем распоряжении данный метод, мож- но вообще не беспокоиться о контейнерной аннотации.
    Метод getAnnotations() получает все аннотации (любого типа), которы- ми аннотируется элемент кода, причем повторяющиеся аннотации заклю- чаются в оболочку контейнеров. Ниже приведен пример реализации метода toString() с учетом аннотаций.
    public class ToStrings { public static String toString(Object obj) { if (obj == null) return "null";
    Class cl = obj.getClass();
    ToString ts = cl.getAnnotation(ToString.class); if (ts == null) return obj.toString();
    StringBuilder result = new StringBuilder();
    if (ts.includeName()) result.append(cl.getName()); result.append("["); boolean first = true; for (Field f : cl.getDeclaredFields()) {
    ts = f.getAnnotation(ToString.class); if (ts != null) { if (first) first = false; else result.append(","); f.setAccessible(true);
    if (ts.includeName()) { result.append(f.getName()); result.append("=");
    } try { result.append(ToStrings.toString(f.get(obj)));
    } catch (ReflectiveOperationException ex) { ex.printStackTrace();
    }
    }
    } result.append("]"); return result.toString();
    }
    }
    Если класс аннотируется средствами интерфейса ToString, то в методе toString() перебираются поля этого класса и выводятся те из них, которые также аннотированы. Если же элемент includeName имеет логическое значе- ние true, то имя класса или поля включается в результирующую символь- ную строку.
    Java_SE_9_for_the_Impatient_2nd_Edit.indb 448 16.04.2018 16:40:27

    449
    11.5. Обработка аннотаций на уровне исходного кода
    Следует иметь в виду, что данный метод вызывается рекурсивно. Всякий раз, когда объект принадлежит классу, который не аннотирован, вызывается его обычный метод toString() и рекурсия останавливается.
    Это простой, но типичный пример применения прикладного программ- ного интерфейса API для обработки аннотаций во время выполнения. Клас- сы, поля и прочие элементы кода обнаруживаются с помощью рефлек- сии, а с целью извлечь аннотации вызывается метод getAnnotation() или getAnnotationsByType() для потенциально аннотированных элементов.
    И далее для получения значений отдельных элементов аннотации вызывают- ся методы из соответствующего интерфейса аннотаций.
    11.5. Обработка аннотаций на уровне исходного кода
    В предыдущем разделе было показано, каким образом аннотации анали- зируются в выполняющейся программе. Еще одним примером применения аннотаций служит автоматическая обработка исходных файлов для получе- ния дополнительного исходного кода, файлов конфигурации, сценариев и вообще всего, что можно сгенерировать.
    Чтобы продемонстрировать внутренний механизм обработки аннотаций на уровне исходного кода, вернемся к примеру формирования методов типа toString. Но на этот раз они будут сформированы в исходном файле Java.
    Затем эти методы будут скомпилированы вместе с остальной частью про- граммы и выполнены с максимальным быстродействием вместо применения рефлексии.
    11.5.1. Процессоры аннотаций
    Обработка аннотаций встроена в компилятор Java. Во время компиляции
    процессоры аннотаций можно вызывать по следующей команде:
    javac -processor ИмяКлассаПроцессора
    1
    ,
    ИмяКлассаПроцессора
    2
    ,...
    Исходные_файлы
    Компилятор обнаруживает аннотации в исходных файлах. Каждый про- цессор аннотаций выполняется по очереди с учетом тех аннотаций, к кото- рым он проявил интерес. Если процессор аннотаций создает новый исход- ный файл, то данный процесс повторяется. Как только все исходные файлы будут обработаны, они компилируются.
    НА ЗАМЕТКУ. Процессор аннотаций может только формировать новые исходные файлы, но не может изменять уже имеющиеся исходные файлы.
    Java_SE_9_for_the_Impatient_2nd_Edit.indb 449 16.04.2018 16:40:27

    Глава 11
    „
    Аннотации
    450
    Процессор аннотаций реализует интерфейс Processor, как правило, рас- ширяя класс AbstractProcessor. При этом нужно указать, какие именно аннотации поддерживаются процессором. В данном случае это следующие аннотации:
    @SupportedAnnotationTypes(
    "com.horstmann.annotations.ToString")
    @SupportedSourceVersion(SourceVersion.RELEASE_8) public class ToStringAnnotationProcessor extends AbstractProcessor {
    @Override public boolean process(
    Set annotations,
    RoundEnvironment currentRound) {
    }
    }
    Процессору могут потребоваться конкретные типы аннотаций, метасим- волы подстановки вроде "com.horstmann.*" (т.е. все аннотации из пакета com.horstmann и любых его подпакетов) или даже "*" (т.е. все аннотации во- обще). Метод process() вызывается один раз на каждом цикле обработки со всеми аннотациями, обнаруженными в любых файлах в данном цикле, а также со ссылкой на интерфейс RoundEnvironment, содержащей сведения о текущем цикле обработки.
    11.5.2. Прикладной программный интерфейс API модели языка
    Для анализа аннотаций на уровне исходного кода служит прикладной программный интерфейс API модели языка. В отличие от прикладного про- граммного интерфейса API для рефлексии, представляющего классы и ме- тоды на уровне виртуальной машины, прикладной программный интерфейс
    API модели языка позволяет анализировать программу на Java по правилам языка Java.
    Компилятор получает дерево, узлами которого являются экземпляры клас- сов, реализующих интерфейс javax.lang.model.element.Element и произво- дные от него интерфейсы TypeElement, VariableElement, ExecutableElement и т.д. Они служат статическими аналогами классов рефлексии Class, Field/
    Parameter, Method/Constructor.
    Не вдаваясь в подробности прикладного программного интерфейса API модели языка, ниже перечислим главные его особенности, о которых нужно знать для обработки аннотаций.

    Интерфейс RoundEnvironment предоставляет все элементы кода, поме- ченные конкретной аннотацией. Для этой цели вызывается следующий метод:
    Java_SE_9_for_the_Impatient_2nd_Edit.indb 450 16.04.2018 16:40:27

    451
    11.5. Обработка аннотаций на уровне исходного кода
    Set getElementsAnnotatedWith(
    Class a)
    Set 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 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
    1   2   3   4


    написать администратору сайта