Java®
Библиотека профессионала
Том 2. Расширенные средства программирования
Ещё больше книг по Java в нашем телеграм
канале:
https://t.me/javalib
Core Java®
Volume 11 - Advanced Features
Eleventh Edition
Сау
S. Horstmann
е
Pearson
Boston • Columbus • lndianapolis • New York • San Francisco • Amsterdam •
Саре Town Dubai • London • Madrid • Milan • Munich • Paris • Montreal • Toronto • Delhi •
Mexico City Sao Paulo • Sydney • Hong Kong • Seoul • Singapore • Taipei • Tokyo
Библиотека профессионала
Том 2. Расширенные средства
программирования
Одиннадцатое издание
Кей Хорстманн
Москва
• Санкт-Петербург
2020
ББК
32.973.26-018.2.75
Х82
у дк
004.432.2
ООО "Диалектика"
Зав. редакцией С.Н. Tpillljб
Перевод с английского и редакция И.В. Берштеl1нп
По общим вонросам обра1цайтесь в издательство "Диалектика" 110 адресу:
info@dialektika.com, http://www.dialektika.com
Хорстманн, Кей С.
Х82
Java.
Библиотека
профессионала,
11ро1раммирования, 11-е изд.: Пер. с англ.
864
том
-
2.
Расширенные
средства
СПб.: ООО "Диалектика",
2020.
с.: ил.
- Парал. тит. англ.
SBN 978-5-907144-38-5 (рус., том 2)
ISBN 978-5-907144-30-9 (рус., многотом)
ББК
32.973.26-018.2.75
Все названия про1раммных продуктов нвляются зарегистрированными торговыми марками
соответствующих фирм.
Никакан часгь настонщего изланин ни в каких целях не может бьпь воспрт11ведена в какой
бы то ни было форме и какими бы то ни было срелсгвами, будь то электронные или механиче
ские, вклю•1ая фотокопирование и зашкь на мапштный носитель, если на это нет письменного
разре111ею1я 11злател1,ства l'reпtice Hall, lпс.
Написание сценариев, компиляция и обработка аннотаций
435
Глава
9. Модульная система
Глава
1О.
Глава
11. Расширенные средства Swing
Глава
12. Платформенно-ориентированные
Ввод и вывод
163
API даты
и времени
на платформе
Java
Безопасность
Предметный указатель
353
493
521
и графика
методы
601
787
849
Содержание
Предисловие
13
К читателю
От издательства
13
13
16
16
16
18
Глава
19
Краткий обзор книги
Условные обозначения
Примеры исходного кода
Благодарности
1.
Потоки данных
1.1. От итерации к потоковым операциям
1.2. Создание потока данных
1.3. Методы filter (), map () и flatMap ()
1.4. Извлечение подпотоков и объединение потоков данных
1.5. Другие операции преобразования потоков данных
1.6. Простые методы сведения
1.7. Тип Optional
1.7.1. Получение необязательных значений
1.7.2. Употребление необюательных значений
1.7.3. Конвейеризация необязательных значений
1.7.4. Как не следует обрабатывать необязательные значения
1.7.5. Формирование необязательных значений
1.7.6. Сочетание функций необязательных значений с методом flatMap ()
1.7.7. Преобра:ювание типа Optional в поток данных
1.8. Накопление результатов
1.9. Накопление результатов в отображениях
1.10. Группирование и разделение
1.11. Нисходящие коллекторы
1.12. Операции сведения
1.13. Потоки данных примитивных типов
1.14. Параллельные потоки данных
2.1. Потоки ввода-вывода
2.1.1. Чтение и запись байтов
2.1.2. Полный комплект потоков ввода-вывода
2.1.3. Сочетание фильтров потоков ввода-вывода
2.1.4. Ввод-вывод текста
2.1.5. Вывод текста
2.1.6. Ввод текста
71
72
75
79
82
83
85
Содержание
2.1.7. Сохранение объектов в текстовом формате
2.1.8. Кодировки символов
2.2. Чтение и запись двоичных данных
2.2.1. Интерфейсы Datalnput и DataOutput
2.2.2. Файлы с произвол1,ным доступом
2.2.3. ZIР-архивы
2.3. Потоки ввода-вывода и сериализация объектов
2.3.1. Сохранение и загрузка сериализируемых объектов
2.3.2. Представление о формате файлов для сериализации
2.3.3. Видоизменение исходного механизма сериализации
2.3.4. Сериализация одноэлементных множеств
объектов
86
90
92
92
95
99
102
102
107
113
2.3.5. Контроль версий
2.3.6. Применение сериализации для клонирования
2.4. Манипулирование файлами
2.4.1. Пути к файлам
2.4.2. Чтение и запись данных в файлы
2.4.3. Создание файлов и каталогов
2.4.4. Копирование, перемещение и удаление файлов
2.4.5. Получение сведений о файлах
2.4.6. Обход элементов каталога
2.4.7. Применение потоков каталогов
2.4.8. Системы ZIР-файлов
2.5. Файлы, отображаемые в памяти
2.5.1. Эффективность файлов, отображаемых в памяти
2.5.2. Структура буфера данных
2.6.3. Блокирование файлов
2.6. Peryлярные выражения
2.7.2. Совпадение со строкой
2.7.3. Обнаружение многих совпадений
2.7.4. Разбиение строк по разделителям
2.7.5. Замена совпадений
3.1. Введение в ХМL
3.2. Струкrура ХМL-документа
3.3. Синтаксический анализ ХМL-документов
3.4. Проверка достоверности ХМL-документов
3.4.1. Определения типов докумеtпов
3.4.2. Схема ХМL-документов
3.4.3. Практический пример применения ХМL-документов
3.5. Поиск информации средствами XPath
3.6. Использование пространств имен
3.7. Потоковые синтаксические анализаторы
3.7.1. Применение SАХ-анализатора
3.7.2. Применение StАХ-анализатора
3.8. Формирование ХМL-документов
3.8.1. ХМL-документы без пространств имен
3.8.2. ХМL-документы с пространствами имен
3.8.3. Запись ХМL-документов
3.8.4. Запись ХМL-документов средствами StAX
3.8.5. Пример формирования файла 11 формате SVG
3.9. Преобразование ХМL-документов языковыми средствами XSLT
215
217
222
224
Глава
235
4.
Работа в сети
4.1. Подключение к серверу
4.1.1. Применение утилиты telnet
4.1.2. Подключение к серверу из программы на Java
4.1.3. Время ожидания для сокетов
4.1.4. Межсетевые адреса
4.2. Реализация серверов
4.2.1. Сокеты сервера
4.2.2. Обслуживание многих клиентов
4.2.3. Полузакрьпие
4.2.4. Прерываемые сокеты
4.3. Получение данных из Интернета
4.3.1. URL и URI
4.3.2. Извлечение данных средствами класса URLConnection
4.3.3. Отправка данных формы
4.4. НТТР-клиент
4.5. Отправка электронной почты
5.1. Структура JDBC
5.1.1. Типы драйверов JDBC
5.1.2. Типичные примеры применения JDBC
5.2. Язык SQL
5.3. Конфигурирование JDBC
5.3.1. URL баз данных
5.3.2. Архинные JАR-файлы драйверов
5.3.3. Запуск ба:ш данных
5.3.4. Регистрация класса драйвера
5.3.5. Подключение к базе данных
5.4. Работа с операторами JDBC
5.4.1. Выполнение операторов SQL
5.4.2. Управление подключениями, операторами
и результирующими наборами
5.4.3. Анализ исключе11ий SQL
5.4.4. Запол11ение базы да11ных
5.5. Выполнение запросов
5.5.1. Подготовленные операторы для запросов
5.5.2. Чте11ие и запись больших объектов
5.5.3. Синтаксис переходов в SQL
5.5.4. Множественные результаты
5.5.5. Извлечение автоматически генерируемых ключей
5.6. Прокручиваемые и обновляемые результирующие наборы
5.6.1. Прокручиваемые результирующие наборы
5.6.2. Обновляемые результирующие наборы
5.7. Наборы строк
5.7.1. Построение наборов строк
5.7.2. Кешируемые наборы строк
5.8. Метаданные
5.9. Транзакции
5.9.1. Программирование транзакций средствами JDBC
5.9.2. Точки сохранения
5.9.3. Групповые обновления
5.9.4. Расширенные типы данных SQL
5.10. Управление подключением к базам данных в неб-приложениях
и корпоративных приложениях
Глава
6.1.
6.2.
6.3.
6.4.
6.5.
6.6.
6.7.
6.
Прикладной интерфейс
API даты
350
и времени
Временная шкала
Местные даты
Корректоры дат
Месrное время
Поясное время
Форматирование и синтаксический анализ даты и времени
Взаимодействие с унаследованным кодом
Глава
7.
331
331
332
335
345
345
346
346
349
Интернационализация
7.1. Региональные насrройки
7.1.1. Назначение региональных настроек
7.1.2. Указание региональных настроек
7.1.3. Региональные настройки по умолчанию
7.1.4. Огображаемые имена
7.2. Форматирование чисел
7.2.1. Форматирование числовых значений
7.2.2. Форматирование денежных сумм в разных валютах
7.3. Форматирование даты и времени
7.4. Сортировка и нормализация
7.5. Форматирование сообщений
7.5.1. Форматирование чисел и дат
7.5.2. Форматы выбора
7.6. Ввод-вывод тексrа
7.6.1. Текстовые файлы
7.6.2. Окончания строк
7.6.3. Консольный ввод-вывод
7.6.4. Протокольные файлы
7.6.5. Огметка порядка следования байтов в кодировке UTF-8
7.6.6. Кодирование символов в исходных файлах
7.7. Комплекты ресурсов
7.7.1. Обнаружение комплектов ресурсов
7.7.2. Файлы свойств
7.7.3. Классы комплектов ресурсов
7.8. Пример интернационализации прикладной программы
Написание сценариев, компиляция и обработка аннотаций
8.1. Написание сценариев для платформы Java
8.1.1. Получение интерпретатора сценариев
8.1.2. Выполнение сценарие11 и привязки
8.1.3. Переадресация 111юда-11ывода
8.1.4. Вы:юв функций и методов из сценариев
8.1.5. Компиляция сце11арие11
8.1.6. Пример со:ца11ия сце11'1рия для обработки
436
436
437
439
440
442
событий
в пользовател1,ском интерфейсе
8.2. Прикладной интерфейс API для компилятора
8.2.1. Вызов компилятора
8.2.2. Запуск заданий 11а компиляцию
8.2.3. Фиксация диагностики
8.2.4. Чтение исходных файлов из оперативной памяти
8.2.5. Запись байт-кодов в оперативную память
8.2.6. Пример динамического генерирования кода Java
8.3. Применение аннотаций
8.3.1. Введение в аннотации
8.3.2. Пример аннотирования обработчиков событий
8.5.
9.
449
450
450
451
453
459
460
Аннотирование объявлений
470
Аннотирование в местах употребления типов данных
471
Аннотирование по ссылке
472
this
473
8.5.1. Аннотации для компиляции
8.5.2. Аннотации для управления ресурсами
8.5.3. Мета-аннотации
8.6. Обработка аннотаций на уровне исходного кода
8.6.1. Процессоры аннотаций
8.6.2. Прикладной интерфейс АР\ модели языка
8.6.3. Генерирование исходного кода с помощью аннотаций
8.7. Конструирование байт-кодов
8.7.1. Модификация файлов классов
Глава
448
Объявление аннотаций
Стандартные аннотации
8.7.2.
443
448
461
466
466
468
Синтаксис аннотаций
8.4.1. Интерфейсы а1111отаций
8.4.
8.4.2.
8.4.3.
8.4.4.
8.4.5.
435
474
475
476
478
479
479
480
483
483
Модификация байт-кодов во время загрузки
489
Модульная система на платформе
493
Java
494
9.1.
9.2.
9.3.
Понятие модуля
9.4.
9.5.
Требования модулей
498
Экспорт пакетов
500
503
505
508
510
Име11011ание модулей
Пример модульной программы
495
"Hello, Modular World!"
9.6. Модульные архивные JАR-файлы
9.7. Модули и рефлексивный доступ
9.8.
9.9.
Автоматические модули
Безымянные модули
496
Содержание
9.10.
9.11.
9.12.
9.13.
9.14.
Параметры командной сrроки для переноса прикладного кода
Переходные и сrатические требования
Уточненный экспорт и открытие модулей
Загрузка служб
Инсrрументальные средсrва для работы с модулями
Глава
1О.
Безопасность
521
10.1. Загрузчики классов
10.1.1. Процесс загрузки классов
10.1.2. Иерархия загрузчиков классов
10.1.3. Применение загрузчиков классов в качестве
10.1.4. Создание собственного загрузчика классов
10.1.5. Верификация байт-кода
10.2. Диспетчеры защиты и полномочия
10.2.1. Проверка полномочий
10.2.2. Организация защиты на платформе Java
10.2.3. Файлы правил защиты
10.2.4. Специальные полномочия
10.2.5. Реализация класса полномочий
10.3. Аутентификация пользователей
10.3.1. Каркас JAAS
10.3.2. Модули регистрации JAAS
10.4. Цифровые подписи
10.4.1. Свертки сообщений
10.4.2. Подписание сообщений
10.4.3. Верификация подписи
10.4.4. Проблема аутентификации
10.4.5. Подписание сертификатов
10.4.6. Запросы сертификатов
10.4.7. Подписание кода
10.5. Шифрование
10.5.1. Симметричные шифры
10.5.2. Генерирование ключей шифрования
10.5.3. Потоки шифрования
10.5.4. Шифрование открытым ключом
Глава
11. Расширенные средства Swing
511
512
514
514
517
пространств имен
и графика
11.1. Таблицы
11.1.1. Проста я таблица
11.1.2. Модели таблиц
11.1.3. Манипулирование строками и столбцами
11.1.4. Воспроиз11едение и редактиро11ание ячеек
11.2. Деревья
11.2.1. Простые деревья
11.2.2. Перечисление узлов дерева
11.2.3. Воспроизведение узлов дерева
11.2.4. Обработка событий в деревьях
11.2.5. Специальные модели деревье11
11.3. Расширенные средства AWT
11.3.1. Конвейер визуализации
11.3.2. Фигуры
11.3.3. Участки
11.3.4. Обводка
11.3.5. Раскраска
11.3.6. Преобразование координат
11.3.7. Отсечение
11.3.8. Прозрачность и композиция
11.4. Растровые изображения
11.4.1. Чтение и запись изображений
11.4.2. Манипулирование изображениями
11.5. Вывод изображений на печать
11.5.1. Вывод графики на печать
11.5.2. Многостраничная печать
11.5.3. Службы печати
11.5.4. Потоковые службы печати
11.5.5. Атрибуты печати
12.1. Вызов функции на С из программы на Java
12.2. Числовые параметры и возвращаемые значения
12.3. Строковые параметры
12.4. Доступ к полям
12.4.1. Доступ к полям экземпляра
12.4.2. Доступ к статическим полям
12.5. Кодирование сигнатур
12.6. Вызов методов на Java
12.6.1. Методы экземпляра
12.6.2. Статические методы
12.6.3. Конструкторы
12.6.4. Альтернативные вызовы методов
12.7. Доступ к элементам массивов
12.8. Обработка ошибок
12.9. Применение прикладного интерфейса API для вызовов
12.10. Практический пример обращения к реестру Windows
12.10.1. Общее представление о реестре Windows
12.10.2. Интерфейс для доступа к реестру на платформе Java
12.10.3. Реализация функций доступа к реестру
в виде платформенно-ориентированных методов
К читателю
Книга, которую вы держите в руках, является вторым томом одиннадцатого
издания, полностью обновленного по версии
вались основные языковые средства
Java,
Java 11.
В первом томе рассматри
а в этом томе речь пойдет о расширен
ных функциональных возможностях, которые могут понадобиться программисту
для разработки программного обеспечения на высоком профессиональном уров
не. Поэтому этот том, как, впрочем, и первый том настоящего и предыдущих
изданий данной книги, нацелен на тех программистов, которые собираются при
менять технологию
Java
в работе над реальными проектами.
Краткий обзор книги
В целом главы этого тома составлены независимо друг от друга. Это дает чи
тателю возможность начинать изучение материала с той темы, которая интере
сует его больше всего, и вообще читать главы второго тома в любом удобном ему
порядке.
В главе
1
рассматривается библиотека потоков данных в
Java,
придающая
современные черты обработке данных благодаря тому, что программисту доста
точно указать, что именно ему требуется, не вдаваясь в подробности, как полу
чить желаемый результат. Такой подход позволяет уделить в библиотеке потоков
данных основное внимание оптимальной эволюционной стратегии, которая дает
особые преимущества при оптимизации параллельных вычислений.
Глава
2
посвящена организации ввода-вывода. В языке
Java
весь ввод-вывод
осуществляется через так называемые потоки ввода-вывоiJа (не путать с потоками
данных, рассматриваемыми в главе
1). Такие
потоки позволяют единообразно об
мениваться данными между различными источниками, включая файлы, сетевые
соединения и блоки памяти. В начале этой главы приводится подробное опи
сание классов чтения и записи в потоки ввода-вывода, упрощающие обработку
данных в Юникоде. Далее в ней рассматривается внутренний механизм сериа
лизации объектов, который делает простым и удобным сохранение и загрузку
объектов. И в завершение главы обсуждаются регулярные выражения, а также
особенности манипулирования файлами и путями к ним. На протяжении всей
Предисловие
этой главы будут представлены долгожданные усовершенствования системы в1ю
да-вывода в последних версиях
Основной темой главы
3
Java.
является
XML.
В ней показывается, каким образом
осуществляется синтаксический анализ ХМL-файлов, формируется ХМL-размет
ка и выполняются ХSL-преобразования. В качестве примера демонстрируется
разметка компоновки Swing-фopмы в формате
ется также прикладной интерфейс
API XPath,
XML.
В этой главе рассматрива
значител1,но упрощающий поиск
мелких подробностей в больших объемах данных формата
XML.
4 рассматривается прикладной интерфейс API для работы в сети.
Java чрезвычайно просто решаются сложные задачи сетевого програм
В главе
В языке
мирования. В этой главе показывается, как устанавливаются сетевые соединения
с серверами, реализуются собственные серверы и организуется связь по сетевому
протоколу НТГР. Здесь также описывается новый НТГР-клиент.
5 посвящена программированию баз данных. Основное внимание в ней
JDBC - прикладному интерфейсу для организации доступа к базам дан
приложений на Java, который позволяет прикладным программам на Java
Глава
уделяется
ных из
устанавливать связь с реляционными базами данных. В этой главе также показы
вается, как писать полезные программы для выполнения рутинных операций с на
стоящими базами данных, применяя только самые основные средства югrерфейса
JDBC.
(Для рассмотрения всех средств интерфейса
JDBC
потребовалась бы отдель
ная книга почти такого же объема, как и эта.) И в завершение главы приводятся
краткие сведения об интерфейсе
JNDI (Java Naming and Directory Interface - ин
Java) и протоколе LDAP (Lightweight Directory
терфейс именования и каталогов
Access Protocol -
упрощенный протокол доступа к каталогам).
Ранее в библиотеках
Java
были предприняты две безуспешные попытки орга
низовать обработку даты и времени. Третья попытка была успешно предпринята
в версии
Java 8.
Поэтому в главе
6 поясняется,
как преодолевать трудности организа
ции календарей и оперирования часовыми поясами, используя новую библиотеку
даты и времени.
В главе
7
обсуждаются вопросы юпернационализации, важность которой,
на наш взгляд, будет со временем только возрастать.
Java
относится к тем немно
гочисленным языкам программирования, где с самого начала предусматривалась
возможность обработки данных в Юникоде, но поддержка интернационализации
в
Java этим не ограничивается. В частности, интернационализация прикладных про
Java позволяет сделать их независимыми не только от платформы, но и от
грамм на
страны применения. В качестве примера в этой главе демонстрируется, как написать
прикладную программу для расчета времени выхода на пенсию с выбором англий
ского, немецкого или китайского языка.
В главе
8
описываются три разные методики обработки исходного кода. Так,
прикладные интерфейсы
API для сценариев и компилятора дают возможность
Java код, написанный на каком-нибудь языке сценариев,
например JavaScript или Groovy, и компилироват1, его в код Java. Аннотации по
зволяют вводить в программу на Java произволшую информацию (иногда еще на
вызывать в программе на
зываемую метаданными). В этой главе показывается, каким образом обработчики
аннотаций собирают аннотации на уровне источника и на уровне файлов классов
и как с помощью аннотаций оказывается воздействие на поведение классов во время
Предисловие
выполнения. Аннотации выгодно использовать тол1,ко вместе с подходящими ин
струментальными средствами, и мы надеемся, что материал этой главы поможет
читателю научиться выбирать именно те средства обработки аннотаций, которые
в наибольшей степени отвечают его потребностям.
В rлаве
сии
мы
9 описывается модульная система на платформе java, внедренная в вер
Java 9 для того, чтобы способствовать нормальной эволюции самой платфор
и базовых библиотек Java. Эrа модульная система обеспечивает инкапсуляцию
пакетов и предоставляет механизм для описания требований к модулям. В этой
главе рассматриваются свойства модулей, на основ 12) count++;
Ниже показано, как аналогичная операция осуществляется с помощью пото
ков данных.
long count = words.stream()
.filter(w -> w.length() > 12)
.count();
В последнем случае не нужно искать в цикле наглядного подтверждения опе
раций фильтрации и подсчета слов. Сами имена методов свидетельствуют о том,
что именно предполагается сделать в коде. Более того, если в цикле во всех под
робностях предписывается порядок выполнения операций, то в потоке данных
операции можно планировать как угодно, при условии, что будет достигнут пра
вилы1ый результат.
Достаточно заменить метод
stream ()
на метод
parallelStream (),чтобы
ор
ганизовать средствами библиотеки потоков данных параллельное выполнение
операций фильтрации и подсчета слов, как показано ниже.
long count = words.parallelStream()
.filter(w -> w.length() > 12)
. count ();
Потоки данных действуют по принципу "что, а не как делать". В рассматрива
емом здес1, примере кода мы описываем, что нужно сделать: получить длинные
слова и подсчитап, их. При этом мы не указываем, в каком порядке или потоке
исполнения это должно произойти. Напротив, в упомянутом выше цикле точ
но указывается порядок организации вычислений, а следовательно, исключается
всякая возможност1, для оптимизации.
На первый взгляд поток данных похож на коллекцию, поскольку он позволяет
преобразовывап, и извлекать данные. Но у потока данных имеются следующие
существенные отличия.
1.1.
1.
От итерации к потоковым операциям
Поток данных не сохраняет свои элементы. Они могуг храниться в основной
коллекции или формироваться по требованию.
2.
Потоковые операции не изменяют их источник. Например, метод
fil ter ()
не удаляет элементы из нового потока данных, но выдает новый поток, в ко
тором они отсутствуют.
3.
Потоковые операции выполняются по требованию, когда это возможно. Это
означает, что они не выполняются до тех пор, пока не потребуется их ре
зультат. Так, если требуется подсчитать только пять дли1шых слов вместо
всех слов, метод
fil ter ()
прекратит фильтрацию после пятого совпаде
ния. Следовательно, потоки данных могуг быть бесконечными!
Вернемся к предыдущему примеру, чтобы рассмотреть его подробнее. Мето
ды
stream () и parallelStream () выдают поток данных для списка слов words.
fil ter () возвращает другой поток данных, содержащий только тесло
длина которых больше 12 букв. И, наконец, метод count () сводит этот поток
А метод
ва,
данных в конечный результат.
Такая последовательность операций весьма характерна для манипулирования
потоками данных. Конвейер операций организуется в следующие три стадии.
1. Создание потока данных.
2.
Указание про.межуточных операций для преобра:ювавия исходного потока
данных в другие потоки
3.
-
возможно, в несколько этапов.
Выполнение оконечной операции для получения результата. Эта операция
принуждает к выполнению по требованию тех операций, которые ей пред
шествуют. А впоследствии поток данных может больше не понадобиться.
В примере кода из листинга
или
parallelStream
().Метод
1.1 поток данных создается
fil ter () преобразует его, а
В следующем разделе будет показано, как создается поток данных. В трех по
следующих разделах рассматриваются потоковые операции преобразования, а в
пяти следующих за ними разделах
-
оконечные операции.
java.util.stream.Stream 8
•
Stream filter(Predicate дерево модели
DOM,
представляющей документ,
а затем вывести его содержимое по месту назначения. Подроб11ости такого под
хода к формированию ХМL-документов обсуждаются в последующих разделах.
З.8.1. ХМL-документы без пространств имен
Чтобы построиП> древовидную структуру
DOM,
11ать сначала пустой документ с помощью метода
DocumentBuilder
Document doc
необходимо сформироиз класса
newDocument ()
следующим образом:
builder.newDocurnent();
=
Затем следует вызвать метод
createElement ()
и;1 класса
Document,
чтобы по
строить элементы документа, как пока:1а1ю ниже.
Element rootElement = doc.createElement(rootName);
Element childElement = doc.createElement(childName);
Далее создаются текстовые у:1лы с помощью метода create'Гext.Node
Text textNode
=
():
doc.createTextNode(textContents);
З.8.2. ХМL-документы с пространствами имен
Если используются пространства имен, то процедура формирования ХМL-до
кумента ока:швается несколько иной. Сначала фабрика построителей докумен
тов устанавливается в режим управления пространствами имен, а затем создает-
01
построитель документов, как показано ниже.
DocumentBuilderFactory factory =
DocurnentBuilderFactory.newinstance();
factory.setNamespaceAware(true);
builder = factory.newDocumentBuilder();
Далее для со:1дания любых узлов вместо метода
метод
createElement ()
вызывается
createElementNS ():
String namespace = "http://www.wЗ.org/2000/svg";
Element rootElement = doc.createElementNS(namespace, "svg");
Если узел имеет уточненное имя с префиксом пространства имен, то любые
требующиеся атрибуты с префиксом
требуется ввести данные формата
SVG
xmlns
создаются автоматически. Так, если
в ХНТМL-документ, для этой цели можно
построить соответствующий элемент аналогично приведенному ниже.
Element svgElement = doc.createElement(namespace, "svg:svg")
Когда этот элемент записывается, он превращается в следующий элемент раз
метки:
Если же требуется установить атрибуты элемента разметки, имена которых
находятся в отдельном пространстве имен, вызывается метод
И3 класса
З.8.З. Запись ХМL-документов
Как ни странно, записать дерево модели
DOM
в поток вывода не так-то просто.
Для этой цели проще всего воспользоваться прикладным интерфейсом
XSLT
(ExtensiЬ\e
зования
Stylesheet Language Transformations ХМL-документов). Более подробно язык XSLT
API
языка
расширяемый язык преобра
рассматривается в последнем
разделе этой главы, а до тех пор допустим, что приведенный ниже код каким-то
волшебным образом позволяет получить даш1ые, выводимые в формате
XML.
Над документом выполняется холостое преобразование, а результат записы
вается в поток вывода. Чтобы включить узел
дует также указать идентификаторы
11
построить
объект холостого преобразования:
t =
Traпsformer
DOCTYPE в выводимые данные, сле
SYSTEM и PUBLIC в качестве свойств вывода.
TraпsformerFactory.пewiпstaпce()
.пewTraпsformer();
11 установить свойства вывода, чтобы получить узел DOCTYPE:
t.setOutputProperty(OutputKeys.DOCTYPE SYSTEM,
systemidentif ier) ;
t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
puЫicideпtifier);
!/ установить отступ:
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.setOutputProperty(OutputKeys.METHOD, "xml");
t.setOutputProperty(
"{http: / /xml. apache. org/xsl t) indent-amount", "2") ;
11 выполнить холостое преобразование и вывести
11 результат в файл:
t.transform(new DOMSource(doc),
пеw StreamResult(new FileOutputStream(file) ));
Еще один способ :ыписи ХМL-документов состоит в применении юттерфей
са
LSSerializer. Для получения экземпляра класса, реализующего этот интер
фейс, служит следующий фрагмент кода:
DOMimplementation impl = doc.getlmplemeпtation();
DOMimplemeпtationLS implLS = (DOMimplementationLS)
impl.getFeature("LS", "3.0");
LSSerializer ser = implLS.createLSSerializer();
Если требуется ввести пробелы и разрывы строк в документ, достаточно уста
новить следующий флаг:
ser. getDomConf ig () . set Parameter ( "f ormat-pretty-pr int",
true);
И тогда преобразован документ в символьную строку не составит особого
труда:
String str
=
ser.writeToString(doc);
Если же требуется вывести документ непосредственно в файл, нужно создать
объект типа
LSOutput следующим образом:
LSOutput out = implLS.createLSOutput();
out.setEncoding("UTF-8");
Text createTextNode (String data)
Создает текстовый узел с указанными данными.
org.wЗc.dom.Node
•
1.4
Node appendChild (Node child)
Присоединяет узел к списку его дочерних узлов. Возвращает присоединенный узел.
org.wЗc.dom.Element
•
void
setAttriЬute
•
void
setAttriЬuteNS
1.4
(String
namв,
String value)
(String uri, String
qnamв,
String value)
Устанавливают заданное значение в атрибуте с указанным именем. Если в полностью уточ
ненном имени имеется альтернативный префикс, то параметр
значение
uri
должен принимать пустое
null.
javax.xml.transform.TransformerFactory 1.4
•
static TransformerFactory newlnstance()
•
Transformer newTransformer()
Возвращает экземпляр класса
TransformerFactory.
Возвращает экземпляр класса Transformer, выполняющий тождественное [холостое! пре
образование, не предполагающее никаких действий.
Задает свойство вывода. Перечень этих свойств можно найти по адресу
wЗс.
org/TR/xslt#output.
https: /
/www.
Ниже перечислены наиболее употребительные свойства вы
вода.
doctype-puЬlic
Идентификатор PUВLIC, используемый в объявлении
DOCTYPE
doctype-system
Идентификатор SYSTEМ, используемый в объявлении
DOCTYPE
indent
Принимает значение
"yes" или "no"
method
Принимает значение
"xml", "html", "text"
или специальное строковое
•
значение
void transform(Source from, Result to)
Выполняет преобразование ХМL-документа.
javax.xml.transform.dom.DOMSource 1.4
DOМSource
•
(Node n)
Создает источник данных из заданного узла. Обычно параметр
n
обозначает узел документа.
javax.xml.transform.stream.StreamResult 1.4
•
StreamResult(File f)
•
StreamResult(OutputStream out)
•
StreamResult (Writer out)
•
StreamResult(String systemID)
Создают поток вывода результатов преобразования на основе указанного файла, потока вы
вода, потока записи или системного идентификатора [как правило, это относительный или
абсолютный
3.8.4.
URL].
Запись ХМL-документов средствами
StAX
В предыдущем разделе было показано, как ХМL-документ формируется пу
тем записи дерева модели
DOM.
Но если дерево модели
DOM
нигде больше не
используется, то такой способ оказывается не особенно эффективным. Приклад
ной интерфейс
StAX API
позволяет записывать дерево формируемого документа
непосредственно в формате
XMLStreamWri ter
XML.
Для этого следует создать поток записи типа
из потока вывода типа
OutputStream,
как показано ниже.
XMLOutputFactory factory = XMLOutputFactory.newinstance();
XMLStreamWriter writer = factory.createXMLStreamWriter(out);
Для того чтобы создать и вывести заголовок ХМL-документа, необходимо вы
звать сначала следующий метод:
writer.writeStartDocument()
Глава Э •
XML
а затем метод
writer.writeStartElement(name);
Далее, для вывода атрибуто11 следует вызвать приведенный ниже метод.
writer.writeAttribute(name, value);
Теперь можно вывести дочерние элементы разметки, снова вызвав метод
wri teStartElement
(),или записать символы, вызвав следующий метод:
writer.writeCharacters(text);
После записи всех дочерних узлов следует вызвать приведенный ниже метод,
который закроет текущий элемент разметки.
writer.writeEndElementll;
Чтобы :~аписать элемент ра:1метки без дочерних элементов (например, эле
мент
InputStream stream = source.getByteStream();
var in = new BufferedReader(
new InputStreamReader(stream));
String rootElement = "staff";
var atts = new Attributeslmpl();
if (handler
null)
throw new SAXException 1"No content handler");
handler.startDocument();
handler.startElement("", rootElement,
rootElement, atts);
String line;
while ( ( line
in.readLine()) != null)
{
handler. startElement ( "", "employee",
"employee", atts 1;
var t = new StringTokenizer(line, "1");
handler.startElement("", "name", "name", atts);
String s = t.nextToken();
handler.characters(s.toCharArray(), О,
s. length 1) ) ;
handler. endElement ("", "name", "name");
handler. startElement {"", "salary", "salary",
atts);
s = t.nextToken();
handler.characters(s.toCharArray(), О,
s.length());
handler. endElement (" ", "salary", "salary");
Stream.Source (String systemID)
Создают потоковый источник данных из указанного файла, потока ввода, потока чтения или
системного идентификатора !обычно это относительный или абсолютный
URLJ.
З. 9. Преобразование ХМL-документов языковыми средствами
XSLT
javax.xml.transform.sax.SAXSource 1.4
•
SAXSource (XМLReader reader,
InputSource source)
Создает SАХ-источник, получающий вводимые данные из указанного источника, используя
заданный поток чтения для синтаксического анализа данных.
org. xml. sax. XМLReader 1 . 4
•
void
setContentнandler(ContentHandler
handler)
Устанавливает обработчик, который уведомляется о событиях, наступающих при синтаксиче
ском анализе вводимых данных.
•
void parse (InputSource source)
Анализирует данные, вводимые из указанного источника, и передает обработчику содержи
мого события, наступающие при синтаксическом анализе этих данных.
javax.xml.transform.dom.DOМResult
•
DOМResul t
1.4
(Node n)
Создает источник данных из заданного узла. Обычно в качестве параметра
n
указывается
узел документа.
org.xml.sax.helpers.Attributesimpl 1.4
•
void addAttribute(String uri,
type, String value)
String lname,
Вводит атрибут в коллекцию атрибутов. В качестве параметра
имя без префикса, а в качестве параметра
метр
type
"NМТОКЕN", "NМТОКЕNS",
"ENTITY",
String
lname указывается локальное
уточненное имя с префиксом. Пара
принимает одно из следующих строковых значений:
"IDREFS",
•
qname -
String qname,
"CDATA", "ID", "IDREF",
"NOTATION".
"ENТITIES" ИЛИ
void clear ()
Удаляет все атрибуты из данной коллекции.
Этим примером завершается обсуждение особенностей поддержки
XML в би
Java. Теперь у вас должно сложиться ясное представление о возможно
XML, включая автоматизированный синтаксический анализ и проверку до
блиотеке
стях
стоверности, а также эффективный механизм преобразования ХМL-документов.
Естественно, что всю эту технологию вам удастся поставить себе на службу лишь
в том случае, если вы тщательно разработаете свои форматы
ваши форматы
XML
XML.
Для этого
должны удовлетворять всем насущным производственным
Глава
3 • XML
потребностям, сохранят~, устойчиность с течением времени, а наши деловые
партнеры быть готовыми принимать от вас ХМL-документы. Решение всех этих
вопросов может оказат1,ся гораздо сложнее, чем умелое обращение с синтакси
ческими анализаторами, определениями ОТО или преобра:юваниями, ныполня
емыми средствами
XSLT.
В следующей главе будут обсуждаться вопросы сетевого программирования
на платформе
Java.
Сначала мы рассмотрим основные положения о сетевых со
кетах, а затем перейдем к высокоуровневым протоколам для организации элек
тронной почты и Всемирной паутины.
ГЛАВА
Работа в сети
В этой главе ...
~
Подключение к серверу
~
Реализация серверов
~
Получение данных из Интернета
~
НТТР-клиент
~
Отправка электронной почты
Эта глава начинается с описания основных понятий для работы в сети, а за
тем в ней рассматриваются примеры написания программ на
Java,
позволяющих
устанавливать соединения с серверами. Из нее вы узнаете, как осуществляется
реализация сетевых клиентов и серверов. А завершается глава рассмотрением
вопросов передачи почтовых сообщений из программы на
Java
и сбора данных
с веб-сервера.
4.1.
Подключение к серверу
В последующих разделах сначала рассматривается подключение к серверу
вручную с помощью утилиты
программы на
4.1.1.
Применение утилиты
Утилита
telnet,
а затем автоматическое подключение из
Java.
telnet
telnet
служит отличным инструментальным средством для отлад
ки сетевых программ. Она должна запускаться из командной строки по команде
telnet.
Глава
4 •
Работа в сети
НА ЗАМЕТКУ! В
Windows
утилиту
telnet
необходимо активизировать. С этой целью открой
те панель управления, перейдите в раздел Программы , щелкните на ссыл ке Добавление
или удалениекомпонентов
Windows и установите флажок Клиент Telnet. Следует так
Windows блокирует не которы е сетевые порты , которые будут
же иметь в виду, что брандмауэр
использоваться в примерах программ из этой главы . Чтобы разблокировать эти порты, вы
должны обладать полномочиями администратора.
Утилитой
te l ne t
можно пользоваться не только для соединения с удаленным
компьютером. С ее помощью можно также юаимодействовать с ра :ыичными се
тевыми службами. Ниже приводится один из примеров необычного исполь:юва
ния этой утилиты. Для этого пведите в командной строке следующую команду:
telnet time-a.nist.gov 13
На рис.
4.1
приведен пример ответной реакции сервера, которая в режиме ко
мандной строки будет иметь следующий вид:
54276 07-06-25 21: 37: 31 50
О
О
659.
О
UTC (NIST ) *
-$ telnet time-a .nist .gov 13
Trying 129 .6.15 .28 . . .
Connected to time -a.nist .gov.
Escape character is •Л ]'.
57488 16-04-10 04 :23:00 50 0 0 610 .5 UTC(NIST) *
Connection closed Ьу foreign host.
-$
I
Рис.
4.1. Результат,
11олучаемый из службы учета времени дня
Что же в действительности произошло? Утилита
te lnet
подключилас1, к сер
веру службы учета времени дня, который работает на большинстве компьюте
ров под управлением операционной системы
UNIX.
Указанный в этом приме
ре сервер находится в Национальном институте стандартов и технологий США
(National lnstitute of Standards and Technology).
Его системное время синхрони
зировано с цезиевыми атомными часами. (Безусловно, полученное значение те
кущего времени будет не совсем точным из-за задержек, связанных с передачей
данных по сети.) По принятым правилам сервер службы времени всегда связан
с портом
13.
4.1.
НА ЗАМЕТКУ! В сетевой терминологии порт
-
Подключение к серверу
это не какое-то конкретное физическое устрой
ство, а абстрактное понятие, упрощающее представление о соединении сервера с клиентом
[рис. 4 2).
Сетевой
пакет
/
1111 [
-_____.,__,.,___
132.163.4.103
13
data
Клиент
Рис.
4.2. Схема
соединения клиента с сервером через конкретный порт
Программное обеспечение сервера постоянно работает на удаленном компью
тере и ожидает поступления сетевого трафика через порт
13.
При получении
операционной системой на удаленном компьютере сетевого пакета с запросом
на подключение к порту
13
на сервере активизируется соответствующий процесс
и устанавливается соединение. Такое соединение может быть прервано одним из
его участников.
Когда сеанс связи с сервером через порт
13 начинается по команде t e ln e t
t ime - a . ni st . gov, сетевое программное обеспечение преобразует
"time-a. n is t. gov" в IР-адрес 129 . 6 .15. 28 . Затем оно посылает по это
с параметром
строку
му адресу запрос на соединение с удаленным компьютером через порт
13.
После
установления соединения программа на удаленном компьютере передает обрат
но строку с данными, а затем разрывает соединение. Разумеется, клиенты и сер
веры могут вести и более сложные диалоги до разрыва соединения .
Проведем еще один, более интересный эксперимент. С этой целью выполните
следующие действия.
1.
Введите в режиме командной строки команду
telnet horstmann.com 80
2.
Затем аккуратно и точно введите следующие строки, дважды нажав клавишу
в
GET /
конце:
НТТР/1.1
Host: horstmann.com
пустая
На рис.
стро ка
4.3
показана ответная реакция сервера в окне утилиты
имеет уже знакомый вам вид страницы текста в формате
HTML,
te l n et .
Она
а именно на
чальной страницы веб-сайта Кея Хорстманна. Именно так обычный веб-браузер
Глава
4 •
Работа в сети
получает искомые неб-страницы. Для запроса веб-страниц на сервере 011 приме
няет сетевой протокол НТТР. Разумеется, браузер отображает данные и 11ам1юго
более удобном для чтения ниде, 'lем формат
HTML.
[
tetnet horstmann.com 80
67 . 218 . 118 . 65 .. .
Connected to horstmann.com.
Escape character is •Л J •.
GЕТ / НТТР/1.1
Host : hoгstmann.com
ключения к веб - серверу, на котором под одним и тем же IР-адресом размещаются разные
домены. Ее можн о не указывать, если на веб-сервере размещается единственный домен .
4.1 .2. Подключение
к серверу из программы на
Java
В перном примере сетеной программы, ис х од ный код которой приведен
в листинге
t el net.
емые
11
4.1,
выполняются те же действия, 'ITO и при испол1,:юнании утилиты
Она устаt~анливает соединение с сервером чере:1 порт и 11ы1юд11т пол уча
ответ данные.
Листинг4.1. И с ходный код из фа йла
1
2
3
4
5
6
7
8
9
10
11
12
13
14
socket/SocketTest . java
package socket ;
impo r t
i mport
import
impo rt
.
. o. * ;
Java.i
java . net.* ;
java. n io . c harset. *;
java.uti l. *;
/* *
* В этой п ро гра мме устан а вли вае т ся
* с атомными час ами в г. Боулдере ,
* выв одит с я в ремя, п е р е да в аемое и з
* @ve r s i on 1 .22 20 18-03- 17
* @aut hor Сау Horstmann
*/
try (var s = new Socket("time-a.nist.gov", 13);
var in = new Scanner(s.getinputStream(),
StandardCharsets.UTF 8))
23
24
25
26
27
28
29
while (in.hasNextLine())
{
String line = in.nextLine();
System.out.println(line);
30
31
В данной программе наибольший интерес представляют следующие две стро
ки кода:
Socket s = new Socket("time-a.nist.gov", 13);
Scanner in = new Scanner(s.getinputStream(), "UTF-8"))
В первой строке кода открывается сокет. Сокст
-
это абстрактное пою1-
тие, обозначающее возможность для программ устанавливать соединения
для обмена данными по сети. Конструктору объекта сокета передается адрес
удаленного сервера и номер порта. Если установит~, соединение не удает
ся, генерируется исключение типа
UnknownHost.Exception,
вении каких-нибудь других затруднений
-
а при 1юзникно
исключе11ие типа
IOException.
UnknownHostException является подклассом, производным от класса
IOException, поэтому в данном простом примере обрабатывается только ис
Класс
ключение из суперкласса.
После открытия сокета метод
возвращает объект типа
getinputStream () из класса j ava. net. Socket
InputStream, который можно испол1,зовап, как любой
другой поток ввода. Получив поток ввода, рассматри11аемая здесь программа
приступает к выводу каждой введенной символьной строки в стандартный поток
вывода. Этот процесс продолжается до тех пор, пока не завершится поток ввода
или не разорвется соединение с сервером.
Данная программа может юаимодействовать тол1,ко с очень простыми серве
рами, например со службой учета текущего времени. В более сложных случаях
клиент посылает серверу запрос на получение данных, а сервер может поддер
живать установленное соединение в течение некоторого времени после отправки
ответа на запрос. Примеры реализации подобного поведения представлены да
лее в этой главе.
Класс
Socket
очень удобен для работы в сети, поскол1,ку он скрывает все
сложности и подробности установления сетевого соединения и передачи данных
по сети, реализуемые средствами библиотеки
Java.
Пакет
j ava. net,
по существу,
предоставляет тот же самый программный интерфейс, который используется
для работы с файлами.
Глава
4 •
Работа в сети
НА ЗАМЕТКУ! Здесь рассматривается только сетевой протокол ТСР ITгaпsmiss1on Сопtгоl
Pгotocol - протокол управления передачей). На платформе Java поддерживается также про
токол UDP IUseг Datagгam Pгotocol - протокол пользовательских дейтаграмм), который мо
жет служить для отправки пакетов !называемых иначе дейтаграммами) с гораздо меньшими
издержками, чем по протоколу ТСР. Недостаток такого способа обмена данными по сети за
ключается в том, что пакеты необязательно доставлять получателю в последовательном по
рядке, и они вообще могут быть потеряны. Получатель сам должен позаботиться о том, чтобы
пакеты были организованы в определенном порядке, а кроме того, он должен сам запраши
вать повторно передачу отсутствующих пакетов. Протокол UDP хорошо подходит для тех при
ложений, которые могут обходиться без отсутствующих пакетов, например, для организации
аудио- и видеопотоков или продолжительных измерений.
java.net.Socket 1.0
•
Socket(String bost, int port)
•
InputStream getinputStream()
•
OutputStream getOutputStream()
Создает сокет для соединения с указанным хостом или портом.
Получают поток ввода для чтения данных из сокета или поток вывода для записи данных
в сокет.
4.1.З. Время ожидания дпя сонетов
Чтение данных из сокета продолжается до тех пор, пока данные доступны. Если
хост (т.е. сетевой узел) недоступен, прикладная программа будет ожидать очень
долго, и все будет зависеть от того, когда операционная система, под управлением
которой работает компьютер, определит момент завершения времени ожидания.
Для конкретной прикладной программы можно самостоятельно определить
наиболее подходящую величину времени ожидания для сокета, а затем вызвал,
метод
setSoTimeout
(),чтобы установить эту величину в миллисекундах. В при
веденном ниже фрагменте кода показано, как это делается.
var s = new Socket( . . . );
11 истечение времени ожидания
s.setSoTimeout(lOOOO);
через
10
секунд:
Если величина времени ожидания была задана для сокета, то при выполне
нии всех последующих операций чтения и записи данных будет генерироваться
исключение типа
SocketTimeoutException
по истечении времени ожидания
до фактического завершения текущей операции. Но это исключение можно пе
рехватить, чтобы отреагировать на данное событие надлежащим образом, как
показано ниже.
try
{
InputStream in = s.getinputStream();
11 читать данные из потока ввода in
Что касается времени ожидания для сокетов, то остается еще одно затрудне
ние, которое придется каким-то образом разрешить. Так, приведенный ниже
конструктор может установить блокировку в течение неопределенного перио
да времени до тех пор, пока не будет установлено первоначальное соединение
с хостом.
Socket{String host, int port)
Это затруднение можно преодолеть, если сначала создать несоединяемый со
кет, а затем установить соединение с ним, задав время ожидания:
Socket s = new Socket();
s.connect(new InetSocketAddress(host, port), timeout);
Если же пользователям требуется предоставить возможность прерывать сое
динение с сокетом в любой момент, то далее, в разделе
4.2.4
поясняется, как этого
добиться.
java.net.Socket 1.0
•
Socket () 1 . 1
•
void connect (SocketAddress address) 1. 4
Создает сокет, который еще не соединен в данный момент времени.
Соединяет данный сокет по указанному адресу.
•
void connect (SocketAddress address, int
timeoutinМil.l.iseconds)
1. 4
Соединяет данный сокет по указанному адресу или осуществляет возврат, если заданный
промежуток времени истек.
•
void setSoTimeout (int
timвoutinМil.l.iseconds)
1.1
Задает время ожидания для чтения запросов в данном сокете. По истечении времени ожида
ния возникает исключение типа
InterruptedIOException.
boolean isConnected () 1 . 4
Возвращает логическое значение
•
true,
если установлено соединение с сокетом.
true,
если разорвано соединение с сокетом.
boolean isClosed () 1 . 4
Возвращает логическое значение
4.1.4.
Межсетевые адреса
Как правило, нет особой нужды беспокоиться о межсетевых адресах в Интер
нете
-
числовых адресах хостов, состоящих из четырех байтов (или из шестнад
цати байтов
-
по протоколу IPvб), как, например,
12 9. 6. 15. 2 8.
Но если требу
ется выполнить взаимное преобразование имен хостов и межсетевых адресов, то
для этой цели можно воспользоваться классом
В пакете
j ava. net
InetAddress.
поддерживаются межсетевые адреса по протоколу IPvб,
при условии, что их поддержка обеспечивается и со стороны операционной
Глава
4 •
Работа в сети
системы хоста. В частности, статический метод
getByName () но:шращает объект
InetAddress для хоста. Например, н следующей строке кода возвращает
ся объект типа InetAddress, инкапсулирующий последовательность из четырех
типа
байтов
129.6.15.28:
InetAddress address = InetAddress.getByName("time-a.nist.gov");
Чтобы
получить
байты
межсетевого адреса, достаточно
вызвать
метод
getAddress () следующим образом:
byte[] addressBytes = address.getAddress();
Имена некоторых хостов с большим объемом трафика соответствуют нескол1>
ким межсетевым адресам, что объясняется попыткой сбалансировать нагрузку.
Так, на момент написания данной книги имя хоста
google. сот соответствовало
двенадцати различным сетевым адресам. Один из них выбирается случайным об
разом во время доступа к хосту. Получить межсетевые адреса всех хостов можно,
вызвав метод
getAllByName ():
InetAddress[] addresses = InetAddress.getAllByName(host);
Наконец, иногда требуется адрес локального хоста. Если вы просто запро
сите адрес локального хоста, указав 1оса1 ho s t, то неизмешю получите в ответ
локальный петлевой адрес
127.
О. О.
1,
которым другие не смогут воспользо
ваться для подключения к вашему компьютеру. Вместо этого вы:ювите метод
getLocalHost
(),чтобы получить адрес вашего локального хоста, как показано
ниже.
InetAddress address = InetAddress.getLocalHost();
В листинге
4.2
приведен пример простой программы, выводящей межсетевой
адрес локального хоста, если не указать дополнительные параметры в командной
строке, или же все межсетевые адреса другого хоста, если указать имя хоста в ко
7
* класса InetAddress. В качестве аргумента в командной
8
* строке следует указать имя хоста или запустить
9
* программу без аргументов, чтобы получить в ответ
10 * адрес локального хоста
11 * @version 1.02 2012-06-05
12 * @author Сау Horstmann
13 */
14 puЫic class InetAddressTest
15 {
Возвращает адрес хоста в виде символьной строки с десятичными числами, разделенными
точками, например
String
•
"132 .163. 4 .102".
getнostName
()
Возвращает имя хоста.
4.2.
Реализация серверов
В предыдущем разделе были рассмотрены особенности реализации элемен
тарного сетевого клиента, способного получать данные из сети вообще и Интер
нета в частности. Теперь перейдем к обсуждению реализации простого сервера,
способного посылать данные клиентам.
4.2.1.
Сокеты сервера
После запуска серверная программа переходит в режим ожидания от клиен
тов подключения к портам сервера. Для рассматриваемого здесь примера выбран
Глава
номер порта
4 •
Работа в сети
818 9,
который не используется ни одним из стандартных устройств.
В следующей строке кода создается сервер с контролируемым портом
818 9:
var s = new ServerSocket(8189);
В приведенной ниже строке кода серверной программе предписывается ожи
дать подключения клиентов к заданному порту.
Socket incorning
=
s.accept();
Как только какой-нибудь клиент подключится к данному порту, отпра
вив по сети запрос на сервер, метод
accept ()
возвратит объект типа
Socket,
представляющий установленное соединение. Этот объект можно использовать
для чтения и записи данных в потоки ввода-вывода, как показано в приведенном
ниже фрагменте кода.
InputStrearn inStrearn = incorning.getinputStrearn();
OutputStrearn outStrearn = incorning.getOutputStrearn();
Все данные, направляемые в поток вывода серверной программы, поступают
в поток ввода клиентской программы. А все данные, направляемые в поток выво
да из клиентской программы, поступают в поток ввода серверной программы. Во
всех примерах, приведенных в этой главе, обмен текстовыми данными осущест
вляется через сокеты. Поэтому соответствующие потоки ввода-вывода через сокет
преобразуются в потоки сканирования (типа
Scanner)
и записи (типа
Wri ter)
следующим образом:
var in = new Scanner(inStrearn, "UTF-8");
var out = new PrintWriter(new OutputStrearnWriter(
outStrearn, "UTF-8"),
true /*автоматическая очистка*/);
Допустим, клиентская программа посылает следующее приветствие:
out.println("Hello! Enter
ВУЕ
to exit.");
Если для подключения к серверной программе через порт
утилита
telnet,
818 9
используется
это приветствие отображается на экране терминала.
В рассматриваемой здес1, простой серверной программе вводимые данные,
отправленные клиентской программой, считываются построчно и посылаются
обратно клиентской программе в режиме эхопередачи, как показано в приведен
ном ниже фрагменте кода. Этим наглядно демонстрируется получение данных
от клиентской программы. Настоящая серверная программа должна обработать
полученные данные и выдать соответствующий ответ.
String line = in.nextLine();
out.println("Echo: " + line);
if (line.trirn() .equals("BYE")) done = true;
По завершении сеанса связи открытый сокет закрывается следующим образом:
incorning.close();
Вот, собственно, и все, что делает данная программа. Любая серверная про
грамма, например, веб-сервер, работающий по протоколу НТГР, выполняет ана
логичный цикл следующих действий.
4.2.
1.
Реализация серверов
Получение из потока ввода входящих данных запроса на конкретную ин-
формацию от клие1пской программы.
2.
Расшифровка клиентского запроса.
3.
Сбор информации, запрашиваемой клиентом.
4.
Передача обнаруженной информации клиентской программе через поток
вывода исходящих данных.
В листинге
4.3
приведен весь исходный код описанного выше примера сервер
8 /**
9
* В этой программе реализуется простой сервер,
10 * прослушивающий порт 8189 и посьmающий обратно
11 * клиенту все полученные от него данные
12 * client input.
13 * @version 1.22 2018-03-17
14 * @author Сау Horstmann
15 */
16 puЫic class EchoServer
17 {
puЫic static void main(String[] args)
18
19
throws IOException
20
//установить сокет на стороне сервера
21
22
try (var s = new ServerSocket(8189))
{
23
24
11 ожидать подключения клиента
25
try (Socket incoming = s.accept())
26
{
27
InputStream inStream
28
incoming.getinputStream();
29
OutputStream outStream =
30
incoming.getOutputStream();
31
try (var in
32
new Scanner(inStream,
StandardCharsets.UTF 8))
33
34
var out
new PrintWriter(
35
new OutputStreamWriter(
36
37
outStream, StandardCharsets.UTF_8),
true /* автоматическая очистка */);
38
39
out.println("Hello! Enter ВУЕ to exit.");
40
41
Глава
4 •
Работа в сети
11 п ере д а т ь обра тн о да нн ые ,
11 п олуче нны е от кл и е н та
var do ne = f a l se ;
whi l e ( !done && i n.has Next Line())
42
43
44
45
46
47
48
49
{
String line = in. nextLi ne() ;
out. pr in t l n( "E cho : " + l i ne ) ;
if (li ne . tr im () .equa l s ( " BY E" ))
dопе
true ;
50
51
52
53
54
55
Для проверки работоспособности данной серверной программы ее нужно
скомпилировал, и запустить . Затем необходимо подключиться с помощью ути
литы
tel net
рез порт
к локальному серверу
818 9.
localhost
(или по IР-адресу
1 27 . О
. О
. 1)
че
Если ваш компьютер непосредстве11110 подключен к Интернету,
любой пол1, :юватель может получить доступ к данной серверной программе,
если ему и з вестен IР-адрес и номер порта . При подключении через этот порт
будет получено следующее сообщение (рис.
4.4):
Hello 1 Enter ВУЕ to exit. 1
~1.
fd• 111ow :i>rmmai
"'Ь•
1:1о 1р
-$ te1net 1oca1host 8189
Trying 127.0 .0 .1 . . .
Connected to 1oca1host .
Escape character is • л ] ' .
Не11о! Enter ВУЕ to exit .
НеНо SaHor!
Echo : Не11о Sai1or !
ВУЕ
Echo : ВУЕ
Connection c1osed
-$
Рис.
I
Ьу
4.1.. Сеанс свя з и
foreign host .
с сервером , 11ередающим обратно данные, нолученные
от клиента
Введите любую фра зу и понаблюдайте за тем, как она будет получена обрат1ю в том же самом виде . Для отключения от сервера введите ВУЕ (все символы
в верхнем регистре) . В итоге :sавершится и серверная программа .
1
Пр и вет 1
Введ и те ВУЕ
( П о ка) ,
чтобы выйти из п ро г ра ммы.
4.2.
Реализация серверов
java.net.ServerSocket 1.0
•
ServerSocket (int port)
•
Socket accept ()
Создает сокет на стороне сервера, контролирующего указанный порт.
Ожидает соединения. Этот метод блокирует lт.е. переводит в режим ожидания) текущий поток
до тех пор, пока не будет установлено соединение. Возвращает объект типа
Socket,
через
который программа может взаимодействовать с подключаемым клиентом.
void close ()
Закрывает сокет на стороне сервера.
4.2.2.
Обслуживание многих клиентов
В предыдущем простом примере серверной программы не предусмотрена
возмож1юсть одновременного
подключения сра:~у нескольких клиентских
про
грамм. Обычно серверная программа работает на компьютере сервера, а кли
ентские программы могут одновременно подключаться к ней через Интернет из
любой точки мира. Если на сервере не предусмотрена обработка одновременных
запросов от многих клиентов, один из клиентов может монополизировать доступ
к серверной программе в течение длительного времени. Во избежание подобных
ситуаций следует прибегнуть к помощи потоков исполнения.
Всякий раз, когда серверная программа устанавливает новое сокетное соеди
accept ()
нение, т.е. в результате вызова метода
возвращается сокет, запускается
новый поток исполнения для подключения данного клиента к серверу. После это
го происходит возврат в основную программу, которая переходит в режим ожи
дания следующего соединения. Для того чтобы все это произошло, в серверной
программе следует организовать приведенный ниже основной цикл.
while (true)
{
Socket incoming = s.accept();
var r = new ThreadedEchoHandler(incoming);
var t
new Thread(r);
t.start();
Класс
run ()
ThreadedEchoHandler
реализует интерфейс RunnaЬle и в своем методе
поддерживает юаимодействие с клиентской программой:
Когда 11011ый поток испол11е11ия запускается при каждом соединении , 11еско;11,
ко клиентских программ могут одновременно подключаться к серверу. Это 11е
трудно проверить, выполнив следующие деi1ст1н~я.
1.
Скомпилиру йте и запустите на выполнение серверную программу, исход
ный код которой приведен в листи11ге
4.4.
2.
Откройте несколько окон утилиты
3.
Переходя из одного окна в дру гое, введите команды. В ито ге каждое отдель
ное окно утилиты
te lne t
t elnet
(рис.
4.5).
будет юаимодействовать с серверной програм
мой не:~ависимо от других окон.
4.
Чтобы ра:юрвать соединение и :~акрыть окно утилиты
комбинацию клавиш
!81T• r•ninal
fllt fdit ~tw i-rm1n.i
te l n e t ,
_ о х
Т.Ьs
l::::ttlp
г
-/books/ с j 8/code/v2ch03/ThreadedEchoSe rve r $ java ThreadedEchoSe rver
Spawning 1
Spawning 2
!• IТe1111l11'1I
f:ilt Edit
~.w
1
_ох
F.trmtn.t
ТаЬ.s
нажмите
.
tjelp
-$ tetnet tocathoot 8189
Try. • т.;.~;;,"г
,.
fil• fdn: ~•w Jtrminal т.ь.s 1::::telp
Esc;
Н t - $ tet net tocathoot 8189
н:t Trying 127 . 0.0 . 1" .
Echc Connected to tocathost .
. Escape character is
~1~ НеНо! Enter 8УЕ to exit.
~~с
8 НеНо Saitor !
Echc Echo: Не Но Saitor !
Con How а re you 1
-$ 1 Echo : How are you7
-
'1 1 Х
Con
1
"' ]
'
f
1
.
ВУЕ
Echo: ВУЕ
Connection ctosed
-$ о
1
1
Ьу
foreign host .
1
'
о:=
1
=
Рис.
L..5. Сеанс олн овре м еююй
с 11ю11 нес кольки х кли ентов с се рверо м
НА ЗАМЕТКУ! В рассматриваемой здесь программе для каждого соединения п орождается от
дельный поток и с полнения . Такой прием не вполне подходит для высокопроизв одительного
серв е ра. Более эффективной работы сервера можно до биться, используя средства из паке
та
java. nio. Дополнительные сведения по данному в о пр о с у м ож но получить, обратившись
https : //www _ibm. com/developerworks/java/liЬrary/j-javaio/.
* @author Сау Horstmann
* @version 1.23 2018-03-17
*/
puЫic class ThreadedEchoServer
{
puЫic static void main(String(] args )
{
try (var s = new ServerSocket(8189))
{
int i = 1;
while (true)
24
Socket incoming = s.accept();
System.out.println("Spawning " + i);
RunnaЫe r = new ThreadedEchoHandler(incoming);
var t = new Thread(r);
t.start();
i ++;
25
26
27
28
29
30
31
32
33
catch (IOException
е)
34
35
36
e.printStackTrace();
37
38
39
40 /**
41 * Этот класс обрабатывает данные, получаемые
42 * от клиента через одно сокетное соединение
43 */
44 class ThreadedEchoHandler implements RunnaЫe
45
46
{
try (InputStream inStream =
incoming.getinputStream();
OutputStream outStream =
incoming.getOutputStream();
var in = new Scanner(inStream,
StandardCharsets. UTF 8);
var out = new PrintWriter(
пеw OutputStreamWriter(outStream,
StandardCharsets.UTF 8),
true /* autoFlush */))
out.println("Hello! Enter
БУЕ
to exit.");
// передать обратно данные, полученные
var done = false;
while (!done && in.hasNextLine())
{
String line = in.nextLine();
out.printlп("Echo: " + liпe);
if (line.trim() .eq1ыls("BYE"))
done = true;
catch (IOException
{
от
клиента
е)
e.priпtStackTracel);
4.2.З. Полузакрытие
По.лу.~акрытuе обеспечинает 1юзможность прервать передачу данных на одной
стороне сокетноrо соединения, продолжая в то же время прием данных от дру
гой стороны. Рассмотрим типичную ситуацию. Допустим, данные направляются
на сервер, но заранее неи:шестно, какой именно объем данных требуется пере
дать. Если речь идет о фс:~йле, то
ero
закрытие, по существу, о:шачает завершение
передачи данных. Если же :1акрьпь сокет, то соединение с сервером будет немед
лешю ра:юрвано.
Для преодоления подобного затруднения служит полузакрытие. Если закрыть
поток вывода через сокет, то для сервера это будет означать завершение переда
чи данных запроса. При ::пом поток ввода остается открытым, позволяя получить
ответ от сервера. Код, реали:1ующий механизм полузакрытия на стороне клиен
та, приведен ниже.
try (var socket = new Socket(host, port))
{
var in
=
new Scanner(socket.getinputStream(), "UTF-8");
4.2.
Реализация серверов
var writer = new PrintWriter(socket.getOutputStream() );
11 передать данные заnроса
writer.print ( . . . 1;
writer. flush ();
socket.shutdownOutput();
11 теперь сокет полузакрыт
11 принять данные ответа
while (in.hasNextLine() != null)
{
String line = in.nextLine();
Серверная программа просто читает данные из потока ввода до тех пор, пока
не закроется поток вывода на другом конце соединения. Очевидно, что такой
подход применим только для служб однократного действия по сетевым протоко
лам, подобным НТТР, где клиент устанавливает соединение с сервером, передает
запрос, получает ответ, после чего соединение ра::1рывается.
java.net.Socket 1.0
void shutdownOutput () 1. З
•
Устанавливает поток вывода в состояние завершения.
•
void shutdownlnput () 1 . З
•
Ьoolean
•
Ьoolean
Устанавливает поток ввода в состояние завершения.
isOutputShutdown () 1. 4
Возвращает логическое значение
Возвращает логическое значение
4.2.4.
true,
если вывод данных был остановлен.
islnputShutdown () 1. 4
true,
если ввод данных был остановлен.
Прерываемые сокеты
При подключении через сокет текущий поток исполнения блокируется до тех
пор, пока соединение не будет установлено, или же до истечения времени ожи
дания. Аналогично, если пытаться принять данные через сокет, текущий поток
приостановит свое исполнение до успешного завершения операции или до ис
течения времени ожидания. (Для передачи данных время ожидания не устанав
ливается.)
В прикладных программах, работающих в диалоговом режиме, пользовате
лям желательно предоставить возможность прервать слишком затянувшийся
процесс установления соединения через сокет. Но если поток исполнения бло
кирован для нереагирующего сокета, то разблокировать его не удастся, вызвав
метод
interrupt ().
Для прерывания сокетных операций служит класс
ляемый в пакете
образом:
j ava. nio.
Объект типа
SocketChannel, предостав
SocketChannel создается следующим
Глава
4 •
Работа в сети
Soc ketChannel c han ne l = Soc ketChanne l. open(
new Ine t SocketAdd r ess (host, por t) ) ;
У канала отс утстuуют связа1111ые с ним потоки ввода-вы11ода. Вместо это
го в канале предоставляются методы
read ( )
и
wr i t e (),
испо л ь зу ющие объ
екты типа
Buf f e r.
в главе
Эти методы объявм1ются в интерфейсах R eadaЬleBy t e C h a n nel
2.)
(Подробнее о буферах и:1 системы ввода-вывода
NIO
см .
и Wr i ta Ьl e B yt e C hann e l. Если же 11ет желания иметь дело с буферами, мя чте
ния из канала типа
Scan ner.
So cket Chan n e l можно воспользовал,01 объектом типа
Sca n n e r предусмотрен следующи й конструктор
Для этой цели в К11ассе
с параметром типа R eadaЫe By t e C ha nnel:
var i n
=
new Scanne r( c hannel , St andardCha r set s . UTF 8 );
Чтобы превратить канал в поток вывода , применяется стат и ческий метод
Cha n ne l s .ne wOu t pu tSt re arn() :
Out putStream ou tStream
=
Channe l s.newOutput St r eam(channe l ) ;
Вот, собствешю, и все, что нужно сделать мя прерывания сокет~юй операции.
Если же поток исполнения будет прерван в процессе установления соединения,
чте11ия или записи, соответству ющая операция завершится генерированием ис
ключе11ия .
В примере програ ммы, исходный код кото рой приведен в листише
4.5,
демон
стрируется применение прерываемы х и блокиру ющи х сокето11. Сервер передает
числовые данные, имитируя прерывание их передачи после десятого числа. Если
щелкнуть на любой кнопке, зап устится поток исполнения, устанавливающий
соединение с сервером и выводящий на экран передаваемые данные. В первом
потоке исполнения исполь зуется прерываемый сокет, а во втором
щий . Есл и щел кнуть на кно пке
-
блокиру ю
Cancel (Отмена ) во время выв ода первых десяти
чисел, то прервется и с полнение любого из д вух потоко в .
Если щелкнуть на кнопке
Cancel
после передачи первы х десяти чисел, то пре
рвется испол11ение только первого потока. Блокировка второго потока исполне
ния будет продолжаться до тех пор, пока сервер не разорвет окончател ьно сое
{
try (var s = new ServerSocket(8189))
{
while (true)
{
Socket incoming = s.accept();
RunnaЫe r = new TestServerHandler(incoming);
new Thread(r) .start();
{
try
(
try
{
OutputStream outStream =
incoming.getOutputStream();
var out = new PrintWriter(
new OutputStreamWriter(outStream,
StandardCharsets.UTF 8),
true /* автоматическая очистка */);
while (counter < 1001
{
counter++;
if (counter ame,\l/ ре
(URL)
и унифицированные uдентuфur-;аторы ресурсов
(URI).
В частности,
это лишь синтаксическая конструкция, содержащая ра:ыичные части
символьной строки, обозначающей веб-ресурс.
URL - это особая ра:шовидносп,
URI с исчерпывающими данными о местоположении ресурса.
такие URI, как, например, mail to: cay@hortsmann. сот, которые 11е
идентификатора
Имеются и
являются указателями ресурсов, потому что по ним нелия обнаружить какие-ни
будь данные. Такой
URI
называется унифицированным имене.м ресурса
В классе URI из библиотеки
Java
(URN).
отсутствуют методы доступа к ресурсу по ука
занному идентификатору, поскольку этот класс предна:шачен только для синтак
сического анализа символьной строки, обо:шачающей ресурс. В отличие от него,
класс
URL
позволяет открыть поток ввода-вывода для данного ресурса. Поэто
му в классе
URL
допускается взаимодействие только по тем протоколам и схе
мам, которые поддерживаются в библиотеке
Java, в том числе http:, https:
ftp: - для Интернета, fi le: - для локальной файловой системы, а также
j ar: - для обращения к архивным JАR-файлам.
и
4.3.
Синтаксический анализ
Получение данных иэ Интернета
непростая задача, поскольку идентификаторы
URI -
ресурсов могут иметь сложную структуру. В качестве примера ниже приведены
И здесь квадратные скобки обозначают необязательную часть. Составляющая
полномочия в
URI
серверов имеет приведенную ниже форму, где элемент порт
должен иметь целочисленное значение.
[ сведения_о_пользователе@] хост[: порт]
В документе
RFC 2396,
стандартизирующем идентификаторы
URI,
допускает
ся также механизм указания составляющей полномочия в другом формате на ос
нове данных из реестра. Но он не получил широкого распространения.
Одно из назначений класса
URI
состоит в синтаксическом анализе отдельных
составляющих идентификатора. Они извлекаются с помощью перечисленных
ниже методов.
GetScheme 1)
getSchemeSpecificPart()
getAuthori ty ()
getUserinfo()
getHost 1)
getPort 1)
getPath 1)
getQuery 11
getFragmeпt ()
Другое назначение класса
URI
состоит в обработке абсолютных и относитель
ных идентификаторов. Так, если имеются абсолютный и относительный иденти
Наконец, для доступа к данным указанного ресурса следует вызвать метод
get Inpu tStream (),
предоставляющий поток ввода для чтения данных. (Это
тот же поток ввода, который возвращается методом
са
URL.)
Существует также метод
getContent
openStream ()
из клас
(),но он не такой удобный.
Для обработки содержимого стандартных типов, например текста
(text/
plain) или изображений (image/gif), придется воспользоваться классами
из пакета сот. sun. Кроме того, можно зарегистрировать собственные обра
•
ботчики содержимого, но они в данной книге не рассматриваются .
URLConnection, ошибочно
getinputStream() и getOutpuStream() аналогичны одноименным
Socket. Это не совсем так. Класс URLConnection способен выполнять
НА ЗАМЕТКУ! Некоторые разработчики, пользующиеся классом
считают, что методы
методам из класса
много других функций, в том числе обрабатывать заголовки запросов и ответов. Поэтому ре
комендуется строго придерживаться указанной выше последовательности действий.
Рассмотрим методы из класса
URLConnection
более подробно. В нем имеется
ряд методов, задающих свойства соединения еще до подключения к веб-серверу.
Наиболее важными среди них являются методы
setDoinput ()
и
setDoOutput ().
По умолчанию при соединении предоставляется поток ввода для приема данных
с веб-сервера, но не поток вывода для передачи данных. Чтобы получить поток
вывода (например, с целью разместить данные на неб-сервере), необходимо сде
лать следующий вызов:
connection.setDoOutput(true);
Далее можно установить ряд заголовков запроса и послать их веб-серверу
в составе единого запроса. Ниже приведен пример заголовков запроса.
GET www.server.com/index.html НТТР/1.0
Referer: http://www.somewhere.com/links.html
Proxy-Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Xll; U; Linux i686; en-US; rv:l.8.1.4)
Host: www.server.com
Accept: text/html, image/gif, image/jpeg, image/png, */*
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8
Cookie: orangemilano=l92218887821987
Метод
setifModifiedSince ()
служит для уведомления о том, что требуется
получить только те данные, которые были изменены после определенной даты.
Глава
Работа в сети
4 •
Наконец, с помощью метода
setRequestProperty ()
можно установит~. пару
"имя-значение", имеющую определенный смысл для конкретного протокола.
Формат заголовка запроса по сетевому протоколу НТГР описан в документе
2616.
RFC
Некоторые его параметры не очень хорошо документированы, поэтому
за дополнительными разъяснениями зачастую приходится обращаться к опыту
других программистов. Так, для доступа к защищешюй паролем веб-странице
необходимо выполнит~. следующие действия.
1.
Составить символьную строку из имени пол1,:ювателя, двоеточия и пароля:
String input = username + ":" + password;
2.
Перекодировать полученную в итоге символ~.ную строку по алгоритму ко
дирования
Base64,
как пока:ыно ниже. (Эгот алгоритм преобразует после
довател~.ность байтов в последовательносл, символов в коде АSСП.)
setRequestProperty () с именем свойства "Authorization"
"Basic " + encoding, как показа1ю ниже.
connection.setRequestProperty("Authorization",
"Basic" + encoding);
СОВЕТ. Здесь рассматривается способ обращения к защищенной паролем веб-странице. Для
доступа к защищенному паролем ПР-файлу применяется совершенно другой подход. В этом
случае достаточно сформировать
та. Рассмотрим сначала способ перечисления всех полей :ыголовка. Создатели
рассматриваемого здесь класса посчитали нужным создать собственный способ
перебора полей. Так, в результате вызова приведенного ниже метода получается
11-й ключ заголовка, причем нумерация начинается с единицы! В итоге во:~вра
щается пустое значение
null,
если /1 равно нулю или больше общего количества
полей заголовка.
String key = connection.getHeaderFieldKey(n);
Но
для
определения
количества
полей
не
предусмотрено
никако
го другого метода. Чтобы перебрап, все поля, приходится вызывать метод
getHeaderFieldKey ()
до тех пор, пока не будет получено пустое значение
null.
Аналогично при вызове следующего метода во:шращается значение из 11-го поля:
String value
Метод
=
connection.getHeaderField(n);
getHeaderFields ()
возвращает объект типа Мар с полями :~аголовка:
Map headerFields =
connection.getHeaderFields();
В качестве примера ниже приведен ряд полей :ыголовка из типичного ответа
на запрос по сетевому протоколу НТГР.
4.3.
Получение данных из Интернета
Date: Wed, 27 Aug 2008 00:15:48 GMT
Server: Apache/2.2.2 (Unix)
Last-Modified: Sun, 22 Jun 2008 20:53:38 GMT
Accept-Ranges: bytes
Content-Length: 4813
Connection: close
Content-Type: text/html
НА ЗАМЕТКУ! Получить в ответ строку состояния [например, "НТТР/1.1 200 ОК"] можно,
•
сдел а в вызов
connection. getHeaderField (О)
ил и
headerFields . get (null) .
Для удобства разработки предусмотрены шесть методов, получающих значе
ния из наиболее употребительных полей заголовка и приводящие эти значения
к соответствующим числовым типам по мере необходимости. Все эти удобные
методы перечислены в табл.
4.1.
В методах, возвращающих л~ачения типа
отсчет количества возвращаемых секунд начинается с полуночи
Таблица
4.1. Удобные
1
января
long,
1970
г.
методы, получающие значения полей заголовка из ответа на запрос
String encoding = connection.getContentEncoding();
if (encoding == null) encoding = "UTF-8";
try (var in = new Scanner(
connection.getinputStream(), encoding))
//
//
вывести первые десять
запрашиваемого
строк
содержимого
for (int п = 1; in.hasNextLine() && п О ? args[O]
: "post/post.properties";
var props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get(propsFilename)))
/**
* Сделать НТТР-запрос по команде POST
* @param url Конкретный URL для отправки запроса
* @param nameValuePairs Параметры запроса
* @param userAgent Пользовательский посредник или
пустое значение null, если это
*
var first = true;
for (Map.Entry pair
nameValuePairs.entrySet())
if (first) first = false;
else out.print('&');
String name = pair.getKey() .toString();
String value = pair.getValue().toString();
out.print(name);
out.print('=');
out.print(URLEncoder.encode(value,
StandardCharsets.UTF 8) );
String encoding = connection.getContentEncoding();
if (encoding == null) encoding = "UTF-8";
if (redirects > 0)
{
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection
.НТТР MOVED PERM
1 1 responseCode == HttpURLConnection
. НТТР MOVED ТЕМР
1 1 responseCode == HttpURLConnection
.НТТР SEE OTHER)
String location
var response = new StringBuilder();
try (var in = new Scanner(
connection.getinputStream(), encoding))
while (in.hasNextLine())
{
response.append(in.nextLine() );
response.append("\n");
catch (IOException е)
{
InputStream err = connection.getErrorStream();
if (err == null) throw е;
try (var in = new Scanner(err))
{
response.append(in.nextLine() );
response.append("\n");
return response.toString();
java. net. HttpURLConnection 1. О
•
InputStream getErrorStream()
Возвращает поток ввода, из которого читаются сообщения сервера об ошибках.
java.net.URLEncoder 1.0
•
static String encode (String s,
String encoding)
Возвращает строку s, закодированную в формате
URL
1. 4
с помощью заданной кодировки сим
волов. !Рекомендуется указывать кодировку "UTF-8 11 .I При кодировании в формате URL
символы
'A'-'Z', 'a'-'z', '0'-'9'. '-',' ','.'и,_, оставляются без изменения.
Пробелы заменяются знаками '+',а все остальНЬ1е символы - последовательностями коди
рованных байтов в форме "%ХУ'', где ОхХУ- шестнадцатеричное значение байта.
java.net.URLDecoder 1.2
•
static string decode (String s, String encoding)
Возвращает форму строки s, закодированной в формате
заданной кодировки символов.
URL
1.4
и декодированной с помощью
Глава
4.4.
4 •
Работа в сети
НТТР-клиент
Класс
URLConnection
был разработан еще до того, как сете1юй протокол НТТР
стал универсальным для Интернета. В этом классе поддерживается целый ряд се
тевых протоколов, хотя поддержка протокола НТТР в нем реализована не очень
удобно. Когда было принято решение о поддержке сетевого протокола НТТР/2,
то стало ясно, что лучше предоставить современный клиентский интерфейс вме
сто того, чтобы переделывать уже существующий прикладной интерфейс
Так, в классе
АР! для поддержки сетевого протокола НТТР/2. Начиная с версии
Ht tpClient
API.
предоставляется более удобный прикладной интерфейс
HttpClient
входит в состав пакета
НА ЗАМЕТКУ! В версиях
Java 11
класс
j ava. net. ht tp.
Java 9 и 1О прикладные программы следует запускать на выполне
ние из командной строки со следующим параметром:
--add-modules
jdk.incuЬator.httpclient
В прикладном интерфейсе АР! для НТТР-клие1rrа предоставляется более про
стой механизм подключения к веб-серверу, чем в классе
URLConnection,
где этот
процесс дотошно выполняется в течение целого ряда стадий. НТТР-клиент, реали
зуемый средствами класса
HttpClient,
может выдавать запросы и получать ответы
от неб-сервера. Чтобы получить такой клиент, достаточно сделать следующий вызов:
HttpClient client = HttpClient.newHttpClient()
Если требуется сконфигурировать клиент, то можно восполь:юваться при
кладным интерфейсом
API
его построителя, как показано ниже.
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
Подобным образом получается построитель, вызываются методы для специ
алыюй настройки создаваемого клиента, а затем вызывается метод
build ()
с це
лью завершить весь процесс построения. Это типичный шаблон для построения
неизменяемых объектов.
По такому же шаблону построения составляются запросы. В качестве приме
ра ниже демонстрируется составление НТТР-запроса по команде
навливающими соединение с веб-ресурсом по заданному
URI
URL,
тогда как класс
обеспечивает лишь необходимый синтаксис (схему, хост, порт, путь, запрос,
фрагмент и т.д.).
Для составления НТТР-запроса по команде POST требуется "издатель тела за
проса", где запрашиваемые данные преобразуются в пересылаемые данные. Име
ются издатели тела :1апроса для символьных строк, массивов байтов и файлов.
4.4.
Так, если запрос составляется в формате
НТТР-клиент
JSON, издателю его тела достаточно
JSON, как показано ниже.
.ofString(jsonString))
.build();
К сожалению, в рассматриваемом здесь прикладном интерфейсе АР! не под
держивается требующееся форматирование общеупотребительных типов со
держимого запросов. В приведенном далее примере программы из листинга
4.8
демонстрируется применение издателей тела запроса для обработки данных
формы и выгрузки файлов.
Отправляя запрос на веб-сервер, приходится указывать клиенту порядок об
работки получаемого ответа. Если же требуется отравить лишь тело запроса
в виде символьной строки, это можно сделать с помощью метода
BodyHandlers. ofString ()
HttpResponse.
следующим образом:
HttpResponse response = client.send(request,
HttpResponse.BodyHandlers.ofString() );
Класс
Ht tpResponse
является обобщенным, а параметр его типа обозначает
тип тела запроса. Получить тело запроса в виде символ1,ной строки можно сле
дующим образом:
String bodyString = response.body();
Имеются и другие обработчики тела ответа, получающие ответ в виде массива
байтов или потока ввода. В частности, метод
BodyHandlers. ofFile ( filePa th)
возвращает обработчик, сохраняющий отнет н заданном
файле, а
метод
BodyHandlers. ofFileDownload (directoryPath) сохраняет ответ в заданном
каталоге, используя имя файла из заголовка Content-Disposi tion. Наконец,
обработчик, возвращаемый из метода BodyHandlers. discarding (),просто от
вергает полученный ответ.
Обработка содержимого ответа не обеспечивается как составная част~, рассма
триваемого здесь прикладного интерфейса
мате
JSON,
API.
Так, если ответ получается в фор
для синтаксического анализа его содержимого потребуется отдельная
библиотека, поддерживающая обработку данных формата
В объекте типа
HttpResponse
JSON.
предоставляется также код состояния и заголов
ки ответов:
int status = response.statusCode();
HttpHeaders responseHeaders = response.headers();
Объекты типа
HttpHeaders
можно преобразовать в отображение, как демон
стрируется в приведенной ниже строке кода. В качестве значений в таком отобра
жении служат списки, поскольку в сетевом протоколе НТТР для каждого ключа
допускается несколько значений.
Map headerMap = responseHeaders.map();
Если же требуется значение для конкретного ключа и заранее известно, что
у него не может быть несколько :шачений, следует вызвать метод
firstValue (),
Глава
4 •
Работа в сети
как показано ниже. В ответ получается конкретное значение, а если оно не пре
доставлено
-
пустое необязательное значение.
Optional lastModified =
headerMap.firstValue("Last-Modified");
Огветы можно обрабатывать и асинхронно. Для этого при построении клиен
та предоставляется исполнитель:
как показано ниже. В итоге получается завершаемое будущее дей
ствие типа CompletaЫeFuture, где Т
тела ответа. О том, как применять прикладной интерфейс
будущих действий, см. в главе
12
-
тип обработчика
API
для завершаемых
первого тома настоящего издания.
HttpRequest request = HttpRequest.newBuilder() .uri(uri)
.GET() .build();
client.sendAsync(request,
HttpResponse.BodyHandlers.ofString())
thenAccept(response -> . . . );
СОВЕТ. Чтобы активизировать режим протоколирования для НТТР-клиента типа
достаточно ввести следующую строку кода в файл
net.properties
HttpClient.
JDK:
свойств комплекта
jdk.httpclient.HttpClient.log=all
Вместо параметра all можно указать разделяемый запятыми список параметров headers,
requests, content, errors, ssl, trace и frames, а после них дополнительно - пара
метры :control, :data, :window или :all, но без пробелов.
После этого можно установить уровень протоколирования
httpclient. HttpClient, введя, например,
properties свойств комплекта JDK:
Map data)
var first = true;
var builder = new StringBuilder();
for (Map.Entry entry
data.entrySet())
l f (first) first = false;
else builder.append("&");
builder.append(URLEncoder.encode(
entry.getKey() .toString(),
StandardCharsets.UTF 8));
builder.append("=");
builder.append(URLEncoder.encode(
entry.getValue() .toString(),
StandardCharsets.UTF 8) );
4.5.
j ava . net. h t tp . Ht tpReques t. Builder 11
•
•
•
•
Отправка электронной почты
{окончание}
HttpRequest. Builder GET ()
BttpRequest.Builder DELETE()
HttpRequest.Builder POST(HttpRequest.BodyPuЬlisher bodyPuЬlisher)
HttpRequest.Builder PUТ(HttpRequest.BodyPuЬlisher bodyPuЬlisher)
Устанавливают метод доступа и тело для данного запроса.
java.net.http.HttpResponse 11
•
т
body()
Возвращает тело данного ответа.
•
int statusCode ()
•
HttpHeaders headers ()
Возвращает код состояния для данного ответа.
Возвращает заголовки ответа.
java.net.http.HttpHeaders 11
•
мap
шар()
Возвращает отображение типа мар данных заголовков.
•
Optional firstValue(Strinq name)
Возвращает первое значение по имени, указанному в данных заголовках, если таковое
имеется.
4.5.
Отправка электронной почты
В прошлом для отправки электронной почты досrаточно было написать про
25, который
SMTP (Simple Mail Transport
грамму, усrановливавшую соединение с сетевым сокетом через порт
обычно используется для работы сетевого протокола
Protocol -
просrой протокол передачи почты), описывающего формат электрон
ных сообщений. После подключения к серверу в данной программе нужно было
послать заголовок сообщения, который досrаточно просrо было создать в формате
SMTP,
1.
а затем и тексr сообщения, выполнив перечисленные ниже дейсrвия.
Открыть сокет на своем компьютере, подключенном к Интернету, как по
ка:ыно ниже.
var s = new Socket("mail.yourserver.com", 25);
11 номер порта 25 соответствует протоколу SMTP
var out = new PrintWriter(s.getOutputStream(),"UTF-8");
2.
Направит~, в поток вывода следующие данные:
HELO
хост
МAIL
FROM:
отправителя
адрес
отправителя
Глава
4 •
Работа в сети
RCPT ТО:
DATA
Subject:
адрес
(пустая
строка)
почтовое
(любое
получателя
тема
сообщение
количество
строк)
QUIT
В спецификации сетевого протокола
SMTP
(документ
бы строки завершались последовательностями символов
RFC 821) требуется, что
/r и /n. Первоначально
SМТР-серверы исправно направляли электронную почту от любого адресата. Но
когда навязчивые сообщения наводнили Интернет, большинство этих серверов
было оснащено встроенными проверками и принимали запросы только по тем
IР-адресам, которым они доверяют. Аутентификация обычно происходит через
безопасные сокетные соединения.
стое. Поэтому в этом разделе будет показано, как пользоваться прикладным
интерфейсом
граммы на
су
JavaMail API для отправки сообщений электронной почты из про
Java. С этой целью загрузите данный прикладной интерфейс по адре
https://javaee.github.io/javamail/
и разархивируйте его на жесткий
диск своего компьютера.
Чтобы воспользоваться прикладным интерфейсом
JavaMail
АР!, необходимо
установить некоторые свойства, зависящие от конкретного почтового сервера.
В качестве примера ниже приведены свойства, устанавливаемые для почтового
сервера
GMail.
Они считываются из файла свойств в рассматриваемом здесь при
мере программы из листинга
4.9.
mail.transport.protocol=smtps
mail.smtps.auth=true
mail.smtps.host=smtp.gmail.com
mail.smtps.user=cayhorstmann@gmail.com
Из соображений безопасности пароль не вводится в файл свойств и предла
гается для ввода вручную. После чтения из файла свойств сеанс почтовой связи
устанавливается следующим образом:
Session mailSession = Session.getDefaultinstance(props);
Затем составляется почтовое сообщение с указанием требуемого отправителя,
получателя, темы и текста самого сообщения:
MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(from) );
message.addRecipient(RecipientType.TO,
new InternetAddress(to) 1;
message.setSubject(subject);
message.setText(builder.toStr1ng());
Далее почтовое сообщение отправляется следующим образом:
Transport tr = mailSession.getTransport();
tr.connect(null, password);
tr.sendMessage(message, message.getAllRecipients());
tr.close();
4.5.
Отправка электронной почты
Рассматриваемая здесь программа читает почтовое сообщение из текстового
файла в приведенном ниже формате.
Отправитель
Получатель
Тема
Текст сообщения
(любое количество
строк)
Кроме упомянутого выше прикладного интерфейса
JavaMail API,
для выпол
нения данной программы потребуется архивный JАR-файл каркаса
Activation Framework,
JavaBeans
который можно загрузить по адресу
https: / /www. oracle.
com/technetwork/ j ava/j avase/downloads/ index-13504 6. html#download или из
центрального хранилища Maven Central по адресу https: / /mvnreposi tory. сот/
arti f act/ j avax. acti va tion/ acti va tion. Затем выполните следующую команду:
java -classpath .:javax.mail.jar:activation-1.1.1.jar
path/to/message.txt
На момент написания данной книги почтовый сервер
GMail
не проверял до
стоверность получаемой информации, а следовательно, в почтовом сообщении
можно было указать любого отправителя. (Это обстоятельство следует иметь
в виду при получении от отправителя по адресу
president@whi tehouse. gov
очередного приглашения на официальный прием, организуемый на лужайке пе
ред Белым домом.)
СОВЕТ. Если вам не удастся выяснить причину, по которой соединение с почтовым сервером
не действует, сделайте следующий вызов и проверьте почтовые сообщения:
mailSession.setDebug(true);
Кроме того, обратитесь за полезными советами на веб-страницу
JavaMail API FAQ !Часто за
даваемые вопросы по прикладному программному интерфейсу JavaMail API FAQ), доступную
по адресу
ht tps: / / j avaee. g i thub. io/ j avamail /FAQ.
Листинг 4.9. Исходный код из файла
1
2
3
4
5
6
7
8
9
10
mail/MailTest. java
package mai l;
import
import
import
import
import
import
import
12 * В этой программе демонстрируется применение
13 * прикладного интерфейса JavaMail API для отправки
14 * сообщений по электронной почте
15 * @author Сау Horstmann
16 * @version 1.01 2018-03-17
17 */
18 puЫic class MailTest
19 !
var props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get ("mail", "mail.properties"J))
props.load(in);
List lines = Files.readAllLines(
Paths.get(args[O]), StandardCharsets.UTF 8);
String from = lines.get(O);
String to = lines.get(l);
String subject = lines.get(2);
var builder = new StringBuilder();
for (int i = 3; i < lines.size(); i++)
builder.append(lines.get(i));
builder.append("\n");
Console console = System.console();
var password =
new String(console.readPassword("Password: ") );
Session mailSession =
Session.getDefaultinstance(props);
// mailSession.setDebug(true);
var message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(from));
message.addRecipient(RecipientType.TO,
new InternetAddress(to));
message.setSubject(subject);
message.setText(builder.toString() );
Transport tr = mailSession.getTransport();
try
{
tr.connect(null, password);
tr.sendMessage(message,
message.getAllRecipients() );
f inally
{
tr.close();
В этой главе было показано, как на
Java
пишется исходный код программ
для сетевых клиентов и серверов и как организуется сбор данных с неб-серверов.
В следующей главе речь пойдет о взаимодействии с базами данных. Из нее вы
узнаете, как работать с реляционными базами данных в программах на
пользуя прикладной интерфейс
JDBC
АР!.
Java,
ис
ГЛАВА
Работа с базами данных
В этой главе ...
~ Структура
JDBC
~
Язык
~
Конфигурирование
~
Работа с операторами
~
Выполнение запросов
~
Прокручиваемые и обновляемые результирующие наборы
~
Наборы строк
~
Метаданные
SQL
JDBC
JDBC
~ Транзакции
~
Расширенные типы данных
SQL
~ Управление подключением к базам данных в веб- и корпоративных
приложениях
В
1996
году компания
ного интерфейса
ных
(JDBC).
API
Sun Microsystems
выпустила первую версию приклад
для организации доступа из программ на
Java
к базам дан
Этот прикладной интерфейс позволяет соедишm,ся с базой данных,
запрашивать и обновлять данные с помощью языка структурированных запросов
(Structured Query Language - SQL).
Язык
SQL
фактически стал стандартным сред
ством взаимодействия с реляционными базами данных. С тех пор
из наиболее употребительных прикладных интерфейсов
Прикладной интерфейс
JDBC
JDBC
стал одним
11 библиотеке
Java.
неоднократно обновлялся. На момент написа
ния данной книги самой последней считалась версия
став версии
APJ
JDBC 4.3,
включенная в со
Java 9.
В этой главе рассматриваются принципы, положенные 11 основу прикладного
интерфейса
JDBC.
Из нее вы узнаете (а возможно, лиш1, вспомните) о языке
SQL,
Глава
Работа с базами данных
5 •
который является стандартным средством доступа к реляционным базам данных.
В ней будут также рассмотрены примеры применения интерфейса
JDBC,
демон
стрирующие наиболее распространенные приемы обращения с базами данных
в прикладных программах.
НА ЗАМЕТКУ! Как заявляют в компании Огасlе,
ние
Java Database Connectivity.
JDBC -
это торговая марка, а не сокраще
Она была придумана по аналогии с обозначением
ODBC
стан
дартного прикладного интерфейса для работы с базами данных, который был первоначально
предложен корпорацией
5.1.
Структура
Создатели
Microsoft
и затем внедрен в стандарт
JDBC
Java
с самого начала осознавали потенциальные преимущества дан
ного языка для работы с базами данных. С
ширением стандартной библиотеки
средствами
SQL.
SQL.
1995
года они начали работать над рас
для организации доступа к базам данных
Java
Сначала они попробовали создать такие расширения
Java,
которые
позволили бы осуществлять доступ к произвольной базе данных то.лько средства
ми
Java,
но очень скоро убедились в бесперспективности такого подхода, посколь
ку для доступа к базам данных применялись самые разные протоколы. Кроме того,
поставщики программного обеспечения баз данных были весьма заинтересованы
в разработке на
Java
стандартного сетевого протокола для доступа к базам данных,
но при условии, что за основу будет принят их собственный сетевой протокол.
В конечном счете поставщики баз данных и инструментальных средств для до
ступа к ним сошлись на том, что лучше предоставить прикладной интерфейс АР!
только на
Java
для доступа к базам данных средствами
SQL,
а также диспетчер
драйверов, который позволил бы подключать к базам драйверы независимых про
изводителей. Такой подход позволял поставщикам баз данных создавать собствен
ные драйверы, которые подключались бы с помощью данного диспетчера. Пред
полагалось, что это будет простой механизм регистрации сторонних драйверов.
JDBC основана на вес1,ма
ODBC, разработанного в корпорации Microsoft.
ODBC положен общий принцип: программы, на
Подобная организация прикладного интерфейса
удачной модели интерфейса
В основу интерфейсов
JDBC
и
писанные в соответствии с требованиями прикладного интерфейса АР!, способ
ны взаимодействовать с диспетчером драйверов
JDBC,
который, в свою очередь,
использует подключаемые драйверы для обращения к базе данных. Это означает,
что для работы с базами данных в прикладных программах достаточно пользо
ваться средствами
5.1.1.
JDBC API.
Типы драйверов
Каждый драйвер
•
JDBC
JDBC
Драйвер типа
1.
относится к одному из перечисленных ниже типов.
Преобразует интерфейс
JDBC в ODBC и для взаимодей
ODBC. Один такой драйвер был
названием мост f DBC/ODBC. Но для его
ствия с базой данных использует драйвер
включен в первые версии
Java
под
применения требуется установить и настроить соответствующим образом
драйвер
ODBC.
В первом выпуске
JDBC
этот мост предполагалось исполь:ю
вать тол1,ко для тестирования, а не для применения в рабочих программах.
5.1.
Структура
JDBC
В настоящее время уже имеется достаточное количество более удачных
драйверов, поэтому пользоваться мостом
•
Драйвер типа
2.
Написан частично
JDBC/ODBC не рекомендуется.
на Java и отчасти использует платфор
менно-ориентированный код для взаимодействия с клиентским приклад
ным интерфейсом
API базы данных. Для применения такого драйвера,
Java, на стороне клиента необходимо установить код,
помимо библиотеки
специфический для конкретной платформы.
•
Драйвер типа
ки
Java,
3.
Разрабатывается только на основе клиентской библиоте
в которой используется независимый от базы данных протокол пе
редачи запросов базы данных на сервер. Эrот протокол приводит запросы
базы данных в соответствие с характерным для нее протоколом. Разверты
вание прикладных программ значительно упрощается благодаря тому, что
код, зависящий от конкретной платформы, находится только на сервере.
•
Драйвер типа
на
Java,
4.
Представляет собой библиотеку, написанную только
для приведения запросов
JDBC
в соответствие с протоколом кон
кретной базы данных.
НА ЗАМЕТКУ! Спецификация прикладного интерфейса
JDBC доступна
для загрузки по адресу
https://jcp.org/en/jsr/detail?id=221.
Большинство поставщиков баз данных предоставляют драйверы типа
3 или 4.
Кроме того, целый ряд сторонних производителей специализируется на созда
нии драйверов, которые позволяют добиться более полного соответствия приня
тым стандартам, поддерживают большее количество платформ, обладают более
высокой производительностью или надежностью, чем драйверы, предлагаемые
поставщиками баз данных.
Основные цели прикладного интерфейса
JDBC
можно сформулировать следу
ющим образом.
•
Разработчики пишут программы на
Java, пользуясь
SQL (или его
данных стандартными средствами языка
для доступа к базам
специализированны
ми расширениями), но следуя только соглашениям, принятым в
•
Java.
Поставщики баз данных и инструментальных средств к ним предоставляют
драйверы только низкого уровня. Эrо дает им возможность оптимизиро
вать драйверы под свою конкретную продукцию.
НА ЗАМЕТКУ! На конференции
JavaOne
в мае
1996
года представители компании
Micгosystems указали на ряд следующих причин отказа от модели
•
•
Sun
ODBC.
Трудна в освоении.
Имеет всего лишь несколько команд с большим количеством параметров, тогда как
стиль программирования на
Java
основан на применении большого количества простых
и интуитивно понятных методов.
•
Основана на использовании указателей типа
отсутствующих в
•
void* и других элементов языка С,
Java.
Менее безопасна и более сложна для развертывания. чем решение, получаемое только
на
Java.
Глава
5.1.2.
5 •
Работа с базами данных
Типичные примеры применения
JDBC
Соглас1ю традиционной модели " клиент-сервер" граф11ческ11i1 поль:ювате11ь
ский шперфеiiс (ГПИ) реализуется на стороне клнента, а ба:1а да1111ы х распола
гается на стороне сервера (рис.
5.1 ).
В лом случае драйвер
JDBC
ра : шертывО && sel скалярную функцию,
следует вставить ее стандартное имя и аргументы, как показано ниже. Полныi1
список поддерживаемых имен скалярных функций можно найти в специфика
ции
JDBC.
{fn left(?, 20))
( fn user ( 1 }
Хранимой называется такая процедура, которая выполняется в базе да1111ых
и написана на специальном языке для конкретной базы данных. Для вызова хра
нимой процедуры служит переход
cal1.
Если у процедуры отсутствуют пара
метры, то указывать скобки не нужно. Для фиксации возвращаемого значения
служит :шак равенства. Ниже показано, каким обра:юм вызываются хранимые
процедуры.
{call PROCl (?, ?) }
{call PROC2}
{call ? = РRОСЗ(?)}
Внешнее соединение двух таблиц не требует, чтобы строки из каждой таблицы
совпадали по условию соединения. Например, в приведенном ниже запросе ука
заны книги, для которых столбец PuЬlisher_Id не имеет совпадений в таблице
PuЬlishers, причем пустые значения
NULL
обо:шачают отсутствие совпадений.
SELECT * FROM {oj Books LEFT OUTER JOIN PuЬlishers
ON Books.PuЬlisher Id = PuЫisher.PuЬlisher Id}
-
-
Чтобы включить в запрос и:~дательства бе:~ совпадающих книг, может потре
боваться предложение
и другое
-
RIGHT OUTER JOIN, а чтобы возвратить по запросу и то
FULL OUTER JOIN. Синтаксис переходов требуется
предложение
именнопотому, что не во всех базах данных используется стандартное обозначе
ние внешних соединений.
5.5.
Наконец, знаки
Выполнение запросов
и % имеют специальное назначение в операции LIKE, обо
значая совпадение с одним символом или последовательностью символов. Стан
дартного способа их буквального употребления не существует. Так, для сопо
ставления всех символьных строк, содержащих знак
приведенной ниже конструкцией, где знак
а последовательность символов
!_
_,
можно воспользоваться
определен как символ перехода,
!
буквально обозначает знак подчеркивания .
... WHERE? LIKE % % {escape '' '}
1
5.5.4.
Множественные результаты
По запросу могут быть возвращены множественные результаты. Это может
произойти при выполнении хранимой процедуры или в базах данных, которые
допускают также выполнение многих операторов SELECT в одном запросе. Полу
чить все результирующие наборы можно следующим образом.
1.
Вызвать метод
2.
Получить первый результат или подсчет обновлений.
3.
Повторить вызов метода
execute ()
для выполнения оператора
getMoreResul ts (),
SQL.
чтобы перейти к следую
щему ре:~ультирующему набору.
4.
Завершить процедуру, если больше не остается результирующих набо
ров или подсчетов обновлений.
Методы
execute ()
и
getMoreResul ts ()
возвращают логическое значение
если следующим звеном в цепочке оказывается результирующий набор.
true,
Метод
get.UpdateCount ()
возвращает значение
-1,
если следующим звеном в це
почке не оказывается подсчет обновлений. В следующем цикле осуществляется
последовательный обход всех полученных результатов:
/**
* Удаляет строку из текущей таблицы
*/
puЫic void deleteRow()
{
if (crs == null) return;
new SwingWorker()
{
puЫic Void doinBackground() throws SQLException
{
crs.deleteRow();
crs.acceptChanges(conn);
if (crs.isAfterLast())
if (!crs.last()) crs = null;
return null;
puЫic
void done()
{
dataPanel.showRow(crs);
}.execute();
/**
* Сохраняет
все
внесенные
изменения
*/
puЫic
void saveChanges()
{
if (crs == null) return;
new SwingWorker()
{
puЫic Void doinBackground() throws SQLException
{
dataPanel.setRow(crs);
crs.acceptChanges(conn);
return null;
}.execute();
private void readDatabaseProperties()
throws IOException
props = new Properties();
try (InputStream in = Files.newinputStream(
Paths.get("database.properties") 1)
props.load(in);
String drivers = props.getProperty(
"jdbc.drivers");
if (drivers != null)
System.setProperty("jdbc.drivers", drivers);
Метаданные
Глава
5 •
Работа с базами данных
/**
251
* Получает сведения о подключении к базе данных из
252
253
* свойств, задаваемых в файле database.properties,
254
* и на их основании подключается к базе данных
255
* @return Подключение к базе данных
256
*/
private Connection getConnection()
257
throws SQLException
258
259
String url = props.getProperty("jdbc.url");
260
props.getProperty(
261
String username
"jdbc.username");
262
props.getProperty(
String password
263
"jdbc.password");
2 64
265
return DriverManager.getConnection(url,
266
username, password);
267
268
269
270
271 /**
272 * Панель для отображения содержимого
273 * результирующего набора
*/
274
275 class DataPanel extends JPanel
276 (
private java.util.List fields;
277
276
/**
277
* Конструирует панель для отображения данных
278
* @param rs Результирующий набор, содержимое
279
которого отображается на данной панели
280
*/
281
puЫic DataPanel(RowSet rs) throws SQLException
282
{
283
fields = new ArrayList();
284
setLayout(new GridBagLayout());
285
var gbc = new GridBagConstraints();
286
gbc.gridwidth = 1;
287
gbc.gridheight = 1;
288
289
ResultSetMetaData rsmd = rs.getMetaData();
290
for (int i = 1; i того сш1ска расположена группа к1юпок-перек11ючателей, в котороi1 можно
ныбрап, способ форматирования чисел, денежных сумм или числовых вел11ч1111
н процентах. После выбора ноной регионалыюй настройки или способа форма
тирования чисел число в текстовом поле автоматически переформатируето1.
Просмотрев л11w1, несколько вариантов формат11рованш1 ч11сел
11
денежных
сумм в зависимости от выбранных репю11алы1ых настроек, пол1. :ювате111,, несо
мнешю, составит ясное представление о разнообразии сущесп1ующих форма
тов чисел. В тексто1юм поле можно ввести любое число и щелкнул, на кнопке
Parse,
в резул1.тате чего будет вызва11 метод
parse () ,
выполш1ющий синтаксиче
ский анализ введенной символыюй строки. При удкра
" Par.se erro r"
не. В пропtв1юм случае в текстовом поле появляется сообщение
(Ошибка синтаксического анализа).
имеет доволыю простую структуру. Сначала в конструкторе вы:.1ывается метод
N11mberF'orma t . g etAva ilaЫ e L o ca le s (). Затем для каждого в1ца поддержи
ваемых региональных настроек вы:~ывается метод getD isplayName () ,а во :тра
щаемые ·:>тим методом ре:~ультаты вводятся в список. (Симво11ы1ые строкн 11е
сортируются; подробнее об этом реч1, пойдет в разделе
7.4.)
Во1к11i1 раз, когда
по111,зовател1, выбирает другие регионалы1ые настройки или способ форматиро
вания чисел, со:1дается новый форматирующий объект и 061ю1ияется содерж11мое текстового поля. Если по111,зовате111, щелкнет 11а кнопке
метод
p arse
Parse,
то вызывается
(),преобразующий в число 01м1юлы1ую строку в соответствии с вы
бранными регио11алы1ыми настройками.
НА ЗАМЕТКУ! Длs~ чтениs~ локализованных целых чисел, а та кже чисел с плаваю щей точкой
можно воспользоватьсs~ классом
Scanner,
вызвав метод
useLocale ()
из этого класса
длs~ установки региональных настроек.
Листинг
7.1.
Исходный код из файла numЬerFormat/NumЬerFormatTest. java
1
package
2
3
4
S
6
import
import
import
import
numЬer Format;
java.awt .*;
j a va.aw t . eve nt.*;
j ava .t e xt.*;
java .util. * ;
{
EventQueue.invokeLater(() ->
{
var frame = new NumЬerFormatFrame();
frame. setTi tle ( "NumЬerFormatTest");
frame.setDefaultCloseOperation(
JFrame.EXIT ON CLOSE);
frame.setVisiЬle(true);
));
)
)
/**
*
Этот
*
выбора
способа
*
*
список
для
поле
*
а
*
анализа
фрейм содержит
для
также
*/
class
кнопки-переключатели для
форматирования
выбора
региональных
отображения
кнопку для
NumЬerFormatFrame
комбинированный
настроек,
отформатированного
активизации
содержимого
чисел,
текстового
текстовое
числа,
синтаксического
поля
extends JFrame
private Locale[] locales;
private douЫe currentNumЬer;
private JComЬoBox localeComЬo
new JComЬoBox();
private JButton parseButton =
new JButton("Parse");
private JTextField numЬerText
new JTextField(30);
private JRadioButton numЬerRadioButton
new JRadioButton("Number");
private JRadioButton currencyRadioButton
new JRadioButton("Currency");
private JRadioButton percentRadioButton =
new JRadioButton("Percent");
private ButtonGroup rbGroup = new ButtonGroup();
private NumЬerFormat currentNumЬerFormat;
puЫic
else if (currencyRadioButton.isSelected())
currentNumЬerFormat = NumberFormat
.getCurrencyinstance(currentLocale);
else if (percentRadioButton.isSelected())
currentNumЬerForrnat = NumberFormat
.getPercentinstance(currentLocale);
String forrnatted = currentNumЬerFormat
.format(currentNumЬer);
numЬerText.setText(formatted);
java.text.NumЬerFormat
•
Форматирование чисел
static Locale []
1.1
qetAvailaЬleLocales
()
Возвращает массив объектов типа Locale, для которых доступны форматирующие объекты
типа NumЬerFormat.
•
•
•
•
static
NumЬerFormat
static
NumЬerFormat
qetNumЬerinstance(Locale
static
NumЬerFormat
qetCurrencyinstance()
static
NumЬerFormat
qetCurrencyinstance(Locale 1)
•
static
NumЬerFormat
qetPercentinstance()
•
static
NumЬerFormat
qetPercentinstance(Locale 1)
qetNumЬerinstance()
1)
Возвращают объект, форматирующий числа, денежные суммы или числовые величины в про
центах в соответствии с текущими или заданными региональными настройками.
•
Strinq format (douЫe
•
Strinq format (lonq
х)
х)
Возвращают символьную строку, получаемую в результате форматирования заданного числа
с плавающей точкой или целого числа.
Глава
7 •
Интернационализация
j ava. text. NumЬerForma t
•
NwnЬer
1 .1
/окончание/
parse (String s)
Возвращает число, получаемое в результате синтаксического анализа символьной строки.
Это число может иметь тип
Long
или DouЫe, а символьная строка не должна начинаться
с пробелов. Любые символы, следующие в строке после анализируемого числа, игнорируют
ся. При неудачном исходе синтаксического анализа символьной строки генерируется исклю
ParseException.
void setParseintegerOnly(boolean
boolean isParseintegerOnly ()
чение типа
•
•
Ь)
Устанавливают или получают признак, указывающий на то, что данный форматирующий объ
ект предназначен для синтаксического анализа только целочисленных значений.
Устанавливают или получают признак, указывающий на то, что данный форматирующий объ
ект предназначен для распознавания и разделения групп десятичных разрядов !например,
100, ООО) в анализируемых числах.
void set:МinimumintegerDigits(int n)
int get:МinimumintegerDigits()
void setмaximumintegerDigits(int n)
int getмaximumintegerDigits()
void set:МinimwnFractionDigits(int n)
int get:МinimumFractionDigi ts ()
void setмaximumFractionDigits(int n)
int getмaximumFractionDigits()
•
•
•
•
•
•
•
•
Устанавливают или получают максимальное или минимальное количество цифр в целой или
дробной части числа.
7.2.2.
Форматирование денежных сумм в разных валютах
Для форматирования
денежных сумм
служит
метод
Numbe r Fo rma t.
но он не очень удобен, поскольку возвращает формати
getCurrencyinstance (),
рующий объект только для одной валюты. Допустим, для американского :ыказ
чика выписывается счет-фактура, где одни суммы представлены в долларах США,
а другие в евро. Для решения этой задачи нельзя просто воспользоваться двумя
форматирующими объектами, как показа1ю ниже. Счет, в котором фигурируют
такие суммы, как
при
$100,
ООО и
представлении сумм
100.
ОООС, будет выглядеть не совсем обычно. Ведь
в евро для
точка, а сумм в долларах США
NumberFormat dollarFormatter
-
разделения
групп
разрядов используется
:ыпятая.
=
NumЬerFormat.getCurrencylnstance(Locale.US);
NumberFormat euroFormatter
=
NumЬerFormat.getCurrencyinstance(Locale.GERMANY);
Для управления форматированием денежных сумм в ра:шых валютах лучше
воспользоваться классом
Currency.
Сначала получается объект типа
Currency,
7.2.
мя чего статическому методу
Форматирование чисел
Currency. getinstance () передается идентифи
setCurrency () мя каждого форматиру
катор валюты. Затем вызывается метод
ющего объекта. В приведенном ниже фрагменте кода показано, как подстроить
под американского :шказчика объект, форматирующий денежные суммы в евро.
euroFormatter =
Nl1mЬerFormat
NumЬerFormat.getCurrencyinstance(Locale.US);
euroFormatter.setCurrency(Currency.getinstance("EUR") );
Идентификаторы налют определяются по стандарту
Стили форматирования даты и времени с учетом региональных настроек
Стиль
Дата
Время
SHORT
7/16/69
Jul 16, 1969
July 16, 1969
9:32 АМ
9:32: 00
МEDIUМ
LONG
9: 32: 00
9: 32: 00
АМ
АМ
для класса
FULL
Wednesday,
July 16, 1969
EDT
с учетом региональных настроек
МSZ с учетом региональных настроек
en-US,
de-DE (только
ZonedDateTime)
9: 32: 00 АМ EDT с учетом региональных настроек en-US,
9: 32 Uhr мsz с учетом региональных настроек de-DE
(только для класса ZonedDateTime)
В этих средствах форматирования используются текущие региональные на
стройки форматов даты и времени. Чтобы выбрать другие региональные на
стройки, достаточно вызвать метод
wi thLocale ()
следующим образом:
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDate(style) .withLocale(locale);
Теперь можно отформатировать местную дату (объект типа
местное время и дату (объект типа
LocalTime)
но ниже.
LocalDateTime),
или поясное время и дату (объект типа
LocalDate),
местное время (объект типа
ZonedDateTime),
как показа
7.3.
Форматирование даты и времени
ZonedDateTime appointment = ... ,
String formatted = formatter.format(appointment);
НА ЗАМЕТКУ! В данном случае применяется класс
DateTimeFormatter из пакета java.
java. text. DateFormatter, внедренный еще
объектами типа Date и Calendar.
Имеется также устаревший класс
time.
в версии
Java 1.1
для манипулирования
Для синтаксического анализа символьной строки, содержащей дату и вре
мя,
служит один
из
статических
LocalDateTime, LocalTime
или
методов parse ()
ZonedDateTime:
LocalTime time = Loca1Time.parse("9:32
Но методы
parse ()
в
классах
LocalDate,
formatter);
АМ",
из упомянутых выше классов непригодны для синтакси
ческого анализа данных, вводимых пользователем, по крайней мере, для их пред
варительной обработки. Например, средство форматирования даты и времени
в кратком стиле для Соединенных Штатов способно проанализировать символь
"9: 32
ную строку
АМ", но не строку
"9: 32АМ"
или
"9: 32 am".
ВНИМАНИЕ! Средства форматирования дат подвергают синтаксическому анализу несуще
•
ствующие даты вроде
31
ноября, корректируя их по последней дате в данном месяце.
Иногда в календарном приложении требуется отображать, например, толь
ко наименования дней недели и месяцы. С этой целью можно вызвать метод
getDisplayName ()
из перечислений
DayOfWeek
и
Month,
как показано ниже.
for (Month m : Month.values())
System.out.println(m.getDisplayName(textStyle, locale) +" ");
Стили
форматирования
STANDALONE
текста
перечислены
пример, январь по-фински обозначается как
"tammikuu"
Таблица
7.5.
в
табл.
7.5.
Стили
типа
служат для отображения за пределами форматируемой даты. На
"tammikuuta"
в самой дате, но как
за ее пределами или отдельно.
Стили форматирования текста, представленные константами из перечисления
java.time.format.TextStyle
Пример
Стиль
FULL /
FULL_ STANDALONE
SHORT /
NARROW /
SHORT_STANDALONE
NARROW STANDALONE
January
Jan
J
НА ЗАМЕТКУ! Первым днем недели может быть суббота, воскресенье или понедельник в за
висимости от конкретных региональных настроек. Выяснить первый день недели с учетом
региональных настроек можно следующим образом:
DayOfWeek first = WeekFields.of (locale) .getFirstDayOfWeek();
Глава
7 •
Интернационализация
В примере программы, исходный код которой приведен в листинге
монстрируется применение класса
DateFormat
7.2,
де
11а практике. Эта программа по
:~воляет выбирап. ра:ыичные региональные настройки и наблюдать за тем, как
в разных странах форматируются дата и время. На рис.
7.2
пока зано рабочее
окно данной программы после установки китайских шрифтов на компьютере.
Как видите, даты вы1юдято1 11а экран в правильном формате для китайских реги
ональных настроек .
••-.::;
•
::а
.
Localo Chlnes• (ChlnвJ
Oate Lon1_ j~201~5я б 8
Tlme Short 1 • 1•.f4:Зб
Date and time Full
Рис.
\• 1
~
--
~
- -- -
-
Parse
- -' P-a r-se_ ,
-
Т~20lб!!'5Яб8 lila!E 1"f0411
22
{
var frame = new DateTime f ormatt er Frame ( ) ;
frame.setTitle(" DateFo rmatTest " ) ;
frame.setDe faultCl oseOpera ti on(
J frame .EXIT_ON_CLOSE);
Для проверки правил1,ности синтаксического анализа и преобразования сим
вольной строки в даrу достаточно ввести даrу, время или и то и другое, а затем
щелкнуть на кнопке
Parse
(Произвести синтаксический анализ). В рассматривае
мом здесь примере программы используется вспомогателы1ый класс EnumComЬo,
исходный код которого приведен в листинге
бинированного списка значениями типа
7.3.
Он служит для заполнения ком
Short, Medium
и
Long,
а также для ав
томатического преобразования выбранного пользователем иарианта в значение
FormatStyle. SHORT, FormatStyle .MEDIUM или FormatStyle. LONG. Чтобы не пи
сать повторяющийся код, в данном случае применяется рефлексия. Выбранный
пользователем вариант преобразуется в верхний регистр, пробелы заменяются
символами
подчеркивания,
после чего
определяется
значение
в
статическом
поле с полученным в итоге именем. (Более подробно рефлексия рассматривается
в главе
5
Листинг
7.3.
1
2
3
4
5
6
7
8
9
10
11
12
13
первого тома настоящего издания.)
Исходный код из файла dateFormat/EnumComЬo. java
package dateFormat;
import java.util.*;
import javax.swing.*;
/**
* Комбинированный список для выбора среди значений
* статических полей, имена которых задаются в
* конструкторе вспомогательного класса
* @version 1.15 2016-05-06
* @author Сау Horstmann
*/
puЫic class EnumComЬo extends JComЬoBox
Производит синтаксический анализ заданной символьной строки и возвращает описанную
в ней местную дату или время в виде объекта типа
или
ZonedDateTime.
LocalDate, LocalTime, LocalDateTime
DateTimeParseException при неу
Генерирует исключение типа
дачном исходе синтаксического анализа.
Гпава
7.4.
7 •
Интернацнонапизация
Сортировка и нормализация
Как известно, для сравнения символьных строк служит метод
класса
String.
с пользователями. В методе
дировке
cornpareTo ()
из
К сожалению, этот метод не совсем годится для взаимодействия
UTF-16,
cornpareTo ()
применяются строковые значения в ко
что приводит к абсурдным результатам, даже на английском
языке. Например, следующие пять символьных строк упорядочиваются по ре
зультатам сортировки методом
cornpareTo ()
таким образом:
Athens
Zulu
аЫе
zebra
Engstrom
При упорядочении словаря приходится учитывать регистр букв, но совсем не
обязательно ударение. Для англоязычного пользователя приведенный выше пе
речень слов должен быть упорядочен следующим образом:
аЫе
Engstrom
Athens
zebra
Zulu
Но такой порядок следования слов неприемлем для шведскоязычного поль
зователя. Ведь в шведском языке буква А отличается от буквы А и поэтому сорти
руется после буквы
Z!
Это означает, что для шведскоязычного пользователя упо
мянутый выше перечень слов должен быть отсортирован следующим образом:
аЫе
Athens
zebra
Zulu
Angstrom
Чтобы получить компаратор с учетом региональных настроек, следует вызвать
метод
Collator. getinstance (),как показано ниже. Класс Collator реализует
Cornparator, поэтому объект типа Collator можно передать методу
List. sort (Cornpara tor), чтобы отсортировап, символьные строки.
11 Класс Collator реализует интерфейс Comparator:
Collator coll = Collator.getinstance(locale);
words.sort(coll);
интерфейс
Для средств сортировки предусмотрены четыре уровня избирательности: пер
востепенный, второстененный, третьесте11еннь1й и идентичный. Например, в ан
глийском языке отличие букв А и Z считается первостепенным, букв А и А
ростепенным, а букв А и а
-
-
вто
третьестепенным.
Для того чтобы при сортировке внимание обращалось только на первосте
пенные отличия, следует задать уровень ее избирательности
Если задать уровень избирательности
Collator. PRIМARY.
Colla tor. SECONDARY, то будут учтены
и второстепенные отличия. Таким образом, вероятность найти отличия в двух
символьных строках будет больше при установке более высокого уровня избира
тельности, как показано в табл.
7.6.
7.4.
Сортировка и нормализация
Таблица 7.6. Сортировка с разными уровнями избирательности [английские региональные
настройки)
Первостепенный уровень
Angstrom
АЫе
=
аЫе
Третьестепенный уровень
Второстепенный уровень
= Angstrom
Angstrom
АЫе
=
*
Angstrom
Angstrom
аЫе
АЫе
Если же установлен уровень избирательности
*
*
Angstrom
аЫе
Collator. IDENTICAL,
то отли
чия не допускаются. Этот уровень избирательности используется главным об
разом вместе с режимом раJ.ложения на составляющие, который устанавливается
для сортировки и рассматривается ниже.
Иногда символ или последовательность символов могут быть описаны не
только в Юникоде. Например, символу А в Юникоде соответствует код U+OOC5.
С другой стороны, его можно представить в виде последовательности символов А
(код
U+0065)
и
0
тельность букв
гатура
ffi"
(кружок сверху; код U+ОЗОА). Еще удивительнее, что последова
"ffi"
может быть описана одним символом "латинская малая ли
с кодом U+FBOЗ. (Можно, конечно, спорить, что это вопрос представ
ления символов, решение которого не должно приводить к появлению разных
символов в Юникоде, но правила установлены не нами.)
В стандарте на Юникод определяются четыре формы норма.лUJации символь
ных строк (О, КО, С и КС; подробнее об этом см. по адресу
org/reports/trl5/tr15-23.html).
http://www.unicode.
Две из этих форм используются для сортировки.
В форме нормализации О символы с ударением раскладываются на составляю
щие их буквы и ударения. Например, символ А раскладывается на составляющие
символы А и
0
•
А в формах нормализации КС и КО на составляющие расклады
ваются такие символы, как лигатура
ffi
или знак торговой марки ти.
Для сортировки можно выбрать определенную степень нормализации. Так,
если установить значение константы
Collator.NO_DECOMPOSITION,
то символь
ные строки вообще не будут нормализованы при сортировке. В этом режиме со
ртировка выполняется быстрее, но он может быть непригодным для сортировки
текста, где символы выражаются во многих формах. По умолчанию устанавли
вается значение константы
Collator. CANONICAL _DECOMPOSITION,
определяющее
режим, в котором используется форма нормализации О. Это самая полезная
форма для сортировки текста, содержащего символы с ударениями, но не ли
гатуры. Наконец, в режиме полного разложения на составляющие используется
форма нормализации КО. Характерные примеры сортировки в режимах разло
жения на составляющие приведены в табл.
Таблица
7.7.
7.7.
Сортировка в разных режимах разложения на составляющие
Беэ разложения
Каноническое разложение
на составляющие
на составляющие
Полное разложение
на составляющие
А
А
""=
тм
Глава
7 •
Интернационализация
Если одна символьная строка сравнивается многократно с другими строками,
то во избежание ее повторного разложения на составляющие и ради повыше
ния эффективности результат разложения на составляющие следует сохранить
в объекте ключа сортировки. Например, в приведенном ниже фрагменте кода ме
тод
getCollationKey ()
возвращает объект типа
CollationKey,
используемый
для ускорения всех последующих операций сравнения.
if(aKey.compareTo(coll.getCollationKey(b)) == 0)
Наконец, символьные строки иногда требуется преобразовать в их нормали:ю
ванные формы, не прибегая к сортировке. Такая потребность возникает, напри
мер, при сохранении символы1ых строк в базе данных или при юаимодействии
с другой программой. Для этой цели служит класс
java. text. Normalizer,
вы
полняющий процесс нормализации, как показано ниже. Нормализо11анная стро
ка содержит десяп, символов. Символы А и Ь заменяются последовательностями
символов "А 0
"
и "Ь".
String name = "Angstrom";
11 использовать форму нормализации D:
String normalized = Normalizer.normalize(
name, Normalizer.Form.NFD);
Тем не менее это обычно не самая лучшая форма для хранения и передачи
символьных строк. В форме нормализации С сначала 11ыполняется разложение
на составляющие, а затем в установленном порядке присоединяются ударения.
В соответсrвии с рекомендациями консорциума WЗС такой режим является наи
более предпочтительным для передачи данных через Интернет.
Программа, исходный код которой приведен в листинге
7.4,
по:шоляет экспе
риментировать с разными видами сортировки. Досrаточно ввести слово в тексто
вом поле и щелкнуть на кнопке
Add,
чтобы добавить введенное слово в список.
Список сортируется заново после добавления в него каждого слова, изменения
региональных настроек (в раскрывающемся списке Locale), уровня избиратель
ности сортировки (в раскрывающемся списке
на составляющие (в раскрывающемся списке
Strength) или режима разложения
Decomposition). Знак равенства (=)
обозначает, что слона считаются одинаковыми (рис.
7.3).
Наименования регионалыiых настроек в раскрывающемся списке Locale ото
бражаются в порядке, отсортированном в соответствии с устанавливаемыми
по умолчанию региональными настройками. Так, если запустип, рассматривае
US English, то
(Norway, Nynorsk) окажутся выше в дан
настройки Norwegian (Norway), несмотря на то,
мую здесь программу при стандартных региональных настройках
региональные настройки
Norwegian
ном списке, чем региональные
что значение знака запятой в Юникоде больше, чем значение :шака закрываю
щей скобки.
7.4.
tcompos1t1on.Full Dtcomposition
__ I дdd [_ -
-
Сортировка и нормализация
-в
=---~~
аые
=
Рис.
АЬlе
7.3.
Ра60•1ее окно 1!ро1раммы
Листинг 7.4. Исходный код из файла
1
2
3
4
5
6
7
8
9
10
11
12
13
import
import
import
import
import
java . a wt.*;
java.awt.event.*;
java. tex t.*;
java.t1t il.*;
java .u t il.List;
i mport
JЗ vax .s w i n g.*;
/ **
* В этой про грамме демон с триру е1'ся сортировка
* симsольн ых с трок при выборе разных ре гион альных
* настроек
* @ve rsi o n 1.1 6 2018-05-0 1
* @a uthor Сау Horstmann
15
16
*/
17
18
puЫi c
19
(
c la ss Co llatio nTes t
20
puЫic
21
{
2Э
24
25
26
collation/CollationTest. java
package co ll a ti o n;
14
22
CollationTest
s tati c vo id main (String [] args)
Event Que ue .invokeLater (( ) - >
(
var frame = ne w Co ll a t 1onFrame(J;
fr ame . setTit l e ("Colla t i o nT es t" ) ;
fr a me . setDefau lt Cl oseOpera t ion(
sortedWords.setText("");
for (int i
О; i < strings.size(); i++)
{
String s
strings.get(i);
if (i >О && currentCollator.compare(s,
strings.get(i - 1)) == 0)
sortedWords.append("= ");
sortedWords.append(s + "\n");
pack();
java.text.Collator 1.1
•
s ta tic Locale []
qetAvailaЬleLocales
Возвращает массив объектов типа
типа
Locale,
()
для которых существуют сортирующие объекты
Collator.
•
static Collator qetinstance()
•
static Collator qetinstance (Locale 1)
•
int compare (Strinq
Возвращают объект типа
Collator для
а,
Strinq
текущих или заданных региональных настроек.
Ы
Возвращает отрицательное значение, если строка а предшествует строке Ь; нулевое значе
ние, если строки считаются одинаковыми; или положительное значение, если строка Ь пред
шествует строке а.
•
boolean equals (Strinq
а,
Возвращает логическое значение
че
•
•
-
логическое значение
Strinq
true,
Ь)
если строки а и Ь считаются одинаковыми. а ина
false.
void setStrenqth (int strength)
int qetStrenqth()
Устанавливают или получают уровень избирательности сортировки. Чем выше уровень из
бирательности, тем больше вероятность того, что сортируемые слова будут признаны разны
ми. Поддерживаются следующие уровни избирательности сортировки:
Collator. SECONDARY
•
•
И
Collator. PRIМARY,
Collator. ТERTIARY.
void setDecomposition (int decomp)
int qetDecompositon ()
Устанавливают или получают режим разложения на составляющие при сортировке символь
ных строк. Чем выше степень разложения на составляющие, тем строже выполняется сравне
ние сортируемых символьных строк. Поддерживаются следующие режимы разложения на со
ставляющие:
И
•
Collator .NO_DECOМPOSITION, Collator. CANONICAL_DECOМPOSITION
Collator. FULL DECOМPOSITION.
CollationКey
qetCollationКey(Strinq
а)
Возвращает ключ сортировки с разложенными на составляющие символами, чтобы быстро
сравнить их по другому ключу сортировки.
7.5.
Форматирование сообщений
java.text.CollationKey 1.1
•
int compareTo(CollationKey
Ь)
Возвращает отрицательное значение, если данный ключ сортировки предшествует ключу Ь;
нулевое значение, если ключи одинаковы; или положительное значение, если данный ключ
следует за ключом Ь.
java.text.Normalizer 6
static String normalize (CharSequence str, Normalizer. Form form)
Возвращает нормализованную форму символьной строки
str. Параметр form может прини
мать одно из следующих значений: ND, NКD, NC или NКС.
7.5.
Форматирование сообщений
В состав библиотеки
Java
входит класс
MessageFormat для форматирования
текста, содержащего фрагменты с переменными. Этот механизм подобен форма
тированию с помощью метода
printf
(),но он действует с учетом региональных
настроек, а также форматов чисел и дат. В последующих разделах этот механизм
рассматривается более подробно.
7.5.1.
Форматирование чисел и дат
Ниже приведен пример типичной строки форматирования сообщений, где
номера в фигурных скобках служат в качестве заполнителей для подлинных
имен и :шачений.
{2), а {0) destroyed {1) houses
caused {3} of damage."
"Оп
апd
Подставить значения переменных можно с помощью статического метода
MessageFormat. format ()
с переменным числом параметров, где подстановка
значений переменных может быть произведена следующим образом:
Striпg
1/1/99 12:00 АМ, а hurricaпe destroyed 99 houses and
caused 100,000,000 of damage.
Результат для начала неплохой, но вряд ли может устроить полностью. В част
ности, время 12:
00
АМ отображать не следует, а сумму ущерба от урагана нужно
представить в денежных единицах. Это можно сделать, указав формат для неко
торых переменных, как выделено ниже полужирным.
Глава
7 •
Интернационализация
{2,date,long},
"Оп
caused
а
{О}
destroyed {1} houses
of damage."
апd
{3,numЬer,currency}
В результате очередной подстановки получается следующая строка:
January 1, 1999, а hurricaпe destroyed 99 houses
caused $100,000,000 of damage.
Оп
апd
Обычно после заполнителя допускается задавать тип и стиль, разделяя их за
пятыми. Ниже перечислены допустимые типы.
пumber
time
date
choice
Если указан тип numЬer, то допускаются следующие стили:
iпteger
curreпcy
perceпt
В качестве стиля может быть также ука3ан шаблон числового формата, напри
мер $,##О. (Подробнее об этом см. в документации на класс
Для типа
time
или
date
Decimal Forma t.)
может быть указан один из следующих стилей:
short
medium
loпg
full
Аналогично числам, в качестве стиля может быть указан шаблон даты, напри
мер ггг-мм-дд. (Допустимые форматы подробно рассматриваются в документа
ции на класс
•
SimpleDateFormat.)
ВНИМАНИЕ! Статический метод
format ()
форматирует значения с учетом текущих регио
нальных настроек. Форматировать сообщения средствами класса
MessageFormat
с учетом
произвольных региональных настроек немного сложнее, поскольку в этом классе отсутствует
метод с переменным числом аргументов. Поэтому форматируемые значения придется разме
стить в массиве
var mf
=
Striпg
msg =
Object [],
как показано ниже.
loc);
Object[] { значения});
пеw MessageFormat(patterп,
mf.format(пew
java.text.MessageFormat 1.1
•
Locale getLocale {)
Устанавливают или получают региональные настройки для заполнителей в сообщении.
Региональные настройки пригодны только для последующих шаблонов, задаваемых с помо
щью метода
•
applyPattern ().
static String format(String pattern, Object ... args)
Форматирует символьную строку по шаблону ра ttern, подставляя вместо заполнителей
ет массив объектов. Форматируемая строка добавляется к значению параметра
которое затем возвращается. Если параметр
pos
resul t,
принимает ссылку на новый объект
new
FieldPosi tion (MessageForma t. Field. ARGUМENT) , его свойства beginindex
и endindex устанавливаются в соответствии с расположением текста, который подставля
ется вместо заполнителя { 1}. Если же сведения о расположении подстановочного текста не
важны, в качестве параметра pos следует задать пустое значение null.
java.text.Format 1.1
•
String format (Object obj)
Форматирует заданный объект по правилам, определяемым текущим форматирующим объек
том. С этой целью делается следующий вызов:
format (obj,
new StringBuffer () ,
new Field Posi tion ( 1) ) . toString () .
7.5.2.
Форматы выбора
Вернемся к шаблону из предыдущего раздела, чтобы рассмотреть его подробнее:
"On {2},
а
{0} destroyed {1} houses and caused {3} of damage."
Если вместо заполнителя {О
ставить строковое значение
},
обозначающего вид стихийного бедствия, под
"earthquake"
(землетрясение), то получится следу
ющее предложение, нарушающее правила английской грамматики в отношении
используемых артиклей:
On January 1, 1999,
а
earthquake destroyed . . .
Для устранения этой грамматической ошибки артикль а придется ввести в за
полнитель {О} следующим образом:
"On {2}, {0} destroyed (1} houses and caused {3} of damage."
Теперь вместо заполнителя {О} будет подставлен текст "а
"an
earthquake ".
hurricane"
или
Такой способ особенно удобен для перевода сообщений
на языки, в которых в каждом роде употребляется отдельный артикль. Напри
мер, на немецком языке этот шаблон должен выглядеть так:
"{0} zerstbrte am {2} {1} H!user und richtete einen Schaden
von {3} an."
В этом случае заполнитель будет заменяться грамматически правильными со
четаниями артикля и имени существительного, например
и
"Ein
Wirbelsturrн"
"Eine Naturkatastrophe".
Теперь рассмотрим заполнитель
{1}.
Если стихийное бедствие оказалось не
очень разрушительным, то вместо этого заполнителя можно подставить значе
ние
1.
Но и в этом случае получится предложение с нарушением правил англий
ской грамматики:
On January 1, 1999,
а
mudslide destroyed 1 houses and . . .
Глава
7 •
Интернационализация
Желательно, чтобы текст сообщения грамотно изменялся в соответствии с од
ним из следующих подставляемых значений:
no houses
one house
2 houses
Именно для этой цели и был внедрен формат выбора типа
choice.
В соответ
ствии с этим форматом задаето1 последователыюст1, пар значений, каждая и:1 ко
торых содержит нижний предел и фор.чатирующую строку. Нижний предел и фор
матирующая строка разделяются знаком
знак
1.
#,
а для разделения пар значений служит
Ниже приведен пример заполнителя
{1},
в котором формат выбора выде
лен полужирным. Резул1,таты форматирования текста сообщения в зависимости
от :шачения, подставляемого вместо заполнителя
А зачем в форматирующей строке дважды ука:1ьшается заполнитель
{1}?
Ког
да к этому заполнителю применяется формат выбора и значение оказывается
равным
2,
возвращаето1 символьная строка"
{1} houses".
Эта строка формати
руется еще раз и включается 11 результирующую строку сообщения.
НА ЗАМЕТКУ! Приведенный выше пример показывает, что разработчики формата выбора
приняли не самое лучшее решение. Так, если имеются три форматирующие строки, то для их
разделения требуются два предела. Как правило, количество пределов должно быть на едини
цу меньше, чем количество форматирующих строк. И как следует из табл.
7.8,
первый предел
в классе МessaqeForшat вообще игнорируется. Синтаксис форматирования сообщений мог
бы быть более понятным, если бы пределы указывались между выбираемыми вариантами,
например, следующим образом:
no houses 111 one house 12 1{1} houses
11 более понятньм, но не действующий
С помощью :шака
< можно указать,
формат
что предлагаемый вариант должен быть вы
бран, если нижний предел ока:1ывается строго меньше подставляемого :шачения.
Вместо :ша ка
#
можно также ука:швать знак
:::; (\ u2 2 6 4
в Юникоде). По желанию
можно даже ука:1ать для пер но го значения нижний предел равным -оо
Получают имена, расширени>~ файлов сценариев и типы
MIME,
по которым известна данна>~
фабрика.
8.1.2.
Выполнение сценариев и привязки
Получив интерпретатор, можно приступить к вызову сценария:
Object result = engine.eval(scriptString);
Если сценарий хранится в файле, необходимо открыть поток чтения типа
Reader
и сделать следующий вызов:
Object result
=
engine.eval(reader);
С помощью одного и того же интерпретатора можно вызвать целый ряд сце
нариев. Если какой-нибудь из сценариев содержит определения переменных,
функций или классов, большинство интерпретаторов сценариев будет сохранять
их для последующего использования. Так, в приведенном ниже примере кода
возвращается значение
172 9.
engine.eval("n = 1728");
Object result = engine.eval("n + l");
Глава В
•
Написание сценариев. номпипяция и обработка аннотаций
НА ЗАМЕТКУ! Чтобы выяснить, безопасно ли параллельное выполнение сценариев во многих
потоках, достаточно сделать следующий вызов:
Object param = factory.getParameter("THREADING");
В итоге возвращается одно из перечисленных ниже значений.
параллельное выполнение небезопасно.
•
null:
•
"МULTITНREADED": параллельное выполнение безопасно. Результаты исполнения одного
потока могут быть доступны для другого потока.
•
"TНREAD-ISOLAТED": то же, ЧТО и значение "МULTIТНREADED", НО только для каждого
потока исполнения поддерживаются разные привязки переменных.
•
"STATELESS":
то же, что и значение "TНREAD-ISOLAТED", но только сценарии не могут
изменять привязки переменных.
Интерпретатор сценариев нередко требуется дополнят~, привязками перемен
ных. Каждая привязка состоит из имени и связываемого объекта
Java.
Рассмо
трим в качестве примера следующие операторы:
engine.put(k, 1728);
Object result = engine.eval("k + 1");
Код сценария читает определение объекта
k из
привязок в области видимости
интерпретатора. И это очень важно, так как почти все языки сценариев могут
получать доступ к объектам
у
Java
и зачастую посредством более простого, чем
синтаксиса. Например:
Java,
engine.put(b, new JButton() );
engine.eval("b.text = 'Ok'");
С другой стороны, можно извлекать значения переменных, привя:ынных опе
раторами сценария, как показано ниже.
engine.eval("n = 1728");
Object result = engine.get("n");
Кроме области видимости интерпретатора сценариев, существует и глобаль
ная область видимости. /lюбые привязки, которые вводятся в объект типа
EngineManager,
Script
становятся видимыми для всех и1rrерпретаторов сценариев.
Вместо того чтобы вводить привязки в глобальную область видимости или
в область видимости интерпретатора сценариев, их можно накапливать в объ
екте типа
Bindings
и передавать методу
eval
(),как пока:ыно ниже. Это очень
удобно, если набор привязок не требуется сохранять для последующих вы:ювов
метода
eval ().
Bindings scope = engine.createBindings();
scope.put(b, new JButton() );
engine.eval(scriptString, scope);
НА ЗАМЕТКУ! Безусловно, может возникнуть потребность иметь и другие области видимости,
отличающиеся как от глобальной области видимости, так и от области видимости интерпре
татора сценариев. Например, веб-контейнеру могут потребоваться области видимости за
просов и сеансов. В подобных случаях разработчикам приходится самостоятельно создавать
класс, реализующий интерфейс
ScriptContext,
чтобы управлять набором своих областей
видимости. Каждая такая область видимости должна снабжаться целочисленным номером,
8.1.
Написание сценариев дnя платформы
Java
а поиск должен выполняться в первую очередь в областях видимости с наименьшим номе
ром.
IB стандартной библиотеке доступен только класс SimpleScriptContext, но он пред
усматривает лишь глобальную область видимости, а также область видимости интерпретатора
сценариев.!
javax. script. ScriptEngine 6
•
Object eval (String script)
•
Object eval (Reader reader)
•
Object eval (String script, Bindings
•
Object eval (Reader
readвr,
Bindings
Ьindings)
Ьindings)
Вычисляют сценарий, предоставляемый в символьной строке или средством чтения с учетом
заданных привязок.
•
Object get (String key)
•
void put (String
kву,
Object value)
Получают или размещают привязку в области видимости интерпретатора сценариев.
•
Bindings createBindings()
Создает пустой объект типа
Bindings,
пригодный для данного интерпретатора сценариев.
javax.script.ScriptEngineManager 6
•
Object get (String key)
•
void put (String
kву,
Object value)
Получают или размещают привязку в глобальной области видимости.
javax.script.Bindings 6
•
Object get (String key)
•
void put (String
kву,
Object value)
Получают или размещают в области видимости привязку, представляемую данным объектом
типа
Bindings.
8.1.З. Переадресация ввода-вывода
Стандартный ввод-вывод можно переадресовывать в сценарии, вызывая метод
setReader ()
или
setWri ter ()
соответственно в контексте сценария, как пока
зано в приведенном ниже примере кода, где любые данные, выводимые с помо
щью таких функций
екту
JavaScript,
как
print ()
или
println (),
направляются объ
writer.
var writer = new StringWriter();
engine.getContext() .setWriter(new PrintWriter(writer, true) );
Глава
Методы
8 •
Написание сценариев, компиляция и обработка аннотаций
setReader ()
и
setWri ter ()
воздейсrвуют только на стандартные исrоч
ники ввода-вывода данных в интерпретаторе сценариев. Например, при выполне
нии приведенного ниже кода в сценарии
JavaScript перенаправлен будет только
Nashom ничего неизвестно о стандартном
метода setReader () ничего не даст.
первый вывод. Интерпретатору сценариев
исrочнике ввода данных, поэтому вызов
javax. script. ScriptEngine 6
ScriptContext getContext()
Получает стандартный контекст сценариев для данного механизма.
javax.script.ScriptContext 6
•
Reader getReader ()
void setReader (Reader reader)
Writer getWriter ()
void setWri ter (Wri ter wri ter)
Writer getErrorWriter()
void setErrorWri ter (Wri ter wri ter)
•
•
•
•
•
Получают или устанавливают поток чтения для вводимых данных или поток записи для обыч
ных или уведомляющих об ошибках выводимых данных.
8.1.4.
Вызов функций и методов из сценариев
При наличии многих интерпретаторов сценариев функция может вызываться
на языке сценариев и без вычисления конкретного кода сценария. Это удобно,
если пользователям разрешается реали:ювывать службу на избранном ими языке
сценариев.
Те интерпретаторы сценариев, которые предоставляют подобные функцио
нальные возможности, реализуют интерфейс InvocaЫe. В частности, этот ин
терфейс реализуется интерпретатором сценариев
Nashom. Чтобы вызвать функ
invokeFunction (),указав
цию из сценария, достаточно обратиться к методу
в нем имя и параметры требуемой функции:
11 определить функцию приветствия в JavaScript
engine.eval("function greet(how, whom)
{ return how + ', ' + whom + ' 1 ' } " ) ;
11 вызвать эту функцию с аргументами "Hello", "World"
result = ( (InvocaЬle) engine) .invokeFunction("greet",
"Hello", "World");
Если же язык сценариев является объектно-ориентированным, можно вызвать
метод
11
invokeMethod ()
определить
следующим образом:
класс
Greeter в JavaScript:
engine.eval("function Greeter(how) { this.how
how } ");
8.1.
Написание сценариев дпя платформы
Java
engine.eval("Greeter.prototype.welcome ="
+ " function (whom) "
+ "{ return this.how + ', ' + whom + '1' 1");
11 получить экземпляр:
Object уо = engine.eval("new Greeter('Yo')");
11 вызвать метод приветствия для экземпляра:
result = ( (InvocaЬle) engine) .invokeMethod(yo,
"welcome", "World");
НА ЗАМЕТКУ! Подробнее об определении классов в JavaScгipt см. в книге
НА ЗАМЕТКУ! Даже если механизм сценариев не реализует интерфейс InvocaЫe,
метод все равно можно вызвать не зависящим от конкретного языка образом. Метод
getмethodCallSyntax
()
из класса
ScriptEngineFactory формирует символьную
eval (). Но все параметры этого метода долж
строку, которую можно затем передать методу
ны быть привязаны к именам, в то время как метод invokeМethod О может вызываться
с произвольными значениями параметров.
Можно пойти еще дальше и запросить интерпретатор сценариев реализовать
интерфейс
Java.
В этом случае появится возможность вызывать функции и мето
ды из сценариев, используя синтаксис
Java
для вызова методов. И хотя это зави
сит от конкретного интерпретатора сценариев, как правило, для каждого метода
из интерфейса достаточно предоставить соответствующую функцию. Рассмо
трим в качестве примера следующий интерфейс
puЬlic
Java:
interface Greeter
{
String greet(String whom);
Если определить глобальную функцию с тем же именем в
Nashom,
ее можно
вызвать через следующий интерфейс:
11 определить функцию приветствия в JavaScript:
engine.eval("function welcome(whom)"
+ " { return 'Hello, ' + whom + '!' 1");
11 получить объект Java и вызвать метод Java:
Greeter g = ( ( InvocaЬle) engine) .getinterface (Greeter.class);
result = g.welcome("World");
В объектно-ориентированном языке сценариев доступ к классу из сценария
можно получить через соответствующий интерфейс
Java.
ре кода демонстрируется, каким образом объект класса
JavaScript
Greeter g
вызывается в синтаксисе языка
В следующем приме
SimpleGreeter
из языка
Java:
( (InvocaЬle) engine)
.getinterface(yo, Greeter.class);
result = g.welcome("World");
=
1 В русском 11ереводе эта книга вышла IIOД названием
тельстве "Питер", СПб.
2012
г.
favaScript.
Cu,\t>Hl>le стороны в изда
Глава В
•
Написание сценариев, компиляция и обработка аннотаций
Таким образом, интерфейс InvocaЫe оказывается удобным в том случае,
если требуется вызвап, код сценария из кода
Вызывают функцию или метод с указанным именем, передавая заданные параметры.
Т
getinterface (Сlавв iface)
Возвращает реализацию указанного интерфейса, методы которого реализуются с помощью
функций из механизма сценариев.
•
Т
getinterface (Object implicitEarameter, Class iface)
Возвращает реализацию указанного интерфейса, методы которого реализуются с помощью
методов заданного объекта.
В.1.5. Компиляция сценариев
Некоторые интерпретаторы сценариев способны компилировать код сценария
в промежуточную форму дл~I более эффективного выполнения. Такие интерпре
таторы реализуют и1перфейс CompilaЫe. В следующем примере кода демонстри
руется компилирование и вычисление кода, содержащегося в файле сценария:
var reader = new FileReader("myscript.js");
CompiledScript script = null;
if (engine implements CompilaЬle)
CompiledScript script =
( (CompilaЬle) engine) .compile(reader);
После компиляции сценария можно перейти к его выполнению. В приведен
ном ниже фрагменте кода демонстрируется выполнение скомпилированного
кода сценария, если компиляция прошла успешно, а иначе
-
исходного сцена
рия, если окажется, что механизм сценариев не поддерживает компиляцию. Без
условно, компилировап, сценарий нужно лишь в том случае, если его требуется
ВЫПОЛНИТ!> повторно.
if (script != null)
script.eval();
else
engine.eval(reader);
Рассматриваемая здесь программа начинается с загрузки интерпретатора сце
нариев на языке, указываемом в командной строке. Если же язык не указан, то
по умолчанию выбирается
JavaScript.
Далее выполняется обработка сценария
ini t. язык,
это удобно, поскольку интерпретаторы языков
если таковой имеется. И
R и Scheme
нуждаются в ряде гро
моздких операций инициализации, которые вряд ли стоит включать в каждый
сценарий для обработки событий.
После этого осуществляется рекурсивный обход всех дочерних компонентов
и ввод привязок (имя, объект) в область видимости интерпретатора сценари
ев. Далее выполняется чтение из файла свойств язык.properties. Для каждо
го свойства конструируется прокси-объект, замещающий обработчик событий,
который, собственно, и заставляет выполняться код сценария. Подробности ре
ализации механизма замещения носят несколько технический характер, поэто
му тем, кто желает разобраться в нем, рекомендуется еще раз прочитать раздел,
Глава
8 •
Написание сценариев, компиляция и обработка аннотаций
посвященный прокси-объектам в главе
6
первого тома настоящего издания. Но
самое главное, что каждый обработчик событий вызывает следующий метод:
engine.eval(scriptCode);
Остановимся подробнее на обработке событий от кнопки выбора желтого
цвета фона (объекте
yellowButton). При обработке приведенной ниже
JButton под именем "yellowButton".
строки
кода обнаруживается компонент
yellowButton.action=panel.background = java.awt.Color.YELLOW
Далее к этому компоненту присоединяется объект типа
тодом
"panel" с объектом типа JPanel. Когда наступает событие, выполняется метод
setBackground () для этого объекта, а в итоге изменяется цвет фона панели.
Запустить рассматриваемую здесь программу с обработчиками событий из
сценария
JavaScript
можно, выполнив команду
java ScriptTest
А для того чтобы использовать обработчики событий из сценария
Groovy,
нужно выполнить такую команду:
java -classpath . :groovy/lib/\* ScriptTest groovy
где
каталог, в котором установлен язык
groovy -
R по
проекту
Groovy.
Для реализации языка
архивные JАR-файлы для библиотеки
Renjin
претатора сценариев
Renjin
Renjin Studio
и интер
следует включить в путь к соответствующим классам.
Оба эти компонента свободно доступны для загрузки по адресу
www. renj in.
org /downloads. html.
В рассматриваемом здесь примере программы демонстрируется применение
сценариев при программировании графического пользовательского интерфейса
на платформе
Java.
Желающие могут пойти еще дальше и описать такой интер
фейс с помощью ХМL-файла, как было показано в главе
3.
В этом случае данная
программа превратится в интерпретатор графических пользовательских интер
фейсов с визуальным представлением, определяемым в формате
XML,
а также
поведением, определяемым на языке сценариев. Это очень похоже на среду соз
дания динамических серверных сценариев и динамических НТМL-страниц.
{
EventQueue.invokeLater( () ->
{
try
{
var manager = new ScriptEngineManager();
String language;
if (args.length == 0)
{
System. out .println ( "AvailaЫe factories: ");
for (ScriptEngineFactory factory :
manager.getEngineFactories())
System.out.println(factory.getEngineName());
language
=
else language
"nashorn";
=
args[OJ;
38
final ScriptEngine engine
manager.getEngineByName(language);
if (engine == null)
String name = c.getName();
if (name != null) { namedComponents.put(name, с); )
if (с instanceof Container)
{
for (Component child :
( (Container) с) .getComponents())
getComponentBindings(child, namedComponents);
/**
* Вводит в объект приемник событий, метод которого
* выполняет сценарий
* @param beanName Имя компонента JavaBeans,
*
в который вводится приемник событий
* @param eventName Имя компонента JavaBeans,
"action" (действие)
"change" (изменение)
*
например,
*
или
* @param scriptCode Выполняемь!Й код сценария
* @param engine Интерпретатор, выполняющий
код
сценария
* @param bindings Привязки для выполнения сценария
* @throws Исключение типа IntrospectionException
*/
private static void addListener(String beanName,
Написание сценариев. компиляция и обработка аннотаций
ButtonFrarne()
{
setSize(DEFAULT_WIDTH, DEFAULT HEIGHT);
panel = new JPanel();
panel.setNarne("panel");
add(panel);
yellowButton = new JButton("Yellow");
yellowButton.setNarne("yellowButton");
ЫueButton = new JButton("Blue");
ЫueButton.setNarne("ЬlueButton");
redButton = new JButton("Red");
redButton.setNarne("redButton");
panel.add(yellowButton);
panel.add(ЬlueButton);
panel.add(redButton);
8.2.
Прикладной интерфейс
API
для компилятора
Имеется немало инструментальных средств, в которых требуется вызывать
компилятор
Java.
К их числу, очевидно, относятся среды разработки и средства
обучения программированию на
Java,
а также инструментальные средства, авто
матизирующие процессы тестирования и построения прикладного кода. Еще од
ним тому примером служит обработка веб-страниц типа
встроенными операторами
8.2.1.
JSP (JavaServer Pages)
со
Java.
Вызов компилятора
Компилятор вызывается очень просто, как показано в приведенном ниже
примере кода. Получаемое в итоге нулевое значение переменной
resul t
указы
вает на удачный исход компиляции.
JavaCornpiler cornpiler =
ToolProvider.getSysternJavaCompiler();
OutputStream outStream = . . . ;
OutputStream errStream = ... ;
int result
compiler.run(null, outStream, errStream,
"-sourcepath", "src", "Test.java");
Все выводимые данные и сообщения об ошибках компилятор направляет
в указанные потоки вывода. В качестве параметров метода
вать и пустое значение
вывода
System. out
и
run () можно указы
null. В данном случае используются стандартные потоки
System. err. Первый параметр метода run () обозначает
поток ввода, но, поскольку никаких данных, вводимых с консоли, компилятор не
принимает, значение этого параметра всегда оставляется пустым
метод
(null). Сам же
run () наследуется из обобщенного интерфейса Tool, допускающего при
менение инструментальных средств для чтения вводимых данных.
8.2.
Остальные параметры метода
ло бы передать утилите
j avac,
run ()
Прикладной интерфейс
API
для компилятора
являются аргументами, которые следова
если бы этот метод вызывался из командной стро
ки. Они могут обозначать как параметры командной строки, так и имена файлов.
8.2.2.
Запуск заданий на компиляцию
С помощью объекта типа
CompilationTask
можно получить еще больший
контроль над процессом компиляции. Это может быть удобно в том случае, если
требуется предоставить исходный код из символьной строки, зафиксировать
файлы классов в оперативной памяти или обработать сообщения или преду
преждения об ошибках или неполадках.
Чтобы
получить
CompilationTask,
задание
на
компиляцию
в
необходимо получить сначала объект
виде
объекта
compiler,
типа
как было по
казано в предыдущем разделе, а затем сделать следующий вызов:
JavaCompiler.CompilationTask task = compiler.getTask(
/!если указано значение null этого параметра,
// то используется поток вывода System.err:
errorWr i ter,
//если указано значение null этого параметра,
//то используется
стандартньШ диспетчер файлов:
fileManager,
// если указано значение null этого параметра,
// то используется поток вывода System.err:
diagnostics,
//если
конкретное
/!указано,
значение
он принимает
этого параметра
пустое
значение
не
null:
options,
//этот
параметр
//если
конкретное
//указано,
служит для
значение
он принимает
обработки аннотаций;
этого параметра не
пустое
значение
null:
classes,
sources);
Три последних параметра в приведенном выше вызове являются экземпляра
ми типа IteraЫe. Например, последовательность параметров компиляции мо
жет быть задана следующим образом:
IteraЫe
options = List.of ("-d", "bin");
В качестве параметра
пляров типа
sources указывается итератор типа IteraЫe
,JavaFileObj ect, представляющих исходные файлы. Если
экзем
требу
ется скомпилировать файлы, находящиеся на жестком диске, следует получить
стандартный диспетчер файлов в виде объекта типа
и вызвать его метод
Написание сценариев. компиляция и обработка аннотаций
8 •
НА ЗАМЕТКУ! Параметр
classes служит лишь для обработки аннотаций. И в этом случае не
task. processors (annotationProcessors) со списком
Processor. Характерный пример обработки аннотаций приведен в разделе 8.6.
обходимо также сделать вызов
объектов типа
Метод
ge t
Та s
k ()
возвращает объект задания, но пока еще не запуска
ет процесс компиляции.
Класс
Compi 1 а t i onTas k
реализует
интерфейс
CallaЫe. Объект этого класса можно передать исполнителю типа
Execu torService
для параллельного исполнения или же сделать синхронный
вызов, как показано ниже.
Boolean success
=
task.call();
8.2.З. Фиксация диагностики
Для приема появляющихся сообщений об ошибках устанавливается приемник
диагностики, реализующий интерфейс
DiagnosticListener.
Всякий раз, когда
компилятор выдает предупреждение или сообщение об ошибке, этот приемник
получает объект типа
реализуется в классе
Diagnostic. В частности, интерфейс DiagnosticListener
DiagnosticCollector, где собираются все диагностические
данные для просмотра и анализа по завершении компиляции, как демонстриру
ется в следующем примере кода:
DiagnosticCollector collector =
new DiagnosticCollector();
compiler.getTask(null, fileManager, collector, null,
null, sources) .call();
for (Diagnostic